# test_levelup_combat_parity.gd —— 40250 升级流与战斗打击感 1:1 纯净无头自检套件 # 验证: # 1. POINT_LEVEL 跃迁升级检测 # 2. EFFECT_LEVELUP 冲天金光粒子挂载 (level_up_beam) # 3. levelup1_1.wav 升级号角音效播放 # 4. 升级瞬间 100% HP 与 SP 回满规则 # 5. HUD 等级与加点通知 # 6. 暴击 DAMAGE_CRITICAL 镜头瞬时微震 (camera.shake) # 7. 暴击大号金色高亮飘字与普通飘字样式分流 extends SceneTree const NetPlay = preload("res://net_play.gd") const NetWorld = preload("res://net_world.gd") const DamageEffect = preload("res://damage_effect.gd") class FakeAudio extends Node: var calls: Array[String] = [] func play_ui(name: String) -> void: calls.append(name) class FakeCamera extends Camera3D: var shake_calls: Array = [] func shake(strength: float, decay := 8.0) -> void: shake_calls.append([strength, decay]) class FakePlayerController extends Node: signal target_selected(target: Node) signal ground_item_clicked(vid: int) signal hover_entity_changed(vid: int) signal moved(pos: Vector3) signal anim_state(state: String) var player: Node3D class FakeHud extends Node: var vitals: Array = [] var levels: Array[int] = [] var levelup_events: Array[int] = [] func set_vitals(hp: int, max_hp: int, sp: int, max_sp: int) -> void: vitals.append([hp, max_hp, sp, max_sp]) func set_stamina(_st: int, _max_st: int) -> void: pass func set_exp(_exp: int, _next_exp: int) -> void: pass func set_level(lvl: int) -> void: levels.append(lvl) func set_energy(_e: int, _max_e: int) -> void: pass func on_level_up(lvl: int) -> void: levelup_events.append(lvl) class FakeClient extends Node: signal entity_spawned(e: Dictionary) signal entity_despawned(vid: int) signal entity_main_set(vid: int) signal vitals_changed(vid: int) signal entity_dead(vid: int) signal damage(vid: int, amount: int, flag: int) signal points_changed(p: Dictionary) signal chat(type: int, vid: int, msg: String) signal entity_moved(vid: int) signal target_info(info: Dictionary) func is_in_game() -> bool: return true func get_main_vid() -> int: return 1001 func get_entity(_vid: int) -> Dictionary: return {} var _fail := 0 func _ck(c: bool, m: String) -> void: if not c: _fail += 1 printerr("FAIL: " + m) var _to_free: Array[Node] = [] func _init() -> void: _run() for n in _to_free: if is_instance_valid(n): n.queue_free() if _fail == 0: print("PASS: test_levelup_combat_parity (40250 LevelUp & Combat Parity)") quit(0) else: printerr("%d check(s) failed in test_levelup_combat_parity" % _fail) quit(1) func _run() -> void: var fc := FakeClient.new() get_root().add_child(fc) var fa := FakeAudio.new() get_root().add_child(fa) var fcam := FakeCamera.new() get_root().add_child(fcam) var hud := FakeHud.new() get_root().add_child(hud) var player := Node3D.new() player.name = "Player" get_root().add_child(player) var pc := FakePlayerController.new() pc.player = player get_root().add_child(pc) var mount := Node3D.new() get_root().add_child(mount) var nw: Node = NetWorld.new() get_root().add_child(nw) nw.setup(fc, mount) nw.set_camera(fcam) var np: Node = NetPlay.new() get_root().add_child(np) np.setup(fc, pc, nw, hud, fa) np.camera = fcam _to_free = [np, nw, mount, pc, player, hud, fcam, fa, fc] var levelup_signals: Array[int] = [] np.level_up.connect(func(lvl: int): levelup_signals.append(lvl)) # --- 1. 初始点数同步 (不触发升级) --- np._on_points({ "level": 1, "hp": 200, "max_hp": 300, "sp": 50, "max_sp": 100, "exp": 0, "next_exp": 1000, }) _ck(levelup_signals.is_empty(), "1. 初始 Lv.1 同步不触发升级信号") _ck(fa.calls.is_empty(), "2. 初始不播放升级音效") _ck(player.get_node_or_null("level_up_beam") == null, "3. 初始无升级光柱") _ck(hud.levels == [1], "4. HUD 记录初始等级") hud.vitals.clear() # --- 2. 角色等级跃迁 (Lv.1 -> Lv.2) --- # 在残血状态下升级:当前 HP 80 / Max HP 350 np._on_points({ "level": 2, "hp": 80, "max_hp": 350, "sp": 20, "max_sp": 120, "exp": 120, "next_exp": 2500, }) _ck(levelup_signals == [2], "5. 等级增长触发 level_up 信号 (Lv.2)") _ck(fa.calls.has("levelup1_1.wav"), "6. 播放 40250 专属升级音效 levelup1_1.wav") _ck(player.get_node_or_null("level_up_beam") != null, "7. 角色脚底挂载冲天金光特效 (level_up_beam)") _ck(hud.levelup_events == [2], "8. 通知 HUD 刷新升级事件") # 40250 核心规则:升级瞬间 HP / SP 立即 100% 满状态恢复 # 检查 hud.set_vitals 是否收到了满血满蓝调用 [350, 350, 120, 120] var got_full_restore := false for vit in hud.vitals: if vit[0] == 350 and vit[1] == 350 and vit[2] == 120 and vit[3] == 120: got_full_restore = true break _ck(got_full_restore, "9. 40250 规则:升级瞬间 HP/SP 立即全满回血回蓝") # --- 3. 战斗暴击反馈 (DAMAGE_CRITICAL) --- # 普通受击:flag = DAMAGE_NORMAL fcam.shake_calls.clear() nw._on_damage(2001, 150, DamageEffect.DAMAGE_NORMAL) _ck(fcam.shake_calls.is_empty(), "10. 普通伤害不震屏") # 暴击命中:flag = DAMAGE_NORMAL | DAMAGE_CRITICAL nw._on_damage(2001, 450, DamageEffect.DAMAGE_NORMAL | DamageEffect.DAMAGE_CRITICAL) _ck(not fcam.shake_calls.is_empty(), "11. 暴击 DAMAGE_CRITICAL 触发镜头震颤") _ck(fcam.shake_calls[0][0] == 0.05, "12. 震颤振幅精确匹配 0.05m") # 暴击飘字样式验证 nw._show_damage = true nw._process_damage_queue() var dmg_node: Node3D = mount.get_node_or_null("dmg_2001") # 如果没有 DDS 纹理降级至 Label3D if dmg_node: var lbl := dmg_node.get_node_or_null("Label3D") as Label3D if lbl: _ck(lbl.modulate == Color(1.0, 0.85, 0.2), "13. 暴击飘字为金色") _ck(lbl.font_size == 46, "14. 暴击字体放大至 46px")