extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_building_stock_queue.gd # Forgejo #16 (part 3 of #12), updated for #23: a building produces out of # its OWN stock, an order it cannot pay for yet is queued rather than # refused, and building now TAKES TIME. # # What #23 changed here: orders no longer complete the instant the ore # lands. The head of the queue consumes its ore, builds for build_time, and # only then finishes - and it finishes into the mothership hangar rather # than straight into the fleet. So a queue is something the player can watch # and cancel, and a full fleet no longer stops a crafter working. # # Verifies: # 1. ordering with an empty stock queues instead of failing, and produces # nothing yet # 2. it does NOT quietly take the cost out of the bank - the whole point is # that the building has to be supplied # 3. delivering enough stock starts the build, and it completes on its own # 4. orders build ONE AT A TIME in order, not all at once the moment a big # delivery covers them # 5. a part-delivery is not enough: stock below the cost leaves the order # queued and the ore untouched # 6. stock and queue survive the space scene being torn down # 7. a build completes with NO fleet in existence, into the hangar, and # waits there - it is not lost, and not silently crewed either var game_state: Node var scene: Node var fleet: Node var field: Node var crafter_id: int = 0 var stage: int = 0 var elapsed: float = 0.0 var ships_at_start: int = 0 var bank_at_start: int = 0 var stored_at_start: int = 0 var ticks: int = 0 const CRAFTER_AT: Vector2 = Vector2(900.0, 0.0) func _initialize() -> void: game_state = root.get_node("GameState") game_state.buildings.clear() game_state.extractors.clear() game_state.stored_vessels.clear() game_state.rocks = 500 game_state.fleet_cargo = 0 # Real build times are 10s+; the timed path is what matters here, not the # wall clock. See GameState.build_time_scale. game_state.build_time_scale = 0.02 game_state.unlocked = ["shipyards", "coordination"] scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) fleet = scene.get_node("Fleet") field = scene.get_node("BuildingField") func _build_time() -> float: return game_state.build_time(GameState.PRODUCT_SHIP) func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) _clear_hazards() fleet.spawn_ships(2, Vector2.ZERO) field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT) crafter_id = int(game_state.buildings[-1].id) ships_at_start = fleet.ships.size() bank_at_start = game_state.rocks elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.2: return false # --- 1 + 2. an unaffordable order queues, and costs nothing -- if not game_state.queue_order(crafter_id): print("TEST_BUILDING_STOCK_QUEUE: FAIL (the crafter refused an order outright)") quit(1) return true elapsed = 0.0 stage = 2 return false 2: if elapsed < 0.3: return false if game_state.pending_orders(crafter_id) != 1: print("TEST_BUILDING_STOCK_QUEUE: FAIL (order did not stay queued: %d pending)" % game_state.pending_orders(crafter_id)) quit(1) return true if not game_state.stored_vessels.is_empty(): print("TEST_BUILDING_STOCK_QUEUE: FAIL (a vessel appeared with an empty stock)") quit(1) return true if game_state.rocks != bank_at_start: print("TEST_BUILDING_STOCK_QUEUE: FAIL (queueing took %d out of the bank - the building is supposed to be supplied)" % (bank_at_start - game_state.rocks)) quit(1) return true print("empty stock -> queued=%d stored=%d bank=%d (untouched)" % [ game_state.pending_orders(crafter_id), game_state.stored_vessels.size(), game_state.rocks ]) # --- 5. a part-delivery is not enough ----------------------- game_state.deposit_to_building(crafter_id, GameState.SHIP_COST - 1) elapsed = 0.0 stage = 3 return false 3: if elapsed < 0.3: return false if game_state.pending_orders(crafter_id) != 1 or not game_state.stored_vessels.is_empty(): print("TEST_BUILDING_STOCK_QUEUE: FAIL (produced on a part-delivery of %d against a cost of %d)" % [ GameState.SHIP_COST - 1, GameState.SHIP_COST ]) quit(1) return true if game_state.building_stock(crafter_id) != GameState.SHIP_COST - 1: print("TEST_BUILDING_STOCK_QUEUE: FAIL (part-delivered ore was consumed anyway)") quit(1) return true print("part delivery (%d of %d) -> still queued, ore intact" % [ game_state.building_stock(crafter_id), GameState.SHIP_COST ]) # --- 3. topping it up starts the build ---------------------- game_state.deposit_to_building(crafter_id, 1) elapsed = 0.0 stage = 4 return false 4: # One tick: the build must have STARTED (ore taken) but not yet # finished, or "takes time" means nothing. ticks += 1 if ticks < 2: return false var queue: Array = game_state.order_list(crafter_id) if queue.is_empty() or not bool(queue[0].started): print("TEST_BUILDING_STOCK_QUEUE: FAIL (a fully funded order never started building)") quit(1) return true if game_state.building_stock(crafter_id) != 0: print("TEST_BUILDING_STOCK_QUEUE: FAIL (starting the build did not consume the ore)") quit(1) return true if not game_state.stored_vessels.is_empty(): print("TEST_BUILDING_STOCK_QUEUE: FAIL (the vessel appeared instantly - builds are supposed to take time)") quit(1) return true print("funded -> building started, ore consumed, nothing delivered yet") elapsed = 0.0 stage = 5 return false 5: if elapsed < _build_time() * 2.0: return false if game_state.pending_orders(crafter_id) != 0: print("TEST_BUILDING_STOCK_QUEUE: FAIL (build never finished)") quit(1) return true if game_state.stored_vessels.size() != 1: print("TEST_BUILDING_STOCK_QUEUE: FAIL (finished build did not reach the hangar: %d stored)" % game_state.stored_vessels.size()) quit(1) return true if fleet.ships.size() != ships_at_start: print("TEST_BUILDING_STOCK_QUEUE: FAIL (the vessel crewed itself - the player is supposed to pick it up)") quit(1) return true print("built -> 1 vessel waiting in the hangar, fleet untouched at %d" % fleet.ships.size()) # --- 4. one at a time, in order ----------------------------- stored_at_start = game_state.stored_vessels.size() game_state.queue_order(crafter_id) game_state.queue_order(crafter_id) game_state.queue_order(crafter_id) game_state.deposit_to_building(crafter_id, GameState.SHIP_COST * 3) elapsed = 0.0 stage = 6 return false 6: # Enough time for exactly one build, and no more. Funding all # three at once must NOT finish all three at once - that is the # behaviour #23 replaced. if elapsed < _build_time() * 1.4: return false var done: int = game_state.stored_vessels.size() - stored_at_start if done != 1: print("TEST_BUILDING_STOCK_QUEUE: FAIL (after one build time, %d of 3 orders finished - they are not building one at a time)" % done) quit(1) return true if game_state.pending_orders(crafter_id) != 2: print("TEST_BUILDING_STOCK_QUEUE: FAIL (%d orders left, expected 2 still queued)" % game_state.pending_orders(crafter_id)) quit(1) return true print("one build time -> 1 of 3 done, 2 still queued (sequential)") elapsed = 0.0 stage = 7 return false 7: if elapsed < _build_time() * 4.0: return false if game_state.stored_vessels.size() - stored_at_start != 3: print("TEST_BUILDING_STOCK_QUEUE: FAIL (the rest of the queue never drained: %d of 3)" % (game_state.stored_vessels.size() - stored_at_start)) quit(1) return true print("queue drained in order -> 3 vessels waiting") # --- 6 + 7. no scene, no fleet ------------------------------ stored_at_start = game_state.stored_vessels.size() scene.queue_free() elapsed = 0.0 stage = 8 return false 8: if elapsed < 0.3: return false if is_instance_valid(fleet): return false game_state.queue_order(crafter_id) game_state.deposit_to_building(crafter_id, GameState.SHIP_COST) elapsed = 0.0 stage = 9 return false 9: if elapsed < _build_time() * 3.0: return false # The queue, the stock and the hangar live in the autoload, so # tearing the space scene down must not touch them. if game_state.pending_orders(crafter_id) != 0: print("TEST_BUILDING_STOCK_QUEUE: FAIL (build stalled once the space scene was gone)") quit(1) return true if game_state.stored_vessels.size() != stored_at_start + 1: print("TEST_BUILDING_STOCK_QUEUE: FAIL (a vessel built with no fleet present was lost)") quit(1) return true print("no scene at all -> build completed into the hangar (%d waiting)" % game_state.stored_vessels.size()) print("TEST_BUILDING_STOCK_QUEUE: PASS") quit(0) return true return false func _clear_hazards() -> void: for a in get_nodes_in_group("asteroids"): if is_instance_valid(a): a.queue_free() for e in get_nodes_in_group("enemies"): if is_instance_valid(e): e.queue_free()