extends Node # Autoload singleton - the one thing that survives change_scene_to_file() # scene swaps between space (Main.tscn) and a planet's surface # (PlanetSurface.tscn). Everything else in either scene gets torn down # and rebuilt from scratch on each swap, so anything that needs to # survive the trip has to live here instead. var has_return_position: bool = false var surviving_ship_count: int = 5 var return_world_position: Vector2 = Vector2.ZERO var return_camera_zoom: float = 1.0 var planet_name: String = "" var planet_type: String = "" # Explored-chunk history for the world map's fog-of-war reveal. Lives # here (not on ExplorationTracker, which gets destroyed and recreated on # every scene swap) so exploration progress isn't lost every time the # player lands on and leaves a planet. var explored_chunks: Dictionary = {} # --- Resources ------------------------------------------------------------- # Rocks mined from asteroid-planet surfaces, spent on buildings and ships. # Starts stocked for play-testing so buildings can be tried without mining # first - drop this back to 0 for a real playthrough. var rocks: int = 500 const MOTHERSHIP_COST: int = 20 const SHIP_CRAFTER_COST: int = 12 const SHIP_COST: int = 6 # --- Buildings ------------------------------------------------------------- # Player-placed structures, as [{"type": String, "position": Vector2}]. # Buildings exist only in space (Main.tscn) but are stored here so they # survive the scene swap to a planet's surface and back. const BUILDING_MOTHERSHIP: String = "mothership" const BUILDING_SHIP_CRAFTER: String = "ship_crafter" const BUILDING_COMMAND_CENTER: String = "command_center" # Extractors are not placed in space like the others - they are built ON a # planet's surface and tracked separately (see `extractors`). const BUILDING_EXTRACTOR: String = "extractor" const COMMAND_CENTER_COST: int = 15 const EXTRACTOR_COST: int = 25 var buildings: Array = [] func add_building(type: String, at_position: Vector2) -> void: buildings.append({"type": type, "position": at_position}) func has_mothership() -> bool: return get_mothership_position() != null func remove_buildings_of_type(type: String) -> int: var kept: Array = [] var removed: int = 0 for b in buildings: if b.type == type: removed += 1 else: kept.append(b) buildings = kept return removed func get_mothership_position() -> Variant: # Where a wiped-out fleet gets rebuilt. Returns null when no mothership # has been built yet, so the caller can fall back to the world origin. for b in buildings: if b.type == BUILDING_MOTHERSHIP: return b.position return null # --- Fleet formation ------------------------------------------------------- # Where each vessel sits relative to the fleet centre, editable at a command # centre. Lives here (not as a const on Fleet) so a layout the player has # arranged survives the trip to a planet's surface and back, and so a # rebuilt fleet keeps their arrangement. const DEFAULT_FORMATION: Array = [ Vector2(0, -48), Vector2(-30, -8), Vector2(30, -8), Vector2(-60, 32), Vector2(60, 32), ] var formation: Array = DEFAULT_FORMATION.duplicate() func reset_formation() -> void: formation = DEFAULT_FORMATION.duplicate() func rotate_formation(radians: float) -> void: # Pivots every slot about the fleet centre, keeping the shape intact # and only changing which way it faces. for i in range(formation.size()): formation[i] = formation[i].rotated(radians) # --- Extractors and their haulers ----------------------------------------- # An extractor sits on a planet and runs a single automated hauler back and # forth to the mothership, delivering ore on each arrival. # # The round trip is SIMULATED HERE, in the autoload, rather than by any # node: it advances whether the player is in space, on a planet's surface, # or far enough away that nothing about the route is loaded. `progress` is # a fraction of one full there-and-back cycle, so a hauler's drawn position # is derived from the same number that decides when ore is delivered - the # visual arrival and the payout cannot drift apart, because they are the # same event. # # Entries are {"position": Vector2 (the planet's position in SPACE), # "planet_name": String, "progress": float}. const HAULER_SPEED: float = 260.0 const HAULER_YIELD: int = 4 # Progress value at which the hauler reaches the mothership: exactly half # way through the cycle, the outbound leg being the first half. const HAULER_ARRIVAL: float = 0.5 var extractors: Array = [] func has_extractor_at(planet_name: String) -> bool: for e in extractors: if e.planet_name == planet_name: return true return false func add_extractor(planet_name: String, at_position: Vector2) -> void: extractors.append({ "position": at_position, "planet_name": planet_name, "progress": 0.0, }) func get_hauler_route_time(extractor: Dictionary) -> float: # Full cycle: out to the mothership and back again. var mothership: Variant = get_mothership_position() if mothership == null: return 0.0 var distance: float = extractor.position.distance_to(mothership) if distance < 1.0: return 0.0 return (distance * 2.0) / HAULER_SPEED func get_hauler_position(extractor: Dictionary) -> Variant: # Where the hauler is right now, derived purely from `progress`. Returns # null when there is no route (no mothership built yet). var mothership: Variant = get_mothership_position() if mothership == null: return null var t: float = extractor.progress if t <= HAULER_ARRIVAL: return extractor.position.lerp(mothership, t / HAULER_ARRIVAL) return mothership.lerp(extractor.position, (t - HAULER_ARRIVAL) / (1.0 - HAULER_ARRIVAL)) func _process(delta: float) -> void: # Runs in the autoload, so extraction keeps earning while the player is # on a planet's surface or nowhere near the route. if extractors.is_empty(): return for e in extractors: var cycle: float = get_hauler_route_time(e) if cycle <= 0.0: continue # nothing to deliver to yet var before: float = e.progress e.progress += delta / cycle # Ore lands the instant the hauler reaches the mothership - the same # instant get_hauler_position() puts it there. if before < HAULER_ARRIVAL and e.progress >= HAULER_ARRIVAL: rocks += HAULER_YIELD while e.progress >= 1.0: e.progress -= 1.0 func can_afford(cost: int) -> bool: return rocks >= cost func spend(cost: int) -> bool: if not can_afford(cost): return false rocks -= cost return true