extends Node2D # Space-side: bootstraps the fleet on Main.tscn startup (a fresh 5-ship # fleet at the origin on first launch, or GameState's surviving ship # count/position if we just returned from a planet's surface), shows an # "enter atmosphere" prompt (name + button) when the fleet is standing on # a landable planet, and - on press - fades out and swaps to a completely # separate PlanetSurface.tscn scene (not an overlay toggled in place), so # asteroids/enemies genuinely cannot exist there rather than merely being # hidden. Fleet size and the world position/zoom to return to are handed # off via the GameState autoload, since change_scene_to_file() tears down # this whole scene tree. @export var fade_duration: float = 0.4 @export var prompt_debounce_time: float = 0.15 @onready var planet_field: Node2D = get_node("../PlanetField") @onready var asteroid_field: Node2D = get_node("../AsteroidField") @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 enter_button: IconButton = get_node("../UI/EnterAtmosphereButton") @onready var world_map: Control = get_node("../UI/WorldMap") @onready var build_controller: Node2D = get_node("../BuildController") @onready var fade_rect: ColorRect = get_node("../FadeOverlay/FadeRect") var current_planet: Node2D = null var transitioning: bool = false var pending_planet: Node2D = null var pending_time: float = 0.0 func _ready() -> void: enter_button.pressed.connect(_on_enter_pressed) if GameState.has_return_position: fleet.spawn_ships(GameState.surviving_ship_count, GameState.return_world_position, GameState.surviving_vessel_types) camera.position = GameState.return_world_position camera.zoom = Vector2(GameState.return_camera_zoom, GameState.return_camera_zoom) GameState.has_return_position = false else: # A brand new game. STARTING_SHIP_COUNT rather than a literal: this # said 5, which was the old BASE_FLEET_SIZE written out by hand, and # it silently became "however many slots exist" once max_fleet_size() # clamped it. The opening fleet size is one decision and it lives in # GameState. fleet.spawn_ships(GameState.STARTING_SHIP_COUNT, Vector2.ZERO) # AsteroidField/PlanetField normally only populate nearby chunks once # their own check_interval timer elapses (~1s) - fine during normal # continuous play (things appear far off-screen, gradually), but # right after a fresh scene load it means an empty-looking world for # a second before asteroids/the planet visibly pop in. Force an # immediate synchronous populate now that the fleet is correctly # positioned, so nothing is missing on the very first visible frame. # DEFERRED, not called straight through. Everything above runs inside the # single frame that builds Main.tscn, and these two populate 25 and 49 # chunks of world on top of it - 150-odd asteroids plus planets and their # visuals. On a phone browser that frame is where the scene appeared to # hang forever: nothing can paint until it ends, so a slow frame and a # freeze look identical. One frame later is still immediate to the eye, # and it lets the scene draw first. asteroid_field.update_chunks.call_deferred() planet_field.update_chunks.call_deferred() func _process(delta: float) -> void: if transitioning: return if world_map.visible or build_controller.is_showing_building_prompt(): # Context actions belong to BuildController. This controller owns the # debounced planet selection and label, and suppresses both while a modal # screen is open. current_planet = null pending_planet = null planet_name_label.visible = false return # Debounced: only commit a visible/hidden change once the raw # landing-detection result has stayed the same for prompt_debounce_time # in a row, instead of reacting to every single frame's reading. Belt # and suspenders on top of get_planet_fleet_is_on() already using the # fleet's centroid (a single stable number - see Planet.gd) rather # than any individual ship's position, so this only has to absorb # genuine sub-debounce-window boundary crossings, not constant noise. var detected_planet: Node2D = planet_field.get_planet_fleet_is_on() if detected_planet != pending_planet: pending_planet = detected_planet pending_time = 0.0 else: pending_time += delta if pending_time < prompt_debounce_time or pending_planet == current_planet: return current_planet = pending_planet if current_planet != null: planet_name_label.text = current_planet.planet_name planet_name_label.visible = true else: planet_name_label.visible = false func _on_enter_pressed() -> void: if transitioning or current_planet == null: return transitioning = true planet_name_label.visible = false enter_button.visible = false GameState.has_return_position = true GameState.surviving_ship_count = fleet.ships.size() GameState.surviving_vessel_types = fleet.vessel_types() # The planet's own position, not fleet.get_centroid() - the fleet only # needs to be somewhere within the planet's landing radius (not # centered exactly on it) for the prompt to show, so the centroid can # be tens of units off from the planet. Returning should always place # the fleet back at the planet itself. GameState.return_world_position = current_planet.position GameState.return_camera_zoom = camera.zoom.x GameState.planet_name = current_planet.planet_name GameState.planet_id = current_planet.planet_id GameState.planet_definition_id = current_planet.definition_id var tween: Tween = create_tween() tween.tween_property(fade_rect, "color:a", 1.0, fade_duration) tween.tween_callback(_enter_surface) func _enter_surface() -> void: get_tree().change_scene_to_file("res://PlanetSurface.tscn")