# test_exp_fly_parity.gd # 检验 40250 怪物阵亡经验光球飞行、自导向追踪与吸收动效 extends SceneTree const FlyObject = preload("res://fly_object.gd") const NetPlay = preload("res://net_play.gd") const NetWorld = preload("res://net_world.gd") const PlayerCtl = preload("res://player_controller.gd") func _init() -> void: print("--- 开始测试 40250 怪物阵亡经验光球飞行与吸收动效 (test_exp_fly_parity) ---") var fails: int = 0 # 1. 验证 40250 FLY 常量与编号映射 if FlyObject.FLY_EXP != 0: printerr("FAIL: FLY_EXP 应为 0,实际为: ", FlyObject.FLY_EXP) fails += 1 if FlyObject.FLY_HP_SMALL != 1 or FlyObject.FLY_SP_SMALL != 4: printerr("FAIL: FLY_HP/SP 常量映射错误") fails += 1 # 2. 模拟 FlyObject 经验球追踪、命中与吸收信号 var mount := Node3D.new() get_root().add_child(mount) var mgr := FlyObject.new() mgr.manual_step = true mount.add_child(mgr) mgr.setup(mount) var player_target := Node3D.new() mount.add_child(player_target) player_target.position = Vector3(5, 1, 0) var exp_absorbed_count := [0] mgr.exp_absorbed.connect(func(_wp: Vector3): exp_absorbed_count[0] += 1 ) var d := FlyObject.FlyData.new() d.init_vel = 15.0 d.flat_range = 30.0 d.bomb_range = 0.8 d.is_homing = true d.homing_start_time = 0.05 d.homing_max_angle = 140.0 d.maintain_parallel = true var start_pos := Vector3(0, 1, 0) var inst = mgr.spawn(start_pos, player_target, false, d, FlyObject.FLY_EXP) if mgr.active_count() != 1: printerr("FAIL: 经验球未成功注册进飞行物管理器!") fails += 1 if inst.skill_index != FlyObject.FLY_EXP: printerr("FAIL: 实例技能索引不符: ", inst.skill_index) fails += 1 # 推进仿真时间步,让光球命中玩家目标 for i in range(40): mgr.step(0.02) if mgr.active_count() == 0: break if exp_absorbed_count[0] != 1: printerr("FAIL: 经验球到达玩家时未发出 exp_absorbed 信号,次数: ", exp_absorbed_count[0]) fails += 1 if mgr.active_count() != 0: printerr("FAIL: 经验球命中后未从活跃队列销毁!") fails += 1 # 3. 验证 net_world._on_fly(FLY_EXP) 生成 3 颗聚能光球 var nw := NetWorld.new() mount.add_child(nw) nw.parent = mount nw._fly_mgr = mgr # 模拟注册实体坐标 var fake_client := _FakeClient.new() fake_client.ents[100] = {"vid": 100, "pos": Vector3(0, 0, 0)} # 怪物 fake_client.ents[1] = {"vid": 1, "pos": Vector3(4, 0, 0)} # 玩家 nw.client = fake_client var mob_node := Node3D.new() mob_node.position = Vector3(0, 0, 0) mount.add_child(mob_node) nw._by_vid[100] = mob_node var player_node := Node3D.new() player_node.position = Vector3(4, 0, 0) mount.add_child(player_node) nw._by_vid[1] = player_node # 触发 40250 官方 _on_fly(0, 100, 1) nw._on_fly(FlyObject.FLY_EXP, 100, 1) if mgr.active_count() != 3: printerr("FAIL: _on_fly(FLY_EXP) 应发射 3 颗经验聚能光球,实际: ", mgr.active_count()) fails += 1 # 清空飞行物 for i in range(50): mgr.step(0.02) if mgr.active_count() == 0: break # 4. 验证 net_world.spawn_exp_fly 接口 nw.spawn_exp_fly(Vector3(2, 0, 2), player_node, 3) if mgr.active_count() != 3: printerr("FAIL: spawn_exp_fly 未成功生成 3 颗光球,实际: ", mgr.active_count()) fails += 1 for i in range(50): mgr.step(0.02) if mgr.active_count() == 0: break # 5. 验证 net_play 经验变动时触发经验获取流程 var np := NetPlay.new() mount.add_child(np) var pc := PlayerCtl.new() pc.player = player_node np.setup(fake_client, pc, nw, null) # 初始经验 np._on_points({"exp": 1000, "next_exp": 5000, "level": 10}) if mgr.active_count() != 0: printerr("FAIL: 初始点数同步不应触发获取经验!") fails += 1 # 杀死怪物获得 500 经验 np._last_dead_mob_pos = Vector3(3, 0, 0) np._on_points({"exp": 1500, "next_exp": 5000, "level": 10}) if mgr.active_count() != 3: printerr("FAIL: 经验增加时未能通过 net_world 生成经验光球!实际数量: ", mgr.active_count()) fails += 1 mount.queue_free() if fails == 0: print("PASS: test_exp_fly_parity (6/6 全部通过)") quit(0) else: printerr("FAILED: test_exp_fly_parity (", fails, " 项失败)") quit(1) class _FakeClient extends Node: signal entity_spawned(entity: Dictionary) signal entity_despawned(vid: int) signal entity_moved(vid: int) signal entity_main_set(vid: int) signal entity_dead(vid: int) signal vitals_changed(vid: int) signal points_changed(points: Dictionary) signal chat(type: int, vid: int, text: String) signal damage(vid: int, amount: int, flag: int) signal target_info(t: Dictionary) signal fly_cue(type: int, start_vid: int, end_vid: int) signal fly_targeting(s_vid: int, t_vid: int, pos: Vector2, append: bool) var ents := {} func is_in_game() -> bool: return true func get_main_vid() -> int: return 1 func get_entity(vid: int) -> Dictionary: return ents.get(vid, {}) func get_entities() -> Array: return ents.values()