fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
This commit is contained in:
@@ -25,10 +25,12 @@ class FakeClient extends Node:
|
||||
var ents := {1000: {"vid": 1000, "hp": 100, "dead": false}, 2000: {"vid": 2000, "hp": 50, "dead": false, "pos": Vector3(1, 0, 0)}}
|
||||
var attacks := []
|
||||
var moves := []
|
||||
var order := []
|
||||
var synced := []
|
||||
var said := []
|
||||
var affects := []
|
||||
var fly_targets := [] # §3.6:CG_FLY_TARGETING
|
||||
var primary_fly_targets := []
|
||||
var shots := [] # §3.6:CG_SHOOT
|
||||
func is_in_game() -> bool: return true
|
||||
func get_main_vid() -> int: return main
|
||||
@@ -37,9 +39,13 @@ class FakeClient extends Node:
|
||||
func get_affects() -> Array: return affects
|
||||
func attack(motion, vid) -> bool: attacks.append([motion, vid]); return true
|
||||
func add_fly_targeting(vid, x, y) -> bool: fly_targets.append([vid, x, y]); return true
|
||||
func fly_targeting(vid, x, y) -> bool:
|
||||
primary_fly_targets.append([vid, x, y])
|
||||
fly_targets.append([vid, x, y])
|
||||
return true
|
||||
func shoot(skill) -> bool: shots.append(skill); return true
|
||||
func set_target(v) -> bool: return true
|
||||
func move(a, b, c, d, e) -> bool: moves.append([a, b, c, d, e]); return true
|
||||
func move(a, b, c, d, e) -> bool: moves.append([a, b, c, d, e]); order.append("state"); return true
|
||||
func sync_positions(arr) -> bool: synced.append(arr); return true
|
||||
func say(t, s) -> bool: said.append([t, s]); return true
|
||||
func get_skills() -> Array: return skills
|
||||
@@ -94,6 +100,7 @@ class FakeView extends Node3D:
|
||||
signal motion_bound(state: String)
|
||||
var states := []
|
||||
var attack_motions := [] # [motion_mode, motion_index, speed_ratio]
|
||||
var order: Array = []
|
||||
var delays := []
|
||||
var anim := FakeAnim.new()
|
||||
func _init() -> void:
|
||||
@@ -101,6 +108,7 @@ class FakeView extends Node3D:
|
||||
func set_anim_state(s): states.append(s)
|
||||
func play_attack_motion(mode: int, index: int, speed: float) -> void:
|
||||
attack_motions.append([mode, index, speed])
|
||||
order.append("motion")
|
||||
motion_bound.emit("attack")
|
||||
func insert_delay(t: float) -> void: delays.append(t)
|
||||
|
||||
@@ -260,6 +268,7 @@ func _run() -> void:
|
||||
"pre_input_time": 0.10, "direct_input_time": 0.20, "input_limit_time": 0.40,
|
||||
"next_combo": 0.20,
|
||||
}
|
||||
view.order = fc.order
|
||||
var pcn := Node3D.new(); get_root().add_child(pcn)
|
||||
np.pc.player = pcn
|
||||
np._vid_reserved = 2000
|
||||
@@ -268,8 +277,11 @@ func _run() -> void:
|
||||
|
||||
# 第一次输入:m_dwcurComboIndex 0 -> __RunNextCombo -> 段 1(段号 14)
|
||||
np._do_attack_swing(pcn, {})
|
||||
_ck(fc.order.size() >= 2 and fc.order[-2] == "motion" and fc.order[-1] == "state",
|
||||
"attack motion is bound before FUNC_COMBO state is sent")
|
||||
_ck(np._combo_index == 1, "first input -> combo index 1, got %d" % np._combo_index)
|
||||
_ck(fc.attacks.size() == 1 and fc.attacks[0][0] == 0, "CG_ATTACK bType is skill 0, not combo seg")
|
||||
_ck(fc.attacks.is_empty(), "motion without AttackingData -> no synthetic CG_ATTACK")
|
||||
_ck(not np._has_attack_motion_data, "missing AttackingData is recorded as invalid local hit source")
|
||||
_ck(fc.moves.size() >= 1 and fc.moves.back()[0] == np.FUNC_COMBO and fc.moves.back()[1] == 14,
|
||||
"FUNC_COMBO carries seg no. 14 (NAME_COMBO_ATTACK_1)")
|
||||
_ck(view.attack_motions.size() == 1 and view.attack_motions[0] == [np.MOTION_MODE_GENERAL, 14, 1.0],
|
||||
@@ -316,7 +328,8 @@ func _run() -> void:
|
||||
view.motion_bound.emit("attack")
|
||||
_ck(np._hit_dedup.is_empty(), "bound attacking motion -> hit data map cleared")
|
||||
|
||||
# 没有段表(缺资源)-> 退化为单段普攻(段号 13);攻速系数原样传给动作播放
|
||||
# 没有段表(缺资源)-> 仍按 40250 的单段普攻状态包(段号 13);没有
|
||||
# AttackingData 时不能伪造 CG_ATTACK,攻速系数仍原样传给动作播放。
|
||||
np._combo_tables = {}
|
||||
fc.moves.clear()
|
||||
np._apply_attack_speed(150)
|
||||
@@ -326,6 +339,7 @@ func _run() -> void:
|
||||
"no combo table -> single NORMAL_ATTACK swing")
|
||||
_ck(view.attack_motions.back() == [np.MOTION_MODE_GENERAL, np.NAME_NORMAL_ATTACK, 1.5],
|
||||
"attack motion plays at fSpeedRatio 1.5, got %s" % [view.attack_motions.back()])
|
||||
_ck(fc.attacks.is_empty(), "missing .msa HitDataContainer -> no local CG_ATTACK fallback")
|
||||
np._apply_attack_speed(100)
|
||||
|
||||
# --- __NormalAttackProcess 的 m_HitDataMap:新窗 insert;旧窗 map::insert 不覆盖 + 每窗上限 ---
|
||||
@@ -513,7 +527,7 @@ func _run() -> void:
|
||||
np._attack_process(0.016)
|
||||
_ck(fc.attacks.is_empty(), "victim behind the attacker -> sweep misses")
|
||||
|
||||
# 占位胶囊(无 .msm):退化防御球
|
||||
# 占位节点(无 .msm 防御球):40250 的空 defending list 不命中,不退化为胶囊。
|
||||
var ph := Node3D.new(); get_root().add_child(ph)
|
||||
await process_frame
|
||||
ph.global_position = Vector3(0, 0, 1.0)
|
||||
@@ -521,7 +535,7 @@ func _run() -> void:
|
||||
_reset_hit(np, fc, nw, fx)
|
||||
np._motion_start_t = np._local_time - 0.105
|
||||
np._attack_process(0.016)
|
||||
_ck(fc.attacks.size() == 1, "node without defending spheres -> fallback sphere -> hit")
|
||||
_ck(fc.attacks.is_empty(), "node without .msm defending spheres -> no synthetic hit")
|
||||
nw.nodes[2000] = vnode
|
||||
vnode.global_position = Vector3(0, 0, 1.0)
|
||||
|
||||
@@ -623,6 +637,7 @@ func _run() -> void:
|
||||
np._combo_tables = {}
|
||||
view.anim.md = {}
|
||||
fc.fly_targets.clear()
|
||||
fc.primary_fly_targets.clear()
|
||||
fc.attacks.clear()
|
||||
var got_shot := []
|
||||
np.bow_shot_fired.connect(func(sk: int): got_shot.append(sk))
|
||||
@@ -635,6 +650,8 @@ func _run() -> void:
|
||||
np._emit_swing(np.NAME_NORMAL_ATTACK, false)
|
||||
_ck(fc.fly_targets.size() == 1 and fc.fly_targets[0][0] == 2000,
|
||||
"弓挥击起手 → OnSetFlyTarget 发 CG_FLY_TARGETING(vid 2000)")
|
||||
_ck(fc.primary_fly_targets.size() == 1 and fc.primary_fly_targets[0][0] == 2000,
|
||||
"弓挥击优先调用独立 fly_targeting API(不误用 add_fly_targeting)")
|
||||
var exp_xy := MapCoord.to_server_cm(Vector3(3.0, 0.0, 4.0))
|
||||
_ck(fc.fly_targets[0][1] == int(exp_xy.x) and fc.fly_targets[0][2] == int(exp_xy.y),
|
||||
"CG_FLY_TARGETING 坐标 = 目标节点 server-XY(OnGetFlyTargetPosition 平面分量)")
|
||||
@@ -643,6 +660,7 @@ func _run() -> void:
|
||||
np._target_vid = 0
|
||||
np._vid_reserved = 0
|
||||
fc.fly_targets.clear()
|
||||
fc.primary_fly_targets.clear()
|
||||
np._send_fly_target()
|
||||
_ck(fc.fly_targets.is_empty(), "无目标 → 不发 CG_FLY_TARGETING")
|
||||
np.net_world = null
|
||||
@@ -668,10 +686,17 @@ func _run() -> void:
|
||||
# 点「在此复活」
|
||||
var hb: Button = null
|
||||
for n in death._root.find_children("*", "Button", true, false):
|
||||
if n.text == "在此复活":
|
||||
# 40250 identifies the control as restart_here_button; localized text is
|
||||
# data and must not be used as the behavioral selector.
|
||||
if n.name == "RestartHereButton":
|
||||
hb = n
|
||||
_ck(hb != null, "restart-here button present")
|
||||
if hb:
|
||||
# The reference dialog blocks restart_here for 10 seconds. This check
|
||||
# targets the command emission after that cooldown, as in the dedicated
|
||||
# restart parity test below.
|
||||
death._here_cooldown_remaining = 0.0
|
||||
hb.disabled = false
|
||||
hb.pressed.emit()
|
||||
_ck(fc.said.size() == 1 and fc.said[0] == [0, "/restart_here"], "restart -> say(0, '/restart_here')")
|
||||
# 复活:hp 回来 -> 自动关
|
||||
|
||||
Reference in New Issue
Block a user