extends GameMode # Timed match: two teams, score tracking, kickoff resets after each goal. # The opponent is a trained AI bot when a policy model is configured # (see TRAINING.md for training and promoting models into res://bots/), # otherwise an inert placeholder ship. signal timer_updated(minutes: int, seconds: int) signal score_changed(score: Dictionary) @export var match_length_seconds := 150.0 @export_group("AI opponent") # Trained policy for the opponent; empty = inert placeholder ship. @export_file("*.json") var bot_model_path: String = "" # Difficulty handicaps, applied on top of the model (see AIShipController). @export_range(1, 60) var bot_reaction_ticks: int = 8 @export_range(0.0, 1.0) var bot_action_noise: float = 0.0 var score := {0: 0, 1: 0} var match_timer: Timer func _start() -> void: spawn_ball() var player_ship := spawn_ship(0, 0, PlayerShipController.new()) spawn_camera_rig(player_ship) spawn_ship(1, 0, _make_opponent_controller()) match_timer = Timer.new() match_timer.one_shot = true match_timer.wait_time = match_length_seconds match_timer.timeout.connect(_on_match_timer_timeout) add_child(match_timer) match_timer.start() func _make_opponent_controller() -> ShipController: if not bot_model_path.is_empty() and FileAccess.file_exists(bot_model_path): var bot := AIShipController.new() bot.model_path = bot_model_path bot.reaction_ticks = bot_reaction_ticks bot.action_noise = bot_action_noise return bot if not bot_model_path.is_empty(): push_warning("MatchMode: bot model not found at %s, spawning inert opponent" % bot_model_path) return ShipController.new() # inert placeholder func _process(_delta): if match_timer and match_timer.time_left > 0: var remaining := ceili(match_timer.time_left) timer_updated.emit(floori(remaining / 60.0), remaining % 60) 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 _on_match_timer_timeout() -> void: print("Full time! Final score: %d - %d" % [score[0], score[1]]) get_tree().change_scene_to_file(MAIN_MENU_SCENE_PATH)