extends Node2D const SURFACE_PROFILES = preload("res://surface/PlanetSurfaceProfiles.gd") const PLANET_DEFINITIONS = preload("res://planets/PlanetDefinitions.gd") const PLANET_SETUPS = preload("res://surface/PlanetSetups.gd") const SETUP_HOST_SCENE: PackedScene = preload("res://surface/PlanetSetupHost.tscn") # The crew's own log, the same voice the prologue uses for the things that # happen TO them rather than the things they are told (#212). const CONQUEST_SPEAKER: String = "KESTREL - LOG" # Bootstraps the surviving fleet, including each vessel type, on this scene's # own startup (from GameState, since this is a fresh scene tree - nothing here # carries over automatically) and handles the always-available exit prompt that # fades back out to Main.tscn, restoring the fleet at wherever it left # space from. @export var fade_duration: float = 0.4 @onready var fleet: Node2D = get_node("../Fleet") @onready var camera: Camera2D = get_node("../Camera2D") @onready var planet_name_label: Label = get_node("../UI/PlanetNameLabel") @onready var exit_button: IconButton = get_node("../UI/ExitButton") @onready var fade_rect: ColorRect = get_node("../FadeOverlay/FadeRect") @onready var background: ColorRect = get_node("../BackgroundLayer/Background") @onready var feature_host: Node2D = get_node("../SurfaceFeatures") var transitioning: bool = false func _ready() -> void: var planet_definition: Resource = PLANET_DEFINITIONS.get_definition(GameState.planet_definition_id) _compose_surface(SURFACE_PROFILES.get_profile(planet_definition.surface_profile_id)) # The Distorder's sustained travel boost is a space-navigation tool. A # landing always returns the fleet to normal surface speed; the separate # aimed dash remains available through its own ability button. GameState.set_fleet_speed_multiplier(1.0) fleet.spawn_ships(GameState.surviving_ship_count, Vector2.ZERO, GameState.surviving_vessel_types) camera.position = Vector2.ZERO camera.zoom = Vector2(GameState.return_camera_zoom, GameState.return_camera_zoom) planet_name_label.text = GameState.planet_name exit_button.pressed.connect(_on_exit_pressed) fade_rect.color.a = 1.0 var tween: Tween = create_tween() tween.tween_property(fade_rect, "color:a", 0.0, fade_duration) func _compose_surface(profile: Resource) -> void: # Shared with PlanetSetupEditor, deliberately: the backdrop is one # instanced scene configured through one method, so what an author sees # while placing markers is what a player sees after landing. See the # header of SurfaceBackground.gd. background.apply_profile(profile) for caption_path in ["../UI/BankCaption", "../UI/FleetCaption", "../UI/CaptureLabel"]: var caption: Label = get_node(caption_path) caption.add_theme_color_override("font_color", profile.caption_color) caption.add_theme_color_override("font_outline_color", profile.caption_outline_color) var capture_label: Label = get_node("../UI/CaptureLabel") # This controller readies before the UI sibling. Store the policy on the # label itself so its first process tick cannot make a scripted encounter's # deliberately hidden generic objective readout visible again. capture_label.display_enabled = bool(profile.show_capture_status) capture_label.visible = bool(profile.show_capture_status) if profile.features_scene != null: var features: Node = profile.features_scene.instantiate() feature_host.add_child(features) var setup: Resource = PLANET_SETUPS.setup_for_planet(GameState.planet_id, profile.profile_id) if setup != null: # Untyped on purpose: a static PlanetSetupHost annotation here would # need the global class_name cache to already know about it, which # is only true after an editor/import pass has run (see #31/#35's # load-order notes) - a plain Node reference plus duck-typed # apply_setup() sidesteps that entirely, same as BuildingDefinitions # and VesselDefinitions already do for their own resource types. var setup_host: Node = SETUP_HOST_SCENE.instantiate() feature_host.add_child(setup_host) setup_host.apply_setup(setup) elif profile.encounter_scene == null: # No PlanetSetup exists for this profile at all (#55: "void" has # none authored) - register zero required points directly, rather # than leaving this planet's conquest state unregistered forever. # capture_state()'s fallback for an unregistered planet_id defaults # to conquered=false (see test_planet_conquest_gate.gd's own "a # fresh planet reported conquered" check), which would otherwise # permanently block extractor placement here - a garrison of zero is # vacuously already beaten. GameState.register_planet_garrison(GameState.planet_id, 0) # #212: conquest is otherwise announced only by a caption changing and a # HUD number the player has no reason to be watching at that moment. The # surface already carries a narration banner, so the moment gets the same # treatment every other turn of the story does. if not GameState.planet_conquered.is_connected(_on_planet_conquered): GameState.planet_conquered.connect(_on_planet_conquered) if profile.encounter_scene != null: var encounter: Node = profile.encounter_scene.instantiate() feature_host.add_child(encounter) func _on_exit_pressed() -> void: if transitioning: return transitioning = true GameState.surviving_ship_count = fleet.ships.size() GameState.surviving_vessel_types = fleet.vessel_types() var tween: Tween = create_tween() tween.tween_property(fade_rect, "color:a", 1.0, fade_duration) tween.tween_callback(_return_to_space) func _return_to_space() -> void: get_tree().change_scene_to_file("res://world/Main.tscn") # The banner is a sibling under UI and may not exist in a stripped-down test # harness, so this is fetched rather than held and is allowed to be missing. func _on_planet_conquered(planet_id: String, research_points_awarded: int) -> void: if planet_id != GameState.planet_id: return var banner: Node = get_node_or_null("../UI/NarrationBanner") if banner == null: return banner.say("The last of the garrison is down. This world is ours.", CONQUEST_SPEAKER) # Said even when it is none, because "this one paid nothing" is the # information a player deciding where to go next actually needs. banner.say("Research points recovered: %d." % research_points_awarded, CONQUEST_SPEAKER)