extends SceneTree ## 炸弹系统回归测试(时间驱动): ## 初始 2 枚 → 引爆清敌清弹-1 → 用尽 → 空仓无效 → Boss 击破 +1 ## → B 道具拾取 +1 → 上限 5 ## 输入映射层:fire 含 J、bomb 含 K ## 运行:godot --headless -s tests/bomb_test.gd const GLOBAL_TIMEOUT := 25.0 var _phase := 0 var _phase_time := 0.0 var _time := 0.0 var _game: Game var _fails: Array[String] = [] func _initialize() -> void: _game = (load("res://scenes/game.tscn") as PackedScene).instantiate() root.add_child(_game) print("[bomb_test] game instantiated") func _fail(msg: String) -> void: _fails.append(msg) print("[FAIL] " + msg) func _next_phase() -> void: _phase += 1 _phase_time = 0.0 func _finish() -> bool: if _fails.is_empty(): print("[bomb_test] PASS") else: print("[bomb_test] FAIL: %d issue(s)" % _fails.size()) quit(0 if _fails.is_empty() else 1) return true ## 输入映射检查(真实按键输入无法无头模拟,检查映射配置本身) func _check_input_maps() -> void: var fire_ok := false if InputMap.has_action("fire"): for event in InputMap.action_get_events("fire"): if event is InputEventKey and (event as InputEventKey).physical_keycode == 74: fire_ok = true if not fire_ok: _fail("fire action missing J key") var bomb_ok := false if InputMap.has_action("bomb"): for event in InputMap.action_get_events("bomb"): if event is InputEventKey and (event as InputEventKey).physical_keycode == 75: bomb_ok = true if not bomb_ok: _fail("bomb action missing K key") func _process(delta: float) -> bool: _time += delta if _time > GLOBAL_TIMEOUT: _fail("global timeout in phase %d" % _phase) return _finish() if _game._enemies == null: return false _phase_time += delta match _phase: 0: _check_input_maps() if _game.player.bombs != 2: _fail("initial bombs=%d expected 2" % _game.player.bombs) # 场上放 3 架敌机 + 2 发敌弹 for i in 3: var enemy := EnemySmall.new() enemy.setup(150.0 + 120.0 * i, 300.0 + 60.0 * i, 100.0) _game._enemies.add_child(enemy) for i in 2: var bullet := EnemyBullet.new() bullet.position = Vector2(120.0 + 200.0 * i, 400.0) _game._enemy_bullets.add_child(bullet) _game.player.use_bomb() _next_phase() 1: # 首波 spawner 敌机 0.8s 后才出现,检查点在 0.5s 安全 if _phase_time >= 0.5: var enemies_left := _game._enemies.get_child_count() var bullets_left := _game._enemy_bullets.get_child_count() if enemies_left != 0: _fail("bomb left enemies: %d" % enemies_left) if bullets_left != 0: _fail("bomb left enemy bullets: %d" % bullets_left) if _game.player.bombs != 1: _fail("bombs=%d expected 1" % _game.player.bombs) else: print("[bomb_test] detonate ok: enemies/bullets cleared, bombs 2->1, score=%d" % _game.score) _next_phase() 2: _game.player.use_bomb() _next_phase() 3: if _phase_time >= 0.3: if _game.player.bombs != 0: _fail("second bomb not consumed: %d" % _game.player.bombs) _game.player.use_bomb() # 空仓 _next_phase() 4: if _phase_time >= 0.3: if _game.player.bombs != 0: _fail("empty stock still fired: %d" % _game.player.bombs) else: print("[bomb_test] stock drain + empty guard ok") var boss := _game.spawn_boss(1.0) boss.apply_damage(99999) _next_phase() 5: if _phase_time >= 1.0: if _game.player.bombs != 1: _fail("boss kill did not grant bomb: %d" % _game.player.bombs) else: print("[bomb_test] boss kill +1 bomb ok") # B 道具拾取链路:1 → 2,再补到 5 验证上限 _collect_bomb_pickup() _next_phase() 6: if _phase_time >= 0.5: if _game.player.bombs != 2: _fail("B pickup did not add bomb: %d" % _game.player.bombs) else: print("[bomb_test] B pickup +1 ok") _collect_bomb_pickup() _collect_bomb_pickup() _collect_bomb_pickup() _collect_bomb_pickup() _next_phase() 7: if _phase_time >= 0.5: if _game.player.bombs != 5: _fail("bomb cap broken: %d expected 5" % _game.player.bombs) else: print("[bomb_test] bomb cap at 5 ok") return _finish() return false func _collect_bomb_pickup() -> void: var powerups := _game.get_node_or_null("Powerups") as Node2D var pickup := Powerup.new() pickup.kind = Powerup.Kind.BOMB pickup.position = _game.player.global_position powerups.add_child(pickup) # 直接触发玩家拾取路径(与 area_entered 信号同一入口) _game.player._collect_powerup(pickup)