Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel unlocks still charged the bank alone. That split was arbitrary from the player's side - the resources are a couple of taps away from being banked either way, so refusing a purchase over where they happen to be sitting was ceremony. Worse, it was inconsistent: the same ore could buy a mothership and not a skill node. So the pooling moved down into can_afford/spend themselves, and the construction-specific pair is gone. can_afford_resources now pools per resource type, and a new spend_resources() charges bank first and hold for the shortfall, settling affordability in full before taking anything - a multi-resource price must not charge for the half it can cover and then fail. unlock_vessel goes through it instead of withdrawing from the bank directly. The skill menu's "you have %d" quoted the bank, which would have told the player they could not afford something they could. Ammo stays out of it. AimedFleetAbilityController reads the fleet hold directly, and should: a gas bomb is fired from what the ships are physically carrying, which is a fact about where the matter is rather than a convenience about where the money is. test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks every route - skill tree, ship lab, vessel research, multi-resource atomicity, and ammo staying unpooled. Two of its own setup steps were being silently clamped by the fleet hold's capacity, which is what made an affordable price look unaffordable; capacity is not what those checks are about, so it is taken out of the way explicitly. Full suite 106/106. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f5a16a7b95
commit
821da05c9f
5 changed files with 341 additions and 215 deletions
92
GameState.gd
92
GameState.gd
|
|
@ -738,10 +738,8 @@ func unlock_vessel(vessel: String) -> bool:
|
|||
return false
|
||||
var costs: Dictionary = research_costs(definition.unlock_id)
|
||||
var points_needed: int = int(definition.research_point_cost)
|
||||
if not can_afford_resources(costs) or research_points < points_needed:
|
||||
if research_points < points_needed or not spend_resources(costs):
|
||||
return false
|
||||
for resource_type in costs:
|
||||
bank_inventory.withdraw(String(resource_type), int(costs[resource_type]))
|
||||
if points_needed > 0:
|
||||
research_points -= points_needed
|
||||
research_points_changed.emit(research_points)
|
||||
|
|
@ -1800,55 +1798,63 @@ func _arrive(t: Dictionary) -> void:
|
|||
t.progress = 1.0
|
||||
|
||||
|
||||
# --- paying for things ------------------------------------------------------
|
||||
#
|
||||
# EVERY payment draws on the bank AND on what the ships are carrying. The
|
||||
# player can already move resources between the two by hand at any building
|
||||
# that takes them, so refusing a purchase that a couple of taps would have
|
||||
# made affordable was pure ceremony.
|
||||
#
|
||||
# In one case it was worse than ceremony. A fresh game starts with an empty
|
||||
# bank, ore is mined into the fleet hold, and the only way to bank it is to
|
||||
# deliver it to a mothership - so charging construction to the bank alone
|
||||
# made the first mothership cost ore that could not yet exist. Mine forever,
|
||||
# build nothing.
|
||||
#
|
||||
# Bank first, hold second. The hold is also the fleet's mining capacity, so
|
||||
# leaving it as full as possible means the next trip out can still carry
|
||||
# something back.
|
||||
#
|
||||
# The one deliberate exception is AMMO - see AimedFleetAbilityController,
|
||||
# which checks fleet_resource() directly. A gas bomb is fired from what the
|
||||
# ships are physically carrying; that is a fact about where the matter is,
|
||||
# not a convenience about where the money is.
|
||||
|
||||
|
||||
func available_resource(resource_type: String) -> int:
|
||||
return bank_inventory.amount(resource_type) + fleet_inventory.amount(resource_type)
|
||||
|
||||
|
||||
func available_ore() -> int:
|
||||
return available_resource(RESOURCE_ORE)
|
||||
|
||||
|
||||
func can_afford(cost: int) -> bool:
|
||||
return bank_inventory.amount(RESOURCE_ORE) >= cost
|
||||
return available_ore() >= cost
|
||||
|
||||
|
||||
func can_afford_resources(costs: Dictionary) -> bool:
|
||||
for resource_type in costs:
|
||||
if bank_inventory.amount(String(resource_type)) < int(costs[resource_type]):
|
||||
if available_resource(String(resource_type)) < int(costs[resource_type]):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func spend(cost: int) -> bool:
|
||||
if not can_afford(cost):
|
||||
return spend_resources({RESOURCE_ORE: cost})
|
||||
|
||||
|
||||
func spend_resources(costs: Dictionary) -> bool:
|
||||
# Affordability is settled in full BEFORE anything is taken. Charging for
|
||||
# part of a purchase that then fails is worse than refusing it, and with
|
||||
# several resource types in one price that is a real possibility rather
|
||||
# than a theoretical one.
|
||||
if not can_afford_resources(costs):
|
||||
return false
|
||||
bank_inventory.withdraw(RESOURCE_ORE, cost)
|
||||
return true
|
||||
|
||||
|
||||
# --- construction: the bank AND what the ships are carrying -----------------
|
||||
#
|
||||
# A build is put up by the fleet, on the spot, so it can use ore the fleet is
|
||||
# holding as well as ore already banked. That is not a convenience: a fresh
|
||||
# game starts with an empty bank, and the ONLY way to bank ore is to deliver
|
||||
# it to a mothership. Charging construction to the bank alone therefore made
|
||||
# the first mothership cost ore that could not exist yet - the fleet could
|
||||
# mine all it liked and the game was unwinnable.
|
||||
#
|
||||
# Research still spends from the bank alone (can_afford/spend above). That
|
||||
# stays deliberate: hauling ore back to a base to fund science is the loop,
|
||||
# and unlike construction it was never deadlocked, because by the time there
|
||||
# is anything to research there is a mothership to bank into.
|
||||
|
||||
|
||||
func construction_ore() -> int:
|
||||
return bank_inventory.amount(RESOURCE_ORE) + fleet_inventory.amount(RESOURCE_ORE)
|
||||
|
||||
|
||||
func can_afford_construction(cost: int) -> bool:
|
||||
return construction_ore() >= cost
|
||||
|
||||
|
||||
func spend_construction(cost: int) -> bool:
|
||||
# Bank first, hold second. The hold is also the fleet's mining capacity,
|
||||
# so leaving it as full as possible means the next trip out can still
|
||||
# carry something back.
|
||||
if not can_afford_construction(cost):
|
||||
return false
|
||||
var from_bank: int = bank_inventory.withdraw(RESOURCE_ORE, cost)
|
||||
var remaining: int = cost - from_bank
|
||||
if remaining > 0:
|
||||
fleet_inventory.withdraw(RESOURCE_ORE, remaining)
|
||||
for resource_type in costs:
|
||||
var id: String = String(resource_type)
|
||||
var owed: int = int(costs[resource_type])
|
||||
owed -= bank_inventory.withdraw(id, owed)
|
||||
if owed > 0:
|
||||
fleet_inventory.withdraw(id, owed)
|
||||
return true
|
||||
|
|
|
|||
Loading…
Reference in a new issue