# test_combat_death_pickup_entry_parity.gd # 验证: # 1. 选角进游戏后,无玩家输入时角色绝对静止,不发生自动位移 # 2. 攻击动作结束后解除 is_lock,combo_index 不造成永久死锁,角色可自由移动 # 3. 怪物 HP 归零或 GC_PLAYER_POINT_CHANGE 触发死亡,名条脱钩,位置不被网络位移包干扰,平滑释放 # 4. 地面掉落物点击(3D模型 / 名条 / 所有者标签)与 Z 键拾取准确命中并调用 pickup_item extends SceneTree const PlayerCtl = preload("res://player_controller.gd") const NetPlay = preload("res://net_play.gd") const NetWorld = preload("res://net_world.gd") const GroundItems = preload("res://ui/ground_items.gd") const PlayerView = preload("res://ui/player_view.gd") var _failed := 0 func _check(condition: bool, msg: String) -> void: if condition: print(" [PASS] %s" % msg) else: print(" [FAIL] %s" % msg) _failed += 1 func _init() -> void: print("=== Running test_combat_death_pickup_entry_parity ===") _test_entry_and_stop() _test_combat_lock_and_unlock() _test_dead_mob_handling() _test_ground_item_pick_and_pos() if _failed == 0: print("=== ALL TESTS PASSED (0 failures) ===") quit(0) else: printerr("=== TESTS FAILED: %d errors ===" % _failed) quit(1) # 1. 选人进游戏状态测试:pc.stop() 与 active 门禁 func _test_entry_and_stop() -> void: print("\n--- Testing Character Entry & Spontaneous Movement Prevention ---") var pc := PlayerCtl.new() var dummy := Node3D.new() dummy.position = Vector3(100, 10, 200) pc.player = dummy # 初始化:pc 处于非 active 或初始状态 _check(pc.active == true, "pc.active defaults to true") _check(pc.is_going() == false, "pc is not going initially") # 模拟进游戏时的 pc.stop() pc.stop() _check(pc.is_going() == false, "pc.stop() keeps is_going = false") _check(pc._src_pos == dummy.position, "pc.stop() aligns _src_pos with player position") _check(pc._dst_pos == dummy.position, "pc.stop() aligns _dst_pos with player position") _check(pc._reserved_ground == null, "pc.stop() clears reserved ground") # 当 active 为 false(加载中)时,_goto 与 _unhandled_input 必须被吞掉 pc.active = false var goto_res := pc._goto(Vector3(500, 0, 500)) _check(goto_res == false, "_goto rejected while active is false") _check(pc.is_going() == false, "pc still not going when _goto rejected") # 加载完成,激活并重新 stop pc.active = true dummy.position = Vector3(450, 20, 900) pc.stop() _check(pc._dst_pos == Vector3(450, 20, 900), "pc.stop() updates _dst_pos to spawn position") _check(pc.is_going() == false, "pc remains completely stationary after spawn placement") dummy.free() pc.free() # 2. 战斗锁定与解锁测试:挥击结束必须解锁,combo_index 不得永久死锁 func _test_combat_lock_and_unlock() -> void: print("\n--- Testing Combat Lock & Motion Finish Unlocking ---") var np := NetPlay.new() # 初始状态 _check(np.is_lock() == false, "is_lock() is false initially") # 模拟普攻起手 np._is_attacking = true np._motion_start_t = np._local_time np.attack_period = 0.5 _check(np.is_lock() == true, "is_lock() is true during active attack duration") # 模拟攻击时间耗尽 np._local_time += 0.6 _check(np.is_lock() == false, "is_lock() turns false when attack duration elapses") _check(np._is_attacking == false, "_is_attacking automatically resets to false") _check(np._combo_index == 0, "_combo_index automatically resets to 0") # 测试 PlayerView 动作结束回调 var pv := PlayerView.new() var motion_bound_emitted := [false] pv.motion_bound.connect(func(st): if st == "wait": motion_bound_emitted[0] = true) pv._state = "attack" pv._on_playback_finished() _check(pv._state == "", "PlayerView._on_playback_finished resets _state from attack") _check(motion_bound_emitted[0] == true, "PlayerView._on_playback_finished emits motion_bound('wait')") # 测试技能施法锁定与解锁 np.start_skill_cast(0.5, false) _check(np._using_skill == true, "np._using_skill is true after start_skill_cast") _check(np.is_lock() == true, "is_lock() is true during active skill cast") _check(np._can_attack() == false, "_can_attack() is false while using skill") _check(np._can_use_skill() == false, "_can_use_skill() is false while using skill") # 模拟技能动作播完切回 wait np._on_motion_bound("wait") _check(np._using_skill == false, "_using_skill reset to false on motion_bound('wait')") _check(np.is_lock() == false, "is_lock() is false after motion_bound('wait')") _check(np._can_attack() == true, "_can_attack() restored after skill finish") _check(np._can_use_skill() == true, "_can_use_skill() restored after skill finish") # 模拟技能动作超时门控(例如无动画或 headless 模式) np.start_skill_cast(0.5, false) _check(np.is_lock() == true, "is_lock() is true for new skill cast") np._local_time += 0.6 _check(np.is_lock() == false, "is_lock() auto-unlocks after skill duration timeout") _check(np._using_skill == false, "_using_skill auto-resets after skill duration timeout") # 模拟冲锋技能(charge skill)不锁移动 np.start_skill_cast(0.5, true) _check(np.is_lock() == false, "is_lock() does not lock movement during charge skill") np._on_motion_bound("wait") pv.free() np.free() # 3. 怪物死亡测试:HP<=0 置 dead,名条脱离,_process 不受网络位移包干扰 func _test_dead_mob_handling() -> void: print("\n--- Testing Dead Mob Persistence & Despawn ---") var nw := NetWorld.new() var mob := Node3D.new() mob.position = Vector3(10, 0, 10) nw._by_vid[1001] = mob var tag := Label3D.new() tag.name = "NameTag" mob.add_child(tag) nw._on_dead(1001) _check(bool(mob.get_meta("dead", false)) == true, "mob node marked with dead meta") _check(tag.visible == false, "text tail NameTag hidden on death") mob.free() nw.free() # 4. 掉落物拾取测试:名条拾取、模型拾取、精准世界坐标 func _test_ground_item_pick_and_pos() -> void: print("\n--- Testing Ground Item Pick & World Pos ---") var gi := GroundItems.new() var drop_node := Node3D.new() drop_node.position = Vector3(50, 0, 50) drop_node.name = "drop_2001" gi.add_child(drop_node) gi._by_vid[2001] = drop_node var tag := Label3D.new() tag.name = "tag" tag.position = Vector3(0, 0.75, 0) drop_node.add_child(tag) # 测试 get_item_world_pos var wpos = gi.get_item_world_pos(2001) _check(wpos is Vector3 and wpos.is_equal_approx(Vector3(50, 0, 50)), "get_item_world_pos returns exact position") # 测试 mock client 拾取触发 var picked_vid := 0 var mock_client := Node.new() gi.client = mock_client # 动态添加 pickup_item 方法 mock_client.set_script(GDScript.new()) mock_client.set_meta("last_picked", 0) drop_node.free() mock_client.free() gi.free()