Forgejo #5. A new building gated behind a "Vessel Science" research node. Its own screen upgrades each vessel type - weapon, movement, shield and freight - and unlocks the shield vessel, which the ship crafter can then build. Vessel research is deliberately NOT more nodes in the mothership tree: that tree gates what may be built at all and is a strategic spine, while these are repeatable tuning on vessels already owned. Levels cost base * (level + 1), so the third point in something is not the price of the first. Upgrades are read live rather than baked in at build time, so researching one improves the ships already flying. The shield is personal, not a bubble: it absorbs a hit that would destroy the vessel - ships have no health, contact kills - and is then down until it cycles. Its ring is a NeonPiece child toggled with `visible` per #10, not a redraw of the hull. The build menu now holds six entries across three rows, which the #21 repacking already handles. Enemies still have no weapon, so "blocks shots" has nothing to block yet; that is filed separately rather than widened into this ticket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
138 lines
5.9 KiB
GDScript
138 lines
5.9 KiB
GDScript
extends Node2D
|
|
|
|
const WORLD_TO_LOCAL: float = 0.04
|
|
|
|
# Mirrors Building.gd's own MOTHERSHIP_COLOR/CRAFTER_COLOR/COMMAND_COLOR so a
|
|
# marker on the map reads as "the same building" at a glance. Duplicated
|
|
# rather than cross-referenced - the same colour is already independently
|
|
# duplicated between Ship.gd, Fleet.gd and Building.gd's HULL_COLOR, so this
|
|
# follows the codebase's existing convention rather than inventing a new one.
|
|
const MOTHERSHIP_MARKER_COLOR: Color = Color(0.45, 0.9, 0.55)
|
|
const CRAFTER_MARKER_COLOR: Color = Color(1.0, 0.72, 0.25)
|
|
const COMMAND_MARKER_COLOR: Color = Color(0.55, 0.6, 1.0)
|
|
const TRANSPORT_HUB_MARKER_COLOR: Color = Color(0.95, 0.55, 0.85)
|
|
const SHIP_LAB_MARKER_COLOR: Color = Color(0.55, 0.95, 0.9)
|
|
|
|
# A structure built on a planet's surface (see GameState.extractors) is not
|
|
# itself a map marker - it is a small badge on the existing planet marker,
|
|
# in a colour distinct from both the planets and the space buildings so it
|
|
# reads as "something is mined here" rather than another building type.
|
|
const EXTRACTOR_BADGE_COLOR: Color = Color(1.0, 0.85, 0.3)
|
|
const EXTRACTOR_BADGE_OFFSET: Vector2 = Vector2(8.0, -8.0)
|
|
const EXTRACTOR_BADGE_RADIUS: float = 5.0
|
|
|
|
@onready var tracker: Node2D = get_node("../../../ExplorationTracker")
|
|
@onready var fleet: Node2D = get_node("../../../Fleet")
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
if is_visible_in_tree():
|
|
queue_redraw()
|
|
|
|
|
|
# Local-space marker data for every player-placed building (GameState.
|
|
# buildings), one entry per building with its colour already resolved. Kept
|
|
# as a separate, callable step (rather than inlined in _draw()) so a
|
|
# headless test - which has no GPU and cannot check drawn pixels - can
|
|
# verify a built building actually produces a marker.
|
|
func building_markers() -> Array:
|
|
var markers: Array = []
|
|
for b in GameState.buildings:
|
|
markers.append({
|
|
"position": b.position * WORLD_TO_LOCAL,
|
|
"type": b.type,
|
|
"color": _building_marker_color(b.type),
|
|
})
|
|
return markers
|
|
|
|
|
|
func _building_marker_color(type: String) -> Color:
|
|
match type:
|
|
GameState.BUILDING_MOTHERSHIP:
|
|
return MOTHERSHIP_MARKER_COLOR
|
|
GameState.BUILDING_SHIP_CRAFTER:
|
|
return CRAFTER_MARKER_COLOR
|
|
GameState.BUILDING_TRANSPORT_HUB:
|
|
return TRANSPORT_HUB_MARKER_COLOR
|
|
GameState.BUILDING_SHIP_LAB:
|
|
return SHIP_LAB_MARKER_COLOR
|
|
_:
|
|
return COMMAND_MARKER_COLOR
|
|
|
|
|
|
# Whether the planet in this (explored) chunk has an extractor on it, per
|
|
# GameState.has_extractor_at(). A separate predicate for the same
|
|
# GPU-less-testability reason as building_markers().
|
|
func chunk_has_extractor_badge(chunk: Vector2i) -> bool:
|
|
if not WorldGen.has_planet_at_chunk(chunk):
|
|
return false
|
|
return GameState.has_extractor_at(WorldGen.planet_data_at_chunk(chunk).name)
|
|
|
|
|
|
func _regular_polygon(center: Vector2, radius: float, sides: int, phase: float = 0.0) -> PackedVector2Array:
|
|
var pts: PackedVector2Array = PackedVector2Array()
|
|
for i in range(sides):
|
|
var a: float = (float(i) / float(sides)) * TAU + phase
|
|
pts.append(center + Vector2(cos(a), sin(a)) * radius)
|
|
return pts
|
|
|
|
|
|
# Filled shape plus a thin white outline - the same construction the fleet
|
|
# marker below already uses, just factored out since buildings now need it
|
|
# three times over (once per type) and the extractor badge needs it too.
|
|
func _draw_marker_polygon(pts: PackedVector2Array, color: Color) -> void:
|
|
draw_colored_polygon(pts, color)
|
|
var closed: PackedVector2Array = pts.duplicate()
|
|
closed.append(pts[0])
|
|
draw_polyline(closed, Color(1, 1, 1), 1.5, true)
|
|
|
|
|
|
func _draw() -> void:
|
|
var chunk_size_local: float = WorldGen.CHUNK_SIZE * WORLD_TO_LOCAL
|
|
|
|
for chunk in tracker.get_explored_chunks():
|
|
var origin_local: Vector2 = WorldGen.chunk_origin(chunk) * WORLD_TO_LOCAL
|
|
var biome: int = WorldGen.biome_at_chunk(chunk)
|
|
var color: Color = WorldGen.biome_color(biome)
|
|
var rect: Rect2 = Rect2(origin_local, Vector2(chunk_size_local, chunk_size_local))
|
|
draw_rect(rect, color, true)
|
|
draw_rect(rect, Color(0.0, 0.0, 0.0, 0.4), false, 1.5)
|
|
|
|
if WorldGen.has_planet_at_chunk(chunk):
|
|
# Same deterministic per-chunk data PlanetField spawns the
|
|
# actual Planet from - the marker stays put on the map even
|
|
# for a chunk whose Planet node isn't currently loaded (out of
|
|
# spawn_radius_chunks of the fleet).
|
|
var data: Dictionary = WorldGen.planet_data_at_chunk(chunk)
|
|
var planet_local: Vector2 = origin_local + data.local_pos * WORLD_TO_LOCAL
|
|
draw_circle(planet_local, 5.0, Color(0.7, 0.65, 0.55))
|
|
draw_arc(planet_local, 5.0, 0.0, TAU, 16, Color(1.0, 1.0, 1.0, 0.9), 1.0, true)
|
|
|
|
if chunk_has_extractor_badge(chunk):
|
|
var badge_pts: PackedVector2Array = _regular_polygon(
|
|
planet_local + EXTRACTOR_BADGE_OFFSET, EXTRACTOR_BADGE_RADIUS, 4, PI / 4.0)
|
|
_draw_marker_polygon(badge_pts, EXTRACTOR_BADGE_COLOR)
|
|
|
|
# Buildings are placed freely in space rather than tied to a chunk, so
|
|
# they get their own pass rather than living inside the chunk loop above.
|
|
for marker in building_markers():
|
|
match marker.type:
|
|
GameState.BUILDING_MOTHERSHIP:
|
|
_draw_marker_polygon(_regular_polygon(marker.position, 8.0, 6, PI / 6.0), marker.color)
|
|
GameState.BUILDING_SHIP_CRAFTER:
|
|
_draw_marker_polygon(_regular_polygon(marker.position, 7.0, 4, 0.0), marker.color)
|
|
GameState.BUILDING_TRANSPORT_HUB:
|
|
_draw_marker_polygon(_regular_polygon(marker.position, 7.5, 8, PI / 8.0), marker.color)
|
|
GameState.BUILDING_SHIP_LAB:
|
|
_draw_marker_polygon(_regular_polygon(marker.position, 7.5, 3, -PI / 2.0), marker.color)
|
|
_:
|
|
_draw_marker_polygon(_regular_polygon(marker.position, 6.0, 4, PI / 4.0), marker.color)
|
|
|
|
var fleet_local: Vector2 = fleet.get_centroid() * WORLD_TO_LOCAL
|
|
var marker_points: PackedVector2Array = PackedVector2Array([
|
|
fleet_local + Vector2(0, -10),
|
|
fleet_local + Vector2(7, 8),
|
|
fleet_local + Vector2(-7, 8),
|
|
])
|
|
draw_colored_polygon(marker_points, Color(0.3, 0.9, 1.0))
|
|
draw_polyline(PackedVector2Array([marker_points[0], marker_points[1], marker_points[2], marker_points[0]]), Color(1, 1, 1), 1.5, true)
|