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