#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.
This commit is contained in:
parent
2e0c666c2b
commit
4c920f10b0
7 changed files with 397 additions and 39 deletions
|
|
@ -25,6 +25,10 @@ var surviving_vessel_types: PackedStringArray = PackedStringArray()
|
|||
# whenever the composition changes. GameState cannot see scene nodes, and the
|
||||
# census the Planning Center works from has to include the fleet.
|
||||
var fleet_vessel_types: PackedStringArray = PackedStringArray()
|
||||
# Where the fleet is, written by Fleet each frame. Only #158 needs it: a
|
||||
# building may draw on the hold when the hold is actually there, and this
|
||||
# autoload has no other way to know.
|
||||
var fleet_position: Vector2 = Vector2.ZERO
|
||||
# #122: the prologue is running. Set by reset_new_game() so it plays once at
|
||||
# the start of a NEW game and never on a resumed one, and cleared by the
|
||||
# director when the prologue hands over.
|
||||
|
|
@ -693,6 +697,7 @@ func reset_new_game() -> void:
|
|||
surviving_ship_count = STARTING_SHIP_COUNT
|
||||
surviving_vessel_types = PackedStringArray()
|
||||
fleet_vessel_types = PackedStringArray()
|
||||
fleet_position = Vector2.ZERO
|
||||
return_world_position = Vector2.ZERO
|
||||
return_camera_zoom = 1.0
|
||||
planet_name = ""
|
||||
|
|
@ -854,8 +859,23 @@ func building_health_fraction(id: int) -> float:
|
|||
# savings and their stored vessels with no route back, so it bottoms out at
|
||||
# one hit point instead of dying. Keyed on the capability rather than on the
|
||||
# type: whatever building holds the bank is the one that cannot be lost.
|
||||
func building_is_indestructible(building: Dictionary) -> bool:
|
||||
return building_has_capability(building, "bank")
|
||||
# #159: "a building with no life should explode" - and nothing is exempt any
|
||||
# more. The mothership used to floor at 1hp, so a battered one sat there
|
||||
# reading as dead and never came down.
|
||||
#
|
||||
# That exemption was there to stop a soft-lock, and the soft-lock turns out
|
||||
# not to exist. Losing the structure does not lose what is in it: the bank is
|
||||
# GameState.bank_inventory and the hangar is stored_vessels, both of which
|
||||
# outlive any building. And rebuilding is always available - 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.
|
||||
#
|
||||
# Kept as a function rather than deleted because the QUESTION is still a real
|
||||
# one - something may well need to be un-killable later - and a caller asking
|
||||
# it is clearer than a caller that has forgotten it was ever asked.
|
||||
func building_is_indestructible(_building: Dictionary) -> bool:
|
||||
return false
|
||||
|
||||
|
||||
# Returns true when this killed the building.
|
||||
|
|
@ -1489,6 +1509,68 @@ func queue_order(id: int, product: String = PRODUCT_SHIP) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
# What a building can actually pay a local cost with (#158).
|
||||
#
|
||||
# #12 made production cost resources physically DELIVERED to the building
|
||||
# rather than money out of the bank, and that stands: the bank is still not
|
||||
# raided from here. What #158 adds is the fleet's own hold - 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.
|
||||
#
|
||||
# But only WHERE THE FLEET IS, and that qualifier is the whole design.
|
||||
# Without it a crafter on the far side of the map reaches into the hold and
|
||||
# spends ore the player withdrew to carry somewhere else - which is exactly
|
||||
# the complaint that got automatic banking at the mothership removed in #24,
|
||||
# arriving from the other direction. test_manual_supply_route caught it
|
||||
# doing precisely that.
|
||||
#
|
||||
# The range is Building.interact_radius, the same distance that decides
|
||||
# whether the transfer button is offered at all. So the rule is not a hidden
|
||||
# one: if you could hand the cargo over by hand, the building can take it.
|
||||
const LOCAL_PAY_RANGE: float = 150.0
|
||||
|
||||
|
||||
func fleet_is_at(building: Dictionary) -> bool:
|
||||
if building.is_empty():
|
||||
return false
|
||||
return fleet_position.distance_to(building.position) <= LOCAL_PAY_RANGE
|
||||
|
||||
|
||||
func local_payment_available(building: Dictionary, inventory: RefCounted,
|
||||
resource_type: String) -> int:
|
||||
var here: int = 0 if inventory == null else inventory.amount(resource_type)
|
||||
return here + (fleet_inventory.amount(resource_type) if fleet_is_at(building) else 0)
|
||||
|
||||
|
||||
func can_pay_locally(building: Dictionary, inventory: RefCounted,
|
||||
costs: Dictionary) -> bool:
|
||||
for resource_type in costs:
|
||||
if local_payment_available(building, inventory, String(resource_type)) \
|
||||
< int(costs[resource_type]):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
# All or nothing, settled in full before anything is taken - a part-charged
|
||||
# order that then fails is worse than one that never started. The building's
|
||||
# own stock goes first, so what was hauled in is spent before the hold the
|
||||
# player is still flying around with.
|
||||
func pay_locally(building: Dictionary, inventory: RefCounted,
|
||||
costs: Dictionary) -> bool:
|
||||
if not can_pay_locally(building, inventory, costs):
|
||||
return false
|
||||
var at_hand: bool = fleet_is_at(building)
|
||||
for resource_type in costs:
|
||||
var id: String = String(resource_type)
|
||||
var owed: int = int(costs[resource_type])
|
||||
if inventory != null:
|
||||
owed -= inventory.withdraw(id, owed)
|
||||
if owed > 0 and at_hand:
|
||||
fleet_inventory.withdraw(id, owed)
|
||||
return true
|
||||
|
||||
|
||||
func _advance_production(delta: float) -> void:
|
||||
for b in buildings:
|
||||
if not can_produce(b.type):
|
||||
|
|
@ -1508,15 +1590,9 @@ func _advance_production(delta: float) -> void:
|
|||
var definition: Resource = vessel_definition(product)
|
||||
|
||||
if not bool(order.started):
|
||||
var affordable := true
|
||||
for resource_type in costs:
|
||||
if inventory.amount(String(resource_type)) < int(costs[resource_type]):
|
||||
affordable = false
|
||||
break
|
||||
if not affordable:
|
||||
# #158: the crafter's own stock, or the hold that flew in.
|
||||
if not pay_locally(b, inventory, costs):
|
||||
continue
|
||||
for resource_type in costs:
|
||||
inventory.withdraw(String(resource_type), int(costs[resource_type]))
|
||||
order.started = true
|
||||
|
||||
order.elapsed = float(order.elapsed) + delta
|
||||
|
|
@ -2038,11 +2114,8 @@ func _advance_lab_research(delta: float) -> void:
|
|||
var costs: Dictionary = research_costs(id)
|
||||
var inventory: RefCounted = _inventory_for_record(lab)
|
||||
if not bool(order.started):
|
||||
var affordable := true
|
||||
for resource_type in costs:
|
||||
if inventory.amount(String(resource_type)) < int(costs[resource_type]):
|
||||
affordable = false
|
||||
break
|
||||
# #158: the lab's own stock, or the hold that flew in.
|
||||
var affordable: bool = can_pay_locally(lab, inventory, costs)
|
||||
# #63: research points are a fleet-wide currency, not delivered
|
||||
# to the lab like ore/gas - checked and spent here too so a
|
||||
# shortfall stalls the order the same way an undelivered
|
||||
|
|
@ -2052,8 +2125,8 @@ func _advance_lab_research(delta: float) -> void:
|
|||
affordable = false
|
||||
if not affordable:
|
||||
continue
|
||||
for resource_type in costs:
|
||||
inventory.withdraw(String(resource_type), int(costs[resource_type]))
|
||||
if not pay_locally(lab, inventory, costs):
|
||||
continue
|
||||
if points_needed > 0:
|
||||
research_points -= points_needed
|
||||
research_points_changed.emit(research_points)
|
||||
|
|
|
|||
Loading…
Reference in a new issue