163 lines
7.4 KiB
GDScript3
163 lines
7.4 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless gameplay test: run with
|
||
|
|
# godot --headless -s res://tests/test_build_uses_fleet_hold.gd
|
||
|
|
# A new game starts with an empty bank. Ore is mined into the FLEET HOLD, and
|
||
|
|
# the only way to move it into the bank is to deliver it to a mothership - so
|
||
|
|
# charging construction to the bank alone made the first mothership cost ore
|
||
|
|
# that could not exist yet. Mine forever, build nothing: the game was
|
||
|
|
# unwinnable from its own opening position.
|
||
|
|
#
|
||
|
|
# Construction now draws on the bank AND the hold. Covers:
|
||
|
|
# A. the deadlock premise still holds - a bank-only test rejects a build
|
||
|
|
# the fleet can plainly pay for, so this is guarding something real
|
||
|
|
# B. the pooled test accepts it, and paying takes from the bank first and
|
||
|
|
# the hold only for the shortfall
|
||
|
|
# C. an unaffordable build takes nothing at all (no partial charge)
|
||
|
|
# D. end to end: a fleet holding ore, with an empty bank, actually gets a
|
||
|
|
# mothership built through BuildController
|
||
|
|
# E. research is deliberately NOT pooled - it still spends from the bank
|
||
|
|
|
||
|
|
# Deliberately more than one starting interceptor can carry (its hold is 20,
|
||
|
|
# which is exactly MOTHERSHIP_COST). The capacity is widened where this is
|
||
|
|
# used, so "the hold paid for it" is visible as a remainder rather than as
|
||
|
|
# the hold happening to empty to zero.
|
||
|
|
const HOLD_ORE: int = 40
|
||
|
|
|
||
|
|
var game_state: Node
|
||
|
|
var scene: Node
|
||
|
|
var build_controller: Node
|
||
|
|
var stage: int = 0
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
game_state = root.get_node("GameState")
|
||
|
|
game_state.reset_progression()
|
||
|
|
game_state.buildings.clear()
|
||
|
|
game_state.extractors.clear()
|
||
|
|
|
||
|
|
var cost: int = GameState.MOTHERSHIP_COST
|
||
|
|
|
||
|
|
# --- A. the deadlock, exactly as a fresh game presents it ---------------
|
||
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
||
|
|
game_state.fleet_inventory.total_capacity = HOLD_ORE
|
||
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, HOLD_ORE)
|
||
|
|
if game_state.can_afford(cost):
|
||
|
|
_fail("the bank-only test passed with an empty bank, so it can no longer show the deadlock this fixes")
|
||
|
|
return
|
||
|
|
print("bank 0 + hold %d: bank-only affordability says no (the original deadlock)" % HOLD_ORE)
|
||
|
|
|
||
|
|
# --- B. pooled, and paid bank-first -------------------------------------
|
||
|
|
if not game_state.can_afford_construction(cost):
|
||
|
|
_fail("a fleet holding %d ore still could not afford a %d build" % [HOLD_ORE, cost])
|
||
|
|
return
|
||
|
|
if not game_state.spend_construction(cost):
|
||
|
|
_fail("spend_construction refused a build it had just called affordable")
|
||
|
|
return
|
||
|
|
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 0:
|
||
|
|
_fail("paying from an empty bank somehow changed it")
|
||
|
|
return
|
||
|
|
if game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != HOLD_ORE - cost:
|
||
|
|
_fail("the hold should have paid the whole %d, but holds %d of %d" \
|
||
|
|
% [cost, game_state.fleet_inventory.amount(GameState.RESOURCE_ORE), HOLD_ORE])
|
||
|
|
return
|
||
|
|
print("paid %d entirely from the hold: hold %d -> %d" % [cost, HOLD_ORE, HOLD_ORE - cost])
|
||
|
|
|
||
|
|
# Bank first when there is a bank to draw on: the hold is also the fleet's
|
||
|
|
# mining capacity, so it is spent last.
|
||
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 8)
|
||
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 30)
|
||
|
|
if not game_state.spend_construction(20):
|
||
|
|
_fail("a 20 build was refused with 8 banked and 30 held")
|
||
|
|
return
|
||
|
|
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 0 \
|
||
|
|
or game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != 18:
|
||
|
|
_fail("a split payment should empty the bank first: bank %d, hold %d (expected 0 and 18)" \
|
||
|
|
% [game_state.bank_inventory.amount(GameState.RESOURCE_ORE),
|
||
|
|
game_state.fleet_inventory.amount(GameState.RESOURCE_ORE)])
|
||
|
|
return
|
||
|
|
print("split payment drained the bank first: bank 8 -> 0, hold 30 -> 18")
|
||
|
|
|
||
|
|
# --- C. no partial charge -----------------------------------------------
|
||
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 5)
|
||
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 5)
|
||
|
|
if game_state.spend_construction(50):
|
||
|
|
_fail("a build that cost more than everything owned was allowed")
|
||
|
|
return
|
||
|
|
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 5 \
|
||
|
|
or game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != 5:
|
||
|
|
_fail("a rejected build still took ore: bank %d, hold %d (expected 5 and 5)" \
|
||
|
|
% [game_state.bank_inventory.amount(GameState.RESOURCE_ORE),
|
||
|
|
game_state.fleet_inventory.amount(GameState.RESOURCE_ORE)])
|
||
|
|
return
|
||
|
|
print("a build nobody can afford takes nothing")
|
||
|
|
|
||
|
|
# --- E. research stays banked -------------------------------------------
|
||
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
||
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 500)
|
||
|
|
if game_state.can_afford(1):
|
||
|
|
_fail("research affordability started counting the fleet hold - it is meant to stay banked")
|
||
|
|
return
|
||
|
|
print("research still spends from the bank alone")
|
||
|
|
|
||
|
|
# --- D. end to end, through the real build path --------------------------
|
||
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
||
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, HOLD_ORE)
|
||
|
|
scene = load("res://Main.tscn").instantiate()
|
||
|
|
root.add_child(scene)
|
||
|
|
current_scene = scene
|
||
|
|
|
||
|
|
|
||
|
|
func _process(delta: float) -> bool:
|
||
|
|
elapsed += delta
|
||
|
|
match stage:
|
||
|
|
0:
|
||
|
|
if elapsed < 0.3:
|
||
|
|
return false
|
||
|
|
scene.get_node("AsteroidField").set_process(false)
|
||
|
|
scene.get_node("EnemySpawner").set_process(false)
|
||
|
|
build_controller = scene.get_node("BuildController")
|
||
|
|
|
||
|
|
# Whatever else the scene did while starting up, the point of the
|
||
|
|
# test is a fleet with ore and an empty bank.
|
||
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
||
|
|
# Widened past what a starting interceptor can carry. Its real
|
||
|
|
# hold is 20 and a mothership costs exactly 20, so at the stock
|
||
|
|
# capacity a successful build empties the hold to zero - which is
|
||
|
|
# indistinguishable from the hold having been emptied by anything
|
||
|
|
# else. With room to spare, the remainder is the evidence.
|
||
|
|
game_state.fleet_inventory.total_capacity = HOLD_ORE
|
||
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, HOLD_ORE)
|
||
|
|
var before: int = game_state.buildings.size()
|
||
|
|
|
||
|
|
build_controller.pending_build_type = GameState.BUILDING_MOTHERSHIP
|
||
|
|
build_controller._confirm_build()
|
||
|
|
|
||
|
|
if game_state.buildings.size() != before + 1:
|
||
|
|
return _fail("building a mothership from the fleet's own hold did not place one (%d -> %d): %s" \
|
||
|
|
% [before, game_state.buildings.size(), build_controller.build_status.text])
|
||
|
|
# Checked as a TOTAL, not as the hold alone: a mothership standing
|
||
|
|
# where the fleet is starts taking delivery of the hold on the
|
||
|
|
# very next tick, so ore legitimately moves from hold to bank
|
||
|
|
# straight after the build. What must be true is that exactly the
|
||
|
|
# cost left the pool - and that it left, since the bank was empty
|
||
|
|
# and could not have paid.
|
||
|
|
var total: int = game_state.construction_ore()
|
||
|
|
if total != HOLD_ORE - GameState.MOTHERSHIP_COST:
|
||
|
|
return _fail("the mothership was placed but %d ore remains, expected %d" \
|
||
|
|
% [total, HOLD_ORE - GameState.MOTHERSHIP_COST])
|
||
|
|
print("built a mothership with an empty bank, paid out of the fleet hold (%d ore left, bank %d + hold %d)" \
|
||
|
|
% [total, game_state.bank_inventory.amount(GameState.RESOURCE_ORE),
|
||
|
|
game_state.fleet_inventory.amount(GameState.RESOURCE_ORE)])
|
||
|
|
|
||
|
|
print("TEST_BUILD_USES_FLEET_HOLD: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
return false
|
||
|
|
|
||
|
|
|
||
|
|
func _fail(message: String) -> bool:
|
||
|
|
print("TEST_BUILD_USES_FLEET_HOLD: FAIL (%s)" % message)
|
||
|
|
quit(1)
|
||
|
|
return true
|