#216: transport hubs and spacial workshops no longer conjure their crews. Transporters and repair drones are ordered at a ship crafter, assigned per building through a new Station menu, flown out from the mothership hangar, and spent by the work they do - 20 runs, 12 repairs, counted where the run ENDS so nothing retires with cargo in the air or a building half repaired. A spent vessel is gone, so the structure is short again and the player has to build another. The menu shows what each one has left, worst first. Craftability needed its own flag: "the crafter can make it" and "it can take a berth in the fleet" stopped being the same question. #217: transporters crawl at 120 rather than 150, and a Burn Drive lets them carry energy and spend it to travel far faster. Fetching it is a leg of its own, to the nearest store that has any - so a refuelling transporter cannot be dispatched mid-errand - and the leg flown to COLLECT fuel is not charged for it. Unresearched, nothing detours and nothing changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
174 lines
6.8 KiB
GDScript
174 lines
6.8 KiB
GDScript
extends SceneTree
|
|
|
|
# Forgejo #216: a transport hub used to conjure its own transporters, free and
|
|
# permanent. They are built at a ship crafter now, flown out from the
|
|
# mothership hangar to whichever hub asked for them, and worn out by the runs
|
|
# they make.
|
|
#
|
|
# Covers:
|
|
# A. a new hub stands empty - nothing is provisioned with the structure
|
|
# B. the crafter accepts a transporter order, which it used to refuse
|
|
# C. one assigned and one in the hangar puts it at the hub, and a hub that
|
|
# was asked for nothing stays empty however many are sitting there
|
|
# D. the transporter then does the job the old free ones did: it supplies a
|
|
# queued order, without ore being created or destroyed
|
|
# E. it wears out, and the spent vessel is gone rather than returned
|
|
|
|
var state: Node
|
|
var crafter_id: int
|
|
var hub_id: int
|
|
var total_at_start: int
|
|
var consumed: int = 0
|
|
var elapsed: float = 0.0
|
|
var stage: int = 0
|
|
var conservation_broken: bool = false
|
|
|
|
|
|
func _initialize() -> void:
|
|
state = root.get_node("GameState")
|
|
state.buildings.clear()
|
|
state.extractors.clear()
|
|
state.transporters.clear()
|
|
state.stored_vessels.clear()
|
|
state.vessel_deliveries.clear()
|
|
state.reset_progression()
|
|
state.rocks = 400
|
|
state.build_time_scale = 0.02
|
|
state.unlocked = ["shipyards", "logistics", "freight"]
|
|
state.add_building(state.BUILDING_MOTHERSHIP, Vector2.ZERO)
|
|
crafter_id = state.add_building(state.BUILDING_SHIP_CRAFTER, Vector2(900, -300))
|
|
hub_id = state.add_building(state.BUILDING_TRANSPORT_HUB, Vector2(600, 200))
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
# --- A. nothing comes with the structure ---------------------
|
|
if not state.transporters.is_empty():
|
|
return _fail("a new hub arrived with %d transporters" % state.transporters.size())
|
|
if state.station_desired(hub_id) != 0:
|
|
return _fail("a new hub already had a crew ordered")
|
|
print("a new hub stands empty")
|
|
|
|
# --- B. the crafter takes the order --------------------------
|
|
if not state.queue_order(crafter_id, state.VESSEL_TRANSPORTER):
|
|
return _fail("the crafter refused a transporter order")
|
|
state.find_building(crafter_id).orders.clear()
|
|
|
|
# --- C. assigned, and only then sent -------------------------
|
|
# One in the hangar and nothing asked for: it must stay there.
|
|
state.stored_vessels.append(state.VESSEL_TRANSPORTER)
|
|
state._advance_station_dispatch()
|
|
if not state.transporters.is_empty():
|
|
return _fail("a hub that asked for nothing took a transporter anyway")
|
|
if state.set_station_desired(hub_id, 1) != 1:
|
|
return _fail("the hub refused an order for one transporter")
|
|
state._advance_station_dispatch()
|
|
if state.stored_vessels.has(state.VESSEL_TRANSPORTER):
|
|
return _fail("the assigned transporter never left the hangar")
|
|
if state.vessel_deliveries.is_empty():
|
|
return _fail("nothing was sent to the hub")
|
|
elapsed = 0.0
|
|
stage = 1
|
|
return false
|
|
1:
|
|
if state.station_crew(hub_id) < 1:
|
|
if elapsed > 25.0:
|
|
return _fail("the transporter never reached the hub")
|
|
return false
|
|
var condition: Array = state.station_condition(hub_id)
|
|
if condition.size() != 1 or int(condition[0]) != GameState.TRANSPORTER_TRIPS:
|
|
return _fail("the arrived transporter reports %s trips, expected [%d]" \
|
|
% [condition, GameState.TRANSPORTER_TRIPS])
|
|
print("ordered, built and flown out: 1 at the hub with %d runs in it" \
|
|
% GameState.TRANSPORTER_TRIPS)
|
|
|
|
# --- D. and it does the work ---------------------------------
|
|
if not state.queue_order(crafter_id, state.PRODUCT_SHIP):
|
|
return _fail("crafter refused the combat-vessel order")
|
|
state.order_completed.connect(_on_order_completed)
|
|
total_at_start = _total()
|
|
elapsed = 0.0
|
|
stage = 2
|
|
return false
|
|
2:
|
|
if _total() + consumed != total_at_start:
|
|
conservation_broken = true
|
|
if state.pending_orders(crafter_id) > 0:
|
|
if elapsed > 25.0:
|
|
return _fail("the hub's transporter never supplied the queued order")
|
|
return false
|
|
if conservation_broken:
|
|
return _fail("ore was created or destroyed on the automatic route")
|
|
print("the hub's own transporter supplied the crafter")
|
|
|
|
elapsed = 0.0
|
|
stage = 3
|
|
return false
|
|
3:
|
|
# --- E. and it is used up by doing so ------------------------
|
|
# The run is spent when it gets HOME, not when the cargo lands, so
|
|
# this waits for the transporter to be idle at its hub again -
|
|
# which is also the rule that stops a vessel retiring with its
|
|
# cargo still in the air.
|
|
if state.transporters.is_empty():
|
|
return _fail("the transporter vanished before completing its run")
|
|
if String(state.transporters[0].state) != state.TRANSPORTER_IDLE:
|
|
if elapsed > 25.0:
|
|
return _fail("the transporter never got home")
|
|
return false
|
|
var worn: Array = state.station_condition(hub_id)
|
|
if worn.is_empty() or int(worn[0]) >= GameState.TRANSPORTER_TRIPS:
|
|
return _fail("a transporter that ran a supply route is still unworn (%s)" % [worn])
|
|
print("a completed run wore it to %d" % int(worn[0]))
|
|
# Wound down to its last run, so the next arrival home retires it.
|
|
state.transporters[0].trips_left = 1
|
|
elapsed = 0.0
|
|
stage = 4
|
|
return false
|
|
4:
|
|
if not state.transporters.is_empty():
|
|
if elapsed > 30.0:
|
|
return _fail("a transporter on its last run was never retired")
|
|
# Keep it working: an idle one at its hub never completes
|
|
# another leg, and it is the arrival home that spends the run.
|
|
state.deposit_resource_to_building(crafter_id, GameState.RESOURCE_ORE, 0)
|
|
state.queue_order(crafter_id, state.PRODUCT_SHIP)
|
|
return false
|
|
if state.stored_vessels.has(state.VESSEL_TRANSPORTER):
|
|
return _fail("a spent transporter went back to the hangar instead of being used up")
|
|
if state.station_crew(hub_id) != 0:
|
|
return _fail("the hub still counts a retired transporter")
|
|
print("spent after its last run: gone, and the hub is short again")
|
|
print("TEST_TRANSPORTER_SUPPLY: PASS")
|
|
quit(0)
|
|
return true
|
|
return false
|
|
|
|
|
|
func _on_order_completed(_building_id: int, product: String) -> void:
|
|
consumed += state.product_cost(product)
|
|
|
|
|
|
# Every place a rock can be while the route runs: the bank (the mothership's
|
|
# own store IS state.rocks, so counting the building too would double it), a
|
|
# building's local store, an order that has started and taken its cost, and a
|
|
# transporter's hold in mid-flight.
|
|
func _total() -> int:
|
|
var sum: int = state.rocks + state.fleet_cargo
|
|
for building in state.buildings:
|
|
if building.type != state.BUILDING_MOTHERSHIP:
|
|
sum += state.building_resource(int(building.id), state.RESOURCE_ORE)
|
|
for order in building.get("orders", []):
|
|
if bool(order.started):
|
|
sum += state.product_cost(String(order.product))
|
|
for transporter in state.transporters:
|
|
sum += int(transporter.get("carrying", 0))
|
|
return sum
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
print("TEST_TRANSPORTER_SUPPLY: FAIL (%s)" % message)
|
|
quit(1)
|
|
return true
|