The fleet hold was unbounded - GameState.fleet_inventory had no total_capacity, so a lone ship could carry unlimited resources. Capacity is now data-driven: only vessels declaring the "cargo" capability contribute their cargo_capacity stat (interceptor: 20; shield vessel and gas bomber: 0, unarmed transporters are never in the fleet at all). Fleet.gd recomputes it in the one place composition can change (spawn_ships/add_ship/store_ship/_on_ship_destroyed), which also covers the space<->surface scene-swap restore since that goes through spawn_ships(). Rock.gd and GasVentField.gd (the two "fly over it to collect" pickup paths) now check what collect_cargo()/collect_gas_cargo() actually accepted before consuming their source, instead of assuming an unlimited hold. The HUD's FLEET HOLD caption shows the live total/cap. Also fixes test_resource_transfer.gd, which pushed more ore through a 2-ship fleet than the new cap allows - it now spawns the largest fleet every unlocked doctrine permits, since that test is about the transfer UI rather than the cap itself (test_fleet_cargo_limit.gd owns that).
107 lines
3.8 KiB
GDScript
107 lines
3.8 KiB
GDScript
extends Area2D
|
|
|
|
# A gatherable crystal of asteroid ore lying on a planet's surface. Picked
|
|
# up by flying any ship over it; the fleet doesn't need to stop.
|
|
#
|
|
# Deliberately drawn as a PERFECT HEXAGON rather than the irregular lumpy
|
|
# polygon Asteroid.gd uses. The two are never on screen
|
|
# together (asteroids are space-only, rocks surface-only), so the player
|
|
# can only tell "collect this" from "this hurts" by learned shape language:
|
|
# irregular and jagged reads as a natural hazard, regular and faceted reads
|
|
# as something valuable and meant to be picked up.
|
|
#
|
|
# It also drifts toward a nearby ship (see _physics_process). Nothing
|
|
# harmful moves helpfully toward you, so the pull doubles as a readability
|
|
# cue on top of making gathering feel less fiddly.
|
|
|
|
signal collected
|
|
|
|
@export var attract_radius: float = 220.0
|
|
@export var attract_speed: float = 260.0
|
|
@export var attract_accel: float = 420.0
|
|
|
|
var shape_points: PackedVector2Array = PackedVector2Array()
|
|
var facet_points: PackedVector2Array = PackedVector2Array()
|
|
var size_scale: float = 1.0
|
|
var velocity: Vector2 = Vector2.ZERO
|
|
|
|
|
|
func _ready() -> void:
|
|
add_to_group("rocks")
|
|
z_index = -1
|
|
area_entered.connect(_on_area_entered)
|
|
queue_redraw()
|
|
|
|
|
|
func build_shape(shape_seed: int) -> void:
|
|
# Only the SIZE varies, never the silhouette: every ore node is the same
|
|
# regular hexagon, which is the whole point of the cue.
|
|
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
|
|
rng.seed = shape_seed
|
|
size_scale = rng.randf_range(0.85, 1.25)
|
|
|
|
var r: float = 13.0 * size_scale
|
|
shape_points = PackedVector2Array()
|
|
for i in range(6):
|
|
var a: float = (float(i) / 6.0) * TAU - PI / 2.0
|
|
shape_points.append(Vector2(cos(a), sin(a)) * r)
|
|
# One flat-to-flat line across the middle, so it reads as a cut face
|
|
# rather than a plain outline at small sizes.
|
|
facet_points = PackedVector2Array([
|
|
shape_points[5], shape_points[2],
|
|
])
|
|
queue_redraw()
|
|
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
# Drift toward the closest ship inside attract_radius. No redraw here:
|
|
# position is a transform property and the crystal's shape is fixed.
|
|
var target: Node2D = _nearest_ship()
|
|
if target == null:
|
|
velocity = velocity.move_toward(Vector2.ZERO, attract_accel * delta)
|
|
else:
|
|
var desired: Vector2 = (target.position - position).normalized() * attract_speed
|
|
velocity = velocity.move_toward(desired, attract_accel * delta)
|
|
position += velocity * delta
|
|
|
|
|
|
func _nearest_ship() -> Node2D:
|
|
var nearest: Node2D = null
|
|
var best: float = attract_radius
|
|
for ship in get_tree().get_nodes_in_group("player_ships"):
|
|
if not is_instance_valid(ship):
|
|
continue
|
|
var d: float = position.distance_to(ship.position)
|
|
if d <= best:
|
|
best = d
|
|
nearest = ship
|
|
return nearest
|
|
|
|
|
|
func _on_area_entered(area: Area2D) -> void:
|
|
if area.is_in_group("player_ships"):
|
|
# Into the fleet's hold, not the bank (#14). It only counts as
|
|
# spendable once the fleet has flown it back to the mothership.
|
|
# #56: the hold has a finite total_capacity now - a full fleet must
|
|
# leave the rock behind rather than despawning it for nothing.
|
|
if GameState.collect_cargo(1) <= 0:
|
|
return
|
|
collected.emit()
|
|
queue_free()
|
|
|
|
|
|
func _draw() -> void:
|
|
if shape_points.is_empty():
|
|
return
|
|
# Deep teal with a near-black core: a dark, saturated colour reads
|
|
# clearly against the bright lunar regolith (sampled at ~0.75 grey),
|
|
# where the original warm yellow disappeared into it.
|
|
var glow: Color = Color(0.0, 0.62, 0.7)
|
|
var core: Color = Color(0.0, 0.12, 0.15)
|
|
NeonDraw.draw_glowing_polygon(self, shape_points, glow, 1.0, core)
|
|
# One line only - the hexagon has a single flat-to-flat facet. This used
|
|
# to draw two lines from a 4-point array; the hexagon rewrite left only
|
|
# 2 points, so the second line indexed out of bounds and every rock
|
|
# threw on every draw.
|
|
if facet_points.size() >= 2:
|
|
NeonDraw.draw_glowing_line(self, facet_points[0], facet_points[1], glow, 0.7, 0.3, core)
|