fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# weapon_trace_test —— 40250 WeaponTrace 的轨迹/生命周期/接入回归。
|
||||
# godot --headless --path project --script weapon_trace_test.gd
|
||||
extends SceneTree
|
||||
|
||||
const WeaponTrace = preload("res://ui/weapon_trace.gd")
|
||||
const PlayerView = preload("res://ui/player_view.gd")
|
||||
|
||||
var _fail := 0
|
||||
|
||||
func _ck(ok: bool, message: String) -> void:
|
||||
if not ok:
|
||||
_fail += 1
|
||||
printerr("FAIL: " + message)
|
||||
|
||||
func _init() -> void:
|
||||
await _run()
|
||||
if _fail == 0:
|
||||
print("PASS: weapon_trace_test")
|
||||
quit(0)
|
||||
else:
|
||||
printerr("%d check(s) failed" % _fail)
|
||||
quit(1)
|
||||
|
||||
func _run() -> void:
|
||||
await _test_trace_lifecycle()
|
||||
await _test_player_view_integration()
|
||||
|
||||
func _test_trace_lifecycle() -> void:
|
||||
var host := Node3D.new()
|
||||
get_root().add_child(host)
|
||||
var weapon := MeshInstance3D.new()
|
||||
var box := BoxMesh.new()
|
||||
box.size = Vector3(0.12, 0.12, 1.6)
|
||||
weapon.mesh = box
|
||||
host.add_child(weapon)
|
||||
var trace: Node = WeaponTrace.new()
|
||||
host.add_child(trace)
|
||||
await process_frame
|
||||
_ck(trace.attach_to_weapon(weapon), "box weapon resolves a trace tip")
|
||||
trace.turn_on()
|
||||
for i in 6:
|
||||
weapon.position = Vector3(float(i) * 0.05, 0.0, 0.0)
|
||||
trace._process(0.016)
|
||||
var active: Dictionary = trace.debug_state()
|
||||
_ck(active.get("playing", false), "TurnOn keeps trace active")
|
||||
_ck(int(active.get("short_points", 0)) >= 2 and int(active.get("long_points", 0)) >= 2,
|
||||
"Update samples both short and long world tracks")
|
||||
_ck(int(active.get("vertex_count", 0)) >= 4, "BuildVertex produces a triangle-strip vertex band")
|
||||
trace.turn_off()
|
||||
var before_fade: Dictionary = trace.debug_state()
|
||||
trace._process(0.05)
|
||||
var during_fade: Dictionary = trace.debug_state()
|
||||
_ck(not during_fade.get("playing", true), "TurnOff stops sampling")
|
||||
_ck(int(during_fade.get("long_points", 0)) > 0 and
|
||||
int(during_fade.get("long_points", 0)) <= int(before_fade.get("long_points", 0)),
|
||||
"TurnOff preserves the tail for lifetime fade")
|
||||
trace._process(0.20)
|
||||
var expired: Dictionary = trace.debug_state()
|
||||
_ck(int(expired.get("short_points", 0)) == 0 and int(expired.get("long_points", 0)) == 0,
|
||||
"expired trace samples are removed")
|
||||
trace.attach_to_weapon(weapon)
|
||||
var replaced: Dictionary = trace.debug_state()
|
||||
_ck(int(replaced.get("short_points", 0)) == 0 and int(replaced.get("long_points", 0)) == 0,
|
||||
"re-attaching a weapon clears the old actor trace")
|
||||
trace.free()
|
||||
host.free()
|
||||
await process_frame
|
||||
|
||||
func _test_player_view_integration() -> void:
|
||||
if not ClassDB.class_exists("Metin2Model") or not ClassDB.class_exists("Metin2AnimPlayer"):
|
||||
print(" (skip PlayerView integration: animation extension unavailable)")
|
||||
return
|
||||
var assets := AssetRoot.path()
|
||||
var weapon_path := assets.path_join("item/ymir work/item/weapon/00010.gr2")
|
||||
if not FileAccess.file_exists(weapon_path):
|
||||
print(" (skip PlayerView integration: weapon asset unavailable)")
|
||||
return
|
||||
var pv: Node = PlayerView.new()
|
||||
get_root().add_child(pv)
|
||||
_ck(pv.build(assets, 0), "PlayerView builds for WeaponTrace integration")
|
||||
pv.set("weapon_gr2", weapon_path)
|
||||
for i in 4:
|
||||
await process_frame
|
||||
var attached: Dictionary = pv.weapon_trace_state()
|
||||
_ck(attached.get("present", false), "ordinary sword creates PlayerView WeaponTrace")
|
||||
pv.play_attack_motion(2, 14, 1.0)
|
||||
var attacking: Dictionary = pv.weapon_trace_state()
|
||||
_ck(attacking.get("playing", false), "attack motion turns WeaponTrace on")
|
||||
# playback_finished is the 40250 motion-queue boundary; invoke the same
|
||||
# callback deterministically instead of trying to interrupt an active once
|
||||
# motion with a loop request (PlayerView correctly defers that request).
|
||||
pv._on_playback_finished()
|
||||
var hidden: Dictionary = pv.weapon_trace_state()
|
||||
_ck(not hidden.get("playing", true), "new non-attack motion turns WeaponTrace off")
|
||||
pv.set_weapon_trace_enabled(false)
|
||||
await process_frame
|
||||
_ck(not pv.weapon_trace_state().get("present", true), "weapon type gate can destroy the trace")
|
||||
pv.free()
|
||||
if ClassDB.class_exists("Metin2Model"):
|
||||
Metin2Model.clear_texture_cache()
|
||||
if ClassDB.class_exists("Metin2AnimPlayer"):
|
||||
Metin2AnimPlayer.clear_clip_cache()
|
||||
await process_frame
|
||||
Reference in New Issue
Block a user