#219: fleet capacity is a built, supplied thing rather than a researched one. The eight fleet-size doctrines are gone; capacity is one berth free plus one per Fleet Office standing. An office charges every 25s for the vessel in its berth, priced off that vessel's own build cost so an expensive hull is expensive to keep, and drawn from its own store the way a gas burner draws gas. An office that cannot pay goes dark, its vessel is scuttled, and the player is told through the same queue quest lines use - which reaches them on a planet surface too. An empty berth is idle rather than starved: an office is only failing while there is something in it to fail, and holding it disabled would leave a berth that could never be crewed and so could never be paid for again. Ships have no hit points and a shield absorbs a hit wholesale, so the lost vessel is scuttled rather than damaged - otherwise a shielded hull would survive its own eviction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
256 lines
9.2 KiB
GDScript
256 lines
9.2 KiB
GDScript
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()
|