extends GameMode # Spectate: bot vs bot exhibition — both ships are AI-driven so two trained # policies can be watched playing each other. Score tracking and kickoff # resets like Match, but no timer. R resets the ball, Esc returns to the menu. signal score_changed(score: Dictionary) @export_group("Team 0 bot") @export_file("*.json") var bot_a_model_path: String = "" @export_range(1, 60) var bot_a_reaction_ticks: int = 8 @export_range(0.0, 1.0) var bot_a_action_noise: float = 0.0 @export_group("Team 1 bot") @export_file("*.json") var bot_b_model_path: String = "" @export_range(1, 60) var bot_b_reaction_ticks: int = 8 @export_range(0.0, 1.0) var bot_b_action_noise: float = 0.0 var score := {0: 0, 1: 0} func _start() -> void: spawn_ball() var ship_a := spawn_ship(0, 0, _make_bot(bot_a_model_path, bot_a_reaction_ticks, bot_a_action_noise)) spawn_ship(1, 0, _make_bot(bot_b_model_path, bot_b_reaction_ticks, bot_b_action_noise)) spawn_camera_rig(ship_a) func _make_bot(model_path: String, reaction_ticks: int, action_noise: float) -> ShipController: if not model_path.is_empty() and FileAccess.file_exists(model_path): var bot := AIShipController.new() bot.model_path = model_path bot.reaction_ticks = reaction_ticks bot.action_noise = action_noise return bot if not model_path.is_empty(): push_warning("SpectateMode: bot model not found at %s, spawning inert ship" % model_path) return ShipController.new() # inert placeholder func _on_goal_scored(conceding_team: int) -> void: var scoring_team := 1 - conceding_team score[scoring_team] += 1 score_changed.emit(score.duplicate()) print("Goal for team %d! Score: %d - %d" % [scoring_team, score[0], score[1]]) reset_ball() reset_ships() func _unhandled_input(event): if event.is_action_pressed("reset_ball"): reset_ball() else: super(event)