- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
114 lines
4.0 KiB
GDScript
114 lines
4.0 KiB
GDScript
# skill_motion_loop_test —— 40250 SetMotionLoopCount / MSA LoopData parity。
|
|
# godot --headless --path project --script skill_motion_loop_test.gd
|
|
extends SceneTree
|
|
|
|
const AssetRoot = preload("res://asset_root.gd")
|
|
const PlayerView = preload("res://ui/player_view.gd")
|
|
|
|
class FakeAnim extends Node:
|
|
signal playback_finished
|
|
var anim_path := ""
|
|
var loop := true
|
|
var time_scale := 1.0
|
|
var playing := true
|
|
var motion_loop_count := -1
|
|
func set_motion_loop_count(count: int) -> void:
|
|
motion_loop_count = count
|
|
func get_motion_data() -> Dictionary:
|
|
return {}
|
|
|
|
var _fail := 0
|
|
|
|
func _ck(ok: bool, message: String) -> void:
|
|
if not ok:
|
|
_fail += 1
|
|
printerr("FAIL: " + message)
|
|
|
|
func _touch(path: String) -> void:
|
|
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
|
|
var f := FileAccess.open(path, FileAccess.WRITE)
|
|
f.store_string("")
|
|
f.close()
|
|
|
|
func _init() -> void:
|
|
call_deferred("_run")
|
|
|
|
func _run() -> void:
|
|
await _test_player_view_payload()
|
|
await _test_native_msa_segment_loop()
|
|
if _fail == 0:
|
|
print("PASS: skill_motion_loop_test")
|
|
quit(0)
|
|
else:
|
|
printerr("%d check(s) failed" % _fail)
|
|
quit(1)
|
|
|
|
func _test_player_view_payload() -> void:
|
|
var root := ProjectSettings.globalize_path("user://skill_motion_loop_test")
|
|
var skill_dir := root.path_join("PC/ymir work/pc/warrior/skill")
|
|
_touch(skill_dir.path_join("loop_skill.msa"))
|
|
var pv: Node = PlayerView.new()
|
|
get_root().add_child(pv)
|
|
var anim := FakeAnim.new()
|
|
pv.add_child(anim)
|
|
pv.anim = anim
|
|
pv._assets_root = root
|
|
pv._race = 0
|
|
pv.motion_dir = root.path_join("PC/ymir work/pc/warrior/general")
|
|
var played: bool = pv.play_skill_motion("loop_skill", 0, 3, true)
|
|
_ck(played, "PlayerView accepts a skill motion from the resource directory")
|
|
_ck(anim.motion_loop_count == 3, "PlayerView forwards uMotLoopCount to the animation player")
|
|
_ck(pv.skill_motion_state() == {"loop_count": 3, "moving": true},
|
|
"PlayerView keeps loop count and moving-skill bit together")
|
|
pv.free()
|
|
await process_frame
|
|
|
|
func _test_native_msa_segment_loop() -> void:
|
|
if not ClassDB.class_exists("Metin2AnimPlayer"):
|
|
print(" (skip native MSA loop test: animation extension unavailable)")
|
|
return
|
|
var assets := AssetRoot.path()
|
|
var msa := assets.path_join("PC/ymir work/pc/assassin/skill/yeonsa.msa")
|
|
if not FileAccess.file_exists(msa):
|
|
print(" (skip native MSA loop test: yeonsa.msa unavailable)")
|
|
return
|
|
var anim: Node = ClassDB.instantiate("Metin2AnimPlayer")
|
|
get_root().add_child(anim)
|
|
if not anim.has_method("set_motion_loop_count") or not anim.has_method("get_motion_loop_count"):
|
|
_ck(false, "Metin2AnimPlayer exposes SetMotionLoopCount")
|
|
anim.free()
|
|
await process_frame
|
|
return
|
|
anim.set("loop", false)
|
|
anim.set("anim_path", msa)
|
|
await process_frame
|
|
var loop_data: Dictionary = anim.call("get_loop_data")
|
|
_ck(bool(loop_data.get("present", false)), "MSA LoopData is exposed")
|
|
_ck(int(loop_data.get("count", 0)) == 7, "yeonsa resource loop count is 7")
|
|
anim.call("set_motion_loop_count", 2)
|
|
_ck(int(anim.call("get_motion_loop_count")) == 2,
|
|
"SetMotionLoopCount overrides the resource count")
|
|
var previous: float = float(anim.call("get_time"))
|
|
var wraps := 0
|
|
var count_after_wrap := -1
|
|
var finished := [false]
|
|
anim.playback_finished.connect(func(): finished[0] = true)
|
|
var frames := 0
|
|
while frames < 500 and not finished[0]:
|
|
await process_frame
|
|
var current: float = float(anim.call("get_time"))
|
|
if current + 0.05 < previous:
|
|
wraps += 1
|
|
count_after_wrap = int(anim.call("get_motion_loop_count"))
|
|
previous = current
|
|
frames += 1
|
|
_ck(wraps == 1, "loop count 2 performs one LoopData segment rewind")
|
|
_ck(count_after_wrap == 1, "segment rewind decrements the remaining count")
|
|
_ck(finished[0], "finite MSA loop returns to playback_finished after the tail")
|
|
anim.set("anim_path", assets.path_join("PC/ymir work/pc/warrior/general/wait.msa"))
|
|
await process_frame
|
|
_ck(int(anim.call("get_motion_loop_count")) == 0,
|
|
"normal motion does not inherit the previous skill loop override")
|
|
anim.free()
|
|
await process_frame
|