extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_fleet_cargo.gd # Forgejo #14 (part 1 of #12): mined ore is CARGO the fleet is carrying, not # instant global income. It only becomes spendable once the fleet flies it # back to the mothership. # # Before this, Rock.gd did `GameState.rocks += 1` on pickup, so ore was # banked instantly from anywhere on the map and a mining run had nothing to # lose. # # Verifies: # 1. mining fills the hold and leaves the bank alone # 2. unloading at the mothership banks the lot and empties the hold - but # only when the player asks. Arriving used to do it by itself; #24 # removed that, so this now checks BOTH halves: parking changes # nothing, and the explicit transfer moves everything # 3. with no mothership there is nowhere to deliver, so cargo just # accumulates - it is never quietly banked anyway # 4. sitting on a DIFFERENT building is not a delivery # 5. ore is conserved: nothing is created or destroyed in the transfer # 6. cargo survives the trip to a planet's surface and back, which is # exactly the trip a mining run is var game_state: Node var scene: Node var fleet: Node var field: Node var stage: int = 0 var elapsed: float = 0.0 var banked_before: int = 0 var mothership_id: int = 0 const MOTHERSHIP_AT: Vector2 = Vector2(1200.0, 0.0) const CRAFTER_AT: Vector2 = Vector2(-4000.0, 0.0) func _initialize() -> void: game_state = root.get_node("GameState") game_state.buildings.clear() game_state.extractors.clear() game_state.rocks = 100 game_state.fleet_cargo = 0 scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) fleet = scene.get_node("Fleet") field = scene.get_node("BuildingField") func _park_fleet_on(at: Vector2) -> void: for ship in fleet.ships: ship.position = at fleet.move_to(at) func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) # --- 1. mining fills the hold, not the bank ----------------- # Through a REAL Rock, flown over by a real ship, rather than by # calling collect_cargo() directly: the whole change is which # pool Rock.gd credits on pickup, and a test that calls the new # API by hand passes just as happily with Rock.gd left untouched. banked_before = game_state.rocks var rock: Area2D = load("res://surface/Rock.tscn").instantiate() rock.build_shape(1) rock.global_position = fleet.ships[0].global_position scene.add_child(rock) elapsed = 0.0 stage = 5 return false 5: # A tick for the Area2D overlap to register. if elapsed < 0.25: return false if game_state.fleet_cargo != 1: print("TEST_FLEET_CARGO: FAIL (flying over a rock put %d in the hold)" % game_state.fleet_cargo) quit(1) return true if game_state.rocks != banked_before: print("TEST_FLEET_CARGO: FAIL (a mined rock was banked instantly: %d -> %d)" % [ banked_before, game_state.rocks ]) quit(1) return true print("flew over a real rock -> hold=%d bank=%d (unchanged)" % [ game_state.fleet_cargo, game_state.rocks ]) game_state.collect_cargo(1) game_state.collect_cargo(1) if game_state.fleet_cargo != 3: print("TEST_FLEET_CARGO: FAIL (mining did not fill the hold: %d)" % game_state.fleet_cargo) quit(1) return true if game_state.rocks != banked_before: print("TEST_FLEET_CARGO: FAIL (mining banked %d straight away)" % (game_state.rocks - banked_before)) quit(1) return true print("mined 3 -> hold=%d bank=%d (unchanged)" % [game_state.fleet_cargo, game_state.rocks]) # --- 3. no mothership means nowhere to deliver -------------- _park_fleet_on(Vector2.ZERO) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.4: return false if game_state.fleet_cargo != 3 or game_state.rocks != banked_before: print("TEST_FLEET_CARGO: FAIL (delivered with no mothership built)") quit(1) return true print("no mothership -> hold still %d, bank still %d" % [game_state.fleet_cargo, game_state.rocks]) # --- 4. a different building is not a delivery point -------- field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT) field.place(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT) mothership_id = int(game_state.buildings[-1].id) _park_fleet_on(CRAFTER_AT) elapsed = 0.0 stage = 2 return false 2: if elapsed < 0.4: return false if game_state.fleet_cargo != 3: print("TEST_FLEET_CARGO: FAIL (the ship crafter accepted a delivery)") quit(1) return true print("parked on the crafter -> hold still %d" % game_state.fleet_cargo) # --- 2. the mothership banks it ----------------------------- _park_fleet_on(MOTHERSHIP_AT) elapsed = 0.0 stage = 3 return false 3: if elapsed < 0.4: return false var total_before: int = banked_before + 3 # Parking alone must do nothing now (#24). if game_state.fleet_cargo != 3 or game_state.rocks != banked_before: print("TEST_FLEET_CARGO: FAIL (arriving at the mothership banked the hold by itself)") quit(1) return true print("parked on the mothership -> hold still %d (no auto-banking)" % game_state.fleet_cargo) game_state.transfer_to_building(mothership_id, 999) print("delivered: hold=%d bank=%d (expected hold=0 bank=%d)" % [ game_state.fleet_cargo, game_state.rocks, total_before ]) if game_state.fleet_cargo != 0: print("TEST_FLEET_CARGO: FAIL (hold not emptied on delivery)") quit(1) return true # --- 5. conservation ---------------------------------------- if game_state.rocks != total_before: print("TEST_FLEET_CARGO: FAIL (ore was created or destroyed in the transfer)") quit(1) return true # --- 6. cargo survives the scene swap ----------------------- # Not simulated by loading PlanetSurface.tscn here: the point is # that cargo lives in the autoload rather than in Main.tscn, so # tearing Main.tscn down must not touch it. game_state.collect_cargo(5) scene.queue_free() elapsed = 0.0 stage = 4 return false 4: if elapsed < 0.3: return false if game_state.fleet_cargo != 5: print("TEST_FLEET_CARGO: FAIL (cargo lost when the space scene was torn down: %d)" % game_state.fleet_cargo) quit(1) return true print("cargo survived the scene teardown: hold=%d" % game_state.fleet_cargo) print("TEST_FLEET_CARGO: PASS") quit(0) return true return false