spaceship/tests/test_local_costs_use_hold.gd

191 lines
8.3 KiB
GDScript3
Raw Permalink Normal View History

#157, #158, #159: pay from the hold you flew in, and nothing is exempt from coming down #157 was already satisfied - second_hull is quest 7 and extractor is quest 11 - so this pins the property rather than adding a quest: nothing costing more than a single hold may be asked for before the chain has grown the fleet or taught banking. It fails if either is ever moved behind it. #158 - a building's local costs may now be paid out of the fleet's hold. #12 made production cost resources physically DELIVERED rather than money out of the bank, and that stands: the bank is still not raided here. The hold is simply the other place resources physically are, and making the player park, open the transfer menu and move cargo across by hand was ceremony rather than a decision. But only WHERE THE FLEET IS, and that qualifier is the whole design. The first draft had no range on it and test_manual_supply_route caught it immediately: a crafter three thousand units away reached into the hold and spent ore the player had just withdrawn to carry somewhere else - which is exactly the complaint that got automatic banking removed in #24, arriving from the other direction. The range is Building.interact_radius, the same distance that decides whether the transfer button appears, so the rule is not a hidden one: if you could hand the cargo over, the building can take it. #159 - the mothership floored at 1hp, so a battered one sat there reading as dead and never exploded. That exemption existed to prevent a soft-lock, and the soft-lock turns out not to exist: the bank is GameState.bank_inventory and the hangar is stored_vessels, both of which outlive any building; the mothership is the one structure research never gates; it costs exactly what a single interceptor's hold carries; and ore comes from asteroids, which do not run out. So the way back is always mine, fly, rebuild - and that is what test_building_health now asserts, in place of the exemption that used to stand in for it.
2026-08-29 15:51:34 +02:00
extends SceneTree
# Headless test: run with
# godot --headless -s res://tests/test_local_costs_use_hold.gd
# Forgejo #158: "any costs local to a building (creating a ship in the ship
# crafter, researching in the lab...) can also be paid thanks to the ship
# inventory."
#
# #12 made a building's production cost resources physically DELIVERED to it
# rather than money out of the bank, and that principle stands - the bank is
# still not raided from here. What changes is that the fleet's own hold
# counts as well, because it is the one other place resources physically
# are: a hull that flew in carrying gas can hand it over, and making the
# player park, open the transfer menu and move it across by hand was
# ceremony rather than a decision.
#
# Covers:
# A. a crafter with an empty stock builds out of the fleet's hold
# B. the building's OWN stock is spent first - what was hauled in goes
# before the hold the player is still flying around with
# C. a cost split across both is taken from both
# D. the bank is still not touched: banked ore alone does not start an
# order, which is the #12 rule this must not quietly undo
# E. all or nothing - an order that cannot be paid for takes nothing
# F. the same for lab research
# G. and only WHERE THE FLEET IS. A crafter across the map must not reach
# into the hold and spend ore the player withdrew to carry elsewhere -
# which is the same complaint that got automatic banking removed in #24,
# arriving from the other direction. test_manual_supply_route caught the
# first draft of this doing exactly that.
var game_state: Node
var crafter_id: int = 0
var lab_id: int = 0
func _fail(message: String) -> bool:
print("TEST_LOCAL_COSTS_USE_HOLD: FAIL (%s)" % message)
quit(1)
return true
# Emptied rather than replaced: GameState.INVENTORY is reached through the
# autoload's name, which an `extends SceneTree` script cannot compile.
func _reset() -> void:
for resource_type in game_state.resource_types():
var id: String = String(resource_type)
game_state.bank_inventory.set_amount(id, 0)
game_state.fleet_inventory.set_amount(id, 0)
game_state.building_inventory(crafter_id).set_amount(id, 0)
game_state.building_inventory(lab_id).set_amount(id, 0)
game_state.fleet_inventory.total_capacity = 500
for b in game_state.buildings:
b["orders"] = []
b["research_orders"] = []
func _initialize() -> void:
# GameState is an autoload: a bare GameState.<instance member> does not
# compile in a SceneTree script, so the node is fetched by name instead.
game_state = root.get_node("GameState")
game_state.reset_new_game()
game_state.intro_active = false
game_state.reset_quests()
game_state.build_time_scale = 0.02
crafter_id = game_state.add_building(GameState.BUILDING_SHIP_CRAFTER, Vector2.ZERO)
lab_id = game_state.add_building(GameState.BUILDING_SHIP_LAB, Vector2(500.0, 0.0))
# Parked on the crafter. The hold only pays for what the fleet is
# standing at - see G below, which is the half of #158 that matters.
game_state.fleet_position = Vector2.ZERO
var cost: int = int(game_state.product_costs(GameState.VESSEL_INTERCEPTOR).get(
GameState.RESOURCE_ORE, 0))
if cost <= 1:
_fail("an interceptor costs %d ore - nothing here would be measurable" % cost)
return
# --- A. built out of the hold alone ---------------------------------
_reset()
game_state.fleet_inventory.deposit(GameState.RESOURCE_ORE, cost)
if not game_state.queue_order(crafter_id, GameState.VESSEL_INTERCEPTOR):
_fail("the order would not queue")
return
game_state._advance_production(0.01)
var order: Dictionary = game_state.order_list(crafter_id)[0]
if not bool(order.started):
_fail("a crafter with the fleet parked on it and %d ore aboard did not start" % cost)
return
if game_state.fleet_resource(GameState.RESOURCE_ORE) != 0:
_fail("the hold still holds %d after paying" \
% game_state.fleet_resource(GameState.RESOURCE_ORE))
return
print("an empty crafter built out of the fleet's hold (%d ore)" % cost)
# --- B and C. the building's own stock goes first -------------------
_reset()
for b in game_state.buildings:
b["orders"] = []
var in_building: int = cost - 1
game_state.building_inventory(crafter_id).set_amount(GameState.RESOURCE_ORE, in_building)
game_state.fleet_inventory.deposit(GameState.RESOURCE_ORE, 5)
game_state.queue_order(crafter_id, GameState.VESSEL_INTERCEPTOR)
game_state._advance_production(0.01)
if not bool(game_state.order_list(crafter_id)[0].started):
_fail("a cost split across the crafter and the hold did not start")
return
if game_state.building_resource(crafter_id, GameState.RESOURCE_ORE) != 0:
_fail("the crafter's own %d ore was not spent first - %d left" \
% [in_building, game_state.building_resource(crafter_id, GameState.RESOURCE_ORE)])
return
if game_state.fleet_resource(GameState.RESOURCE_ORE) != 4:
_fail("the hold paid %d, expected to cover the last 1" \
% (5 - game_state.fleet_resource(GameState.RESOURCE_ORE)))
return
print("stock first, then the hold: %d from the crafter and 1 from the fleet" % in_building)
# --- D. the bank is still not raided --------------------------------
_reset()
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 5000)
game_state.queue_order(crafter_id, GameState.VESSEL_INTERCEPTOR)
game_state._advance_production(0.01)
if bool(game_state.order_list(crafter_id)[0].started):
_fail("the order started out of the BANK - #12's rule is undone")
return
if game_state.banked_resource(GameState.RESOURCE_ORE) != 5000:
_fail("the bank was charged %d for an order that did not start" \
% (5000 - game_state.banked_resource(GameState.RESOURCE_ORE)))
return
print("banked ore alone does not start an order, and is not touched")
# --- E. all or nothing ----------------------------------------------
_reset()
game_state.fleet_inventory.deposit(GameState.RESOURCE_ORE, cost - 1)
game_state.queue_order(crafter_id, GameState.VESSEL_INTERCEPTOR)
game_state._advance_production(0.01)
if bool(game_state.order_list(crafter_id)[0].started):
_fail("an order started one ore short")
return
if game_state.fleet_resource(GameState.RESOURCE_ORE) != cost - 1:
_fail("an order that could not be paid for still took %d from the hold" \
% (cost - 1 - game_state.fleet_resource(GameState.RESOURCE_ORE)))
return
print("one short takes nothing at all")
# --- F. and the lab does the same -----------------------------------
_reset()
var upgrade: String = "int_damage"
var research: Dictionary = game_state.research_costs(upgrade)
if research.is_empty():
_fail("the upgrade %s costs nothing - F would prove nothing" % upgrade)
return
game_state.research_points = 99
game_state.fleet_position = Vector2(500.0, 0.0)
for resource_type in research:
game_state.fleet_inventory.deposit(String(resource_type), int(research[resource_type]))
if not game_state.queue_lab_research(lab_id, upgrade):
_fail("the research would not queue")
return
game_state._advance_lab_research(0.01)
if not bool(game_state.lab_research_order(lab_id).started):
_fail("research did not start from the fleet's hold")
return
for resource_type in research:
if game_state.fleet_resource(String(resource_type)) != 0:
_fail("the hold still holds %s after paying for research" % resource_type)
return
print("a lab researches out of the hold too (%s)" % str(research))
# --- G. and not from across the map ---------------------------------
_reset()
game_state.fleet_position = Vector2(9000.0, 9000.0)
game_state.fleet_inventory.deposit(GameState.RESOURCE_ORE, cost * 4)
game_state.queue_order(crafter_id, GameState.VESSEL_INTERCEPTOR)
game_state._advance_production(0.01)
if bool(game_state.order_list(crafter_id)[0].started):
_fail("a crafter on the far side of the map started out of the fleet's hold")
return
if game_state.fleet_resource(GameState.RESOURCE_ORE) != cost * 4:
_fail("a distant crafter took %d from the hold" \
% (cost * 4 - game_state.fleet_resource(GameState.RESOURCE_ORE)))
return
# ...and flying back is all it takes.
game_state.fleet_position = Vector2.ZERO
game_state._advance_production(0.01)
if not bool(game_state.order_list(crafter_id)[0].started):
_fail("the order did not start once the fleet was parked on the crafter")
return
print("out of range it takes nothing; parked on it, the same order starts")
print("TEST_LOCAL_COSTS_USE_HOLD: PASS")
quit(0)