extends Node2D # A landable world-object - unlike regular Asteroid.gd, planets are # stationary ("Big Stable Asteroid" - stable means it doesn't drift or # destroy ships on touch). Whether the fleet counts as "landed" is decided # by PlanetField comparing the fleet's CENTROID distance to landing_radius, # not per-ship Area2D collision - a single stable number, immune to the # constant minor jostling of individual ships holding formation, which # used to make the "enter atmosphere" prompt flicker on/off as ships # drifted in and out of the boundary independently of each other even # while the fleet as a whole was clearly sitting on the planet. @export var planet_name: String = "" @export var planet_type: String = "rocky" @export var resource_type: String = "ore" @export var surface_profile_id: String = "rocky" @export var landing_radius: float = 140.0 # Set by whoever spawns this planet (PlanetField, seeded from the chunk's # own coordinates) so the outline shape is stable across respawns of the # same chunk, instead of reading as a "different" planet every time. @export var shape_seed: int = 0 var shape_points: PackedVector2Array = PackedVector2Array() # An extractor is built down on this planet's SURFACE, in a different scene # the player is no longer looking at, so from space nothing distinguished a # planet already being mined from an untouched one - you had to land to find # out. This is a derrick planted on the rim: the same amber the world map # uses for its extractor badge, so the two read as the same fact. const EXTRACTOR_MARKER_COLOR: Color = Color(1.0, 0.85, 0.3) const DERRICK_HEIGHT: float = 44.0 const DERRICK_HALF_WIDTH: float = 16.0 # Kept in step with GameState.extractor_added rather than polled per frame. # An extractor is built from SPACE (BuildController._planet_under_fleet(), # with the fleet parked on the planet), so this node is already in the tree # with its _ready() long since run when the answer changes - resolving it # only at _ready() left the planet looking untouched until it happened to # despawn and respawn. var shows_extractor: bool = false var gas_phase: float = 0.0 func _ready() -> void: add_to_group("planets") z_index = -1 var rng: RandomNumberGenerator = RandomNumberGenerator.new() rng.seed = shape_seed var point_count: int = 10 for i in range(point_count): var angle: float = (float(i) / point_count) * TAU var radius: float = rng.randf_range(70.0, 100.0) shape_points.append(Vector2(cos(angle), sin(angle)) * radius) shows_extractor = GameState.has_extractor_at(planet_name) GameState.extractor_added.connect(_on_extractor_added) queue_redraw() func _process(delta: float) -> void: if planet_type == "gas_giant": gas_phase += delta queue_redraw() func _on_extractor_added(built_on: String) -> void: # Only the planet it was actually built on, and only a redraw when the # answer really changed - every other planet in range gets this too. if built_on != planet_name or shows_extractor: return shows_extractor = true queue_redraw() func extractor_derrick_anchor() -> Vector2: # The rim vertex the derrick stands on: the shape's own topmost point, # rather than a fixed radius, so it sits ON the outline whatever lumpy # shape the seed drew instead of floating off it or sinking inside it. var base: Vector2 = Vector2(0.0, -100.0) for p in shape_points: if p.y < base.y: base = p return base func extractor_derrick_tip() -> Vector2: # Exposed separately from the polygon because "does the derrick clear the # planet's outline" is a question about the TIP alone. The base corners # sit a half-width off to either side, so they are sqrt(|base|^2 + w^2) # from the centre and can clear the rim on their own even when the tower # points straight into the ground. var base: Vector2 = extractor_derrick_anchor() var out: Vector2 = base.normalized() if out == Vector2.ZERO: out = Vector2.UP return base + out * DERRICK_HEIGHT func extractor_derrick_points() -> PackedVector2Array: var base: Vector2 = extractor_derrick_anchor() var out: Vector2 = base.normalized() if out == Vector2.ZERO: out = Vector2.UP var side: Vector2 = Vector2(-out.y, out.x) var tip: Vector2 = extractor_derrick_tip() return PackedVector2Array([ base - side * DERRICK_HALF_WIDTH, tip - side * (DERRICK_HALF_WIDTH * 0.35), tip + side * (DERRICK_HALF_WIDTH * 0.35), base + side * DERRICK_HALF_WIDTH, ]) func _draw() -> void: if planet_type == "gas_giant": _draw_gas_giant() if shows_extractor: NeonDraw.draw_glowing_polygon(self, extractor_derrick_points(), EXTRACTOR_MARKER_COLOR, 1.0) return NeonDraw.draw_glowing_polygon(self, shape_points, Color(0.6, 0.55, 0.5), 1.0) if shows_extractor: NeonDraw.draw_glowing_polygon(self, extractor_derrick_points(), EXTRACTOR_MARKER_COLOR, 1.0) func _draw_gas_giant() -> void: # A round, animated giant with drifting latitudinal bands. It is visibly # unlike the rocky outline and keeps moving even while the fleet is idle. var rim := PackedVector2Array() for i in range(32): var a := TAU * float(i) / 32.0 rim.append(Vector2(cos(a) * 118.0, sin(a) * 118.0)) NeonDraw.draw_glowing_polygon(self, rim, Color(0.4, 0.78, 1.0), 0.9) for row in [-62.0, -28.0, 8.0, 43.0, 73.0]: var band := PackedVector2Array() var half_width: float = sqrt(maxf(0.0, 118.0 * 118.0 - row * row)) for i in range(15): var x := lerpf(-half_width, half_width, float(i) / 14.0) band.append(Vector2(x, row + sin(x * 0.045 + gas_phase * 1.6 + row) * 7.0)) NeonDraw.draw_glowing_path(self, band, Color(0.85, 0.52, 1.0), 0.75, 0.65)