Animate ship crafter deliveries

This commit is contained in:
Loïc Wisniewski 2026-08-13 21:39:05 +02:00
commit 798deb48a1
5 changed files with 140 additions and 4 deletions

View file

@ -308,6 +308,11 @@ var next_building_id: int = 1
# Production is simulated here, in the autoload, the same way extractor # Production is simulated here, in the autoload, the same way extractor
# haulers are, so a build finishes whether or not the player is in space. # haulers are, so a build finishes whether or not the player is in space.
var stored_vessels: Array = [] var stored_vessels: Array = []
# Finished crafter orders travel to the mothership before appearing in its
# hangar. State is authoritative here so delivery continues off-screen.
# {"type", "from", "to", "progress"}
var vessel_deliveries: Array = []
const VESSEL_DELIVERY_SPEED: float = 950.0
signal order_completed(building_id: int, type: String) signal order_completed(building_id: int, type: String)
@ -695,9 +700,15 @@ func _advance_production(delta: float) -> void:
# hub and wait for work (#19). # hub and wait for work (#19).
_station_transporter(b.position) _station_transporter(b.position)
else: else:
# Everything else is delivered to the mothership and waits there # A finished hull launches from the crafter and physically flies to
# until the player crews it (#23). # the mothership hangar. With no mothership there is nowhere to fly,
stored_vessels.append(product) # so preserve the old safe fallback of holding it immediately.
var mothership: Variant = get_mothership_position()
if mothership == null:
stored_vessels.append(product)
else:
vessel_deliveries.append({"type": product, "from": b.position,
"to": mothership, "progress": 0.0})
order_completed.emit(int(b.id), product) order_completed.emit(int(b.id), product)
@ -818,6 +829,7 @@ func _process(delta: float) -> void:
# Runs in the autoload, so mining and transport continue while the player # Runs in the autoload, so mining and transport continue while the player
# is on a planet's surface or nowhere near either. # is on a planet's surface or nowhere near either.
_advance_production(delta) _advance_production(delta)
_advance_vessel_deliveries(delta)
for e in extractors: for e in extractors:
e.mining_progress = float(e.get("mining_progress", 0.0)) + delta * EXTRACTOR_RATE e.mining_progress = float(e.get("mining_progress", 0.0)) + delta * EXTRACTOR_RATE
var whole: int = int(floor(float(e.mining_progress))) var whole: int = int(floor(float(e.mining_progress)))
@ -827,6 +839,21 @@ func _process(delta: float) -> void:
_advance_transporters(delta) _advance_transporters(delta)
func vessel_delivery_position(delivery: Dictionary) -> Vector2:
return (delivery.from as Vector2).lerp(delivery.to as Vector2,
clampf(float(delivery.progress), 0.0, 1.0))
func _advance_vessel_deliveries(delta: float) -> void:
for i in range(vessel_deliveries.size() - 1, -1, -1):
var delivery: Dictionary = vessel_deliveries[i]
var distance: float = (delivery.from as Vector2).distance_to(delivery.to as Vector2)
delivery.progress = float(delivery.progress) + delta / maxf(distance / VESSEL_DELIVERY_SPEED, 0.001)
if float(delivery.progress) >= 1.0:
stored_vessels.append(String(delivery.type))
vessel_deliveries.remove_at(i)
# --- transporters (#19) --------------------------------------------------- # --- transporters (#19) ---------------------------------------------------
# Vessels stationed at a transport hub that fetch ore from the mothership and # Vessels stationed at a transport hub that fetch ore from the mothership and

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=22 format=3] [gd_scene load_steps=23 format=3]
[ext_resource type="Script" path="res://Starfield.gd" id="1"] [ext_resource type="Script" path="res://Starfield.gd" id="1"]
[ext_resource type="Script" path="res://Fleet.gd" id="2"] [ext_resource type="Script" path="res://Fleet.gd" id="2"]
@ -24,6 +24,7 @@
[ext_resource type="Script" path="res://TransporterField.gd" id="23"] [ext_resource type="Script" path="res://TransporterField.gd" id="23"]
[ext_resource type="Script" path="res://ResourceBar.gd" id="24"] [ext_resource type="Script" path="res://ResourceBar.gd" id="24"]
[ext_resource type="Script" path="res://GasCounter.gd" id="25"] [ext_resource type="Script" path="res://GasCounter.gd" id="25"]
[ext_resource type="Script" path="res://VesselDeliveryField.gd" id="26"]
[sub_resource type="ShaderMaterial" id="1"] [sub_resource type="ShaderMaterial" id="1"]
shader = ExtResource("5") shader = ExtResource("5")
@ -74,6 +75,9 @@ script = ExtResource("14")
[node name="TransporterField" type="Node2D" parent="."] [node name="TransporterField" type="Node2D" parent="."]
script = ExtResource("23") script = ExtResource("23")
[node name="VesselDeliveryField" type="Node2D" parent="."]
script = ExtResource("26")
[node name="BuildController" type="Node2D" parent="."] [node name="BuildController" type="Node2D" parent="."]
script = ExtResource("15") script = ExtResource("15")

16
VesselDelivery.gd Normal file
View file

@ -0,0 +1,16 @@
extends Node2D
# Visual-only craft-to-mothership flight. GameState owns position/progress;
# this node just renders a finished hull headed toward the hangar.
var vessel_type: String = GameState.VESSEL_INTERCEPTOR
func _ready() -> void:
z_index = -1
queue_redraw()
func _draw() -> void:
var color: Color = Color(0.45, 0.8, 1.0) if vessel_type == GameState.VESSEL_SHIELD else Color(0.95, 0.55, 0.85) if vessel_type == GameState.VESSEL_TRANSPORTER else Color(0.25, 0.85, 1.0)
var hull := PackedVector2Array([Vector2(0, -20), Vector2(13, 15), Vector2(0, 9), Vector2(-13, 15)])
NeonDraw.draw_glowing_polygon(self, hull, color, 1.0)

37
VesselDeliveryField.gd Normal file
View file

@ -0,0 +1,37 @@
extends Node2D
@export var visible_radius: float = 2600.0
@onready var fleet: Node2D = get_node("../Fleet")
var delivery_script: Script = preload("res://VesselDelivery.gd")
var active: Dictionary = {}
func _process(_delta: float) -> void:
var centre: Vector2 = fleet.get_centroid()
for i in range(GameState.vessel_deliveries.size()):
var delivery: Dictionary = GameState.vessel_deliveries[i]
var pos := GameState.vessel_delivery_position(delivery)
if pos.distance_to(centre) > visible_radius:
_despawn(i)
continue
var node: Node2D = active.get(i)
if node == null or not is_instance_valid(node):
node = delivery_script.new()
get_parent().add_child(node)
active[i] = node
node.vessel_type = String(delivery.type)
node.position = pos
var direction: Vector2 = (delivery.to as Vector2) - (delivery.from as Vector2)
if direction.length() > 0.1:
node.rotation = direction.angle() + PI / 2.0
node.queue_redraw()
for key in active.keys():
if key >= GameState.vessel_deliveries.size():
_despawn(key)
func _despawn(index: int) -> void:
var node: Node2D = active.get(index)
if node != null and is_instance_valid(node):
node.queue_free()
active.erase(index)

View file

@ -0,0 +1,52 @@
extends SceneTree
# Forgejo #28: a completed crafter order visibly travels to the mothership,
# rather than appearing in the hangar at the instant the build timer ends.
var game_state: Node
var scene: Node
var stage := 0
var elapsed := 0.0
func _initialize() -> void:
game_state = root.get_node("GameState")
game_state.buildings.clear()
game_state.extractors.clear()
game_state.transporters.clear()
game_state.vessel_deliveries.clear()
game_state.stored_vessels.clear()
game_state.build_time_scale = 0.02
game_state.add_building(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO)
game_state.add_building(GameState.BUILDING_SHIP_CRAFTER, Vector2(900, 0))
game_state.deposit_to_building(2, GameState.SHIP_COST)
game_state.queue_order(2, GameState.PRODUCT_SHIP)
scene = load("res://Main.tscn").instantiate()
root.add_child(scene)
func _process(delta: float) -> bool:
elapsed += delta
if stage == 0 and not game_state.vessel_deliveries.is_empty():
if not game_state.stored_vessels.is_empty():
return _fail("ship reached hangar before its delivery flight")
var field: Node = scene.get_node("VesselDeliveryField")
if field.active.is_empty():
return _fail("delivery flight was not rendered")
print("finished ship launched from crafter toward mothership")
stage = 1
if stage == 1 and game_state.vessel_deliveries.is_empty():
if game_state.stored_vessels.size() != 1:
return _fail("delivered ship did not arrive in hangar")
print("delivery arrived in mothership hangar")
print("TEST_VESSEL_DELIVERY: PASS")
quit(0)
return true
if elapsed > 8.0:
return _fail("delivery timed out")
return false
func _fail(message: String) -> bool:
print("TEST_VESSEL_DELIVERY: FAIL (%s)" % message)
quit(1)
return true