#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
163 lines
6.8 KiB
GDScript
163 lines
6.8 KiB
GDScript
extends SceneTree
|
|
|
|
# The dev button is now a doorway rather than a one-off wave shortcut. This
|
|
# exercises the real Main scene so additions and removals prove they affect
|
|
# the live fleet, not only GameState bookkeeping.
|
|
|
|
const VESSEL_DEFINITIONS = preload("res://vessels/VesselDefinitions.gd")
|
|
const BUILDING_DEFINITIONS = preload("res://buildings/BuildingDefinitions.gd")
|
|
|
|
var state: Node
|
|
var scene: Node
|
|
var fleet: Node
|
|
var dev_button: Control
|
|
var menu: Control
|
|
var stage: int = 0
|
|
var elapsed: float = 0.0
|
|
|
|
|
|
func _initialize() -> void:
|
|
state = root.get_node("GameState")
|
|
state.start_dev_game()
|
|
scene = load("res://world/Main.tscn").instantiate()
|
|
root.add_child(scene)
|
|
current_scene = scene
|
|
fleet = scene.get_node("Fleet")
|
|
dev_button = scene.get_node("UI/DevMenuButton")
|
|
menu = scene.get_node("UI/DevMenu")
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
if elapsed < 0.2:
|
|
return false
|
|
if state.intro_active:
|
|
return _fail("the dev world opened inside the intro")
|
|
if fleet.ships.size() != GameState.STARTING_SHIP_COUNT:
|
|
return _fail("post-intro dev mode did not start with the normal fleet")
|
|
dev_button.button.pressed.emit()
|
|
if not menu.visible:
|
|
return _fail("the HUD dev button opened no menu")
|
|
|
|
menu.unlock_button.pressed.emit()
|
|
for skill in GameState.SKILL_TREE:
|
|
if not state.is_unlocked(String(skill.id)):
|
|
return _fail("unlock-all missed %s" % skill.id)
|
|
# #219: capacity is Fleet Offices, not research, so unlocking the
|
|
# whole tree must NOT move it. The sandbox tool is about doctrines.
|
|
if state.max_fleet_size() != GameState.BASE_FLEET_SIZE:
|
|
return _fail("unlock-all moved the fleet cap to %d - capacity is not research any more" \
|
|
% state.max_fleet_size())
|
|
# The berths the rest of this test needs come from offices.
|
|
for berth in range(GameState.DEFAULT_FORMATION.size() - GameState.BASE_FLEET_SIZE):
|
|
state.add_building(GameState.BUILDING_FLEET_OFFICE,
|
|
Vector2(3000.0 + 300.0 * float(berth), 3000.0))
|
|
if state.max_fleet_size() != GameState.DEFAULT_FORMATION.size():
|
|
return _fail("offices for every formation position gave %d berths" \
|
|
% state.max_fleet_size())
|
|
|
|
for definition in VESSEL_DEFINITIONS.fleet():
|
|
menu.fleet_add_requested.emit(String(definition.definition_id))
|
|
for definition in VESSEL_DEFINITIONS.fleet():
|
|
if not fleet.has_vessel_type(String(definition.definition_id)):
|
|
return _fail("the menu could not add %s" % definition.display_name)
|
|
print("dev menu added every fleet-eligible vessel to the live fleet")
|
|
|
|
var before: int = fleet.ships.size()
|
|
menu.fleet_remove_requested.emit(GameState.VESSEL_GAS_BOMBER)
|
|
if fleet.ships.size() != before - 1:
|
|
return _fail("the dev menu did not remove a vessel")
|
|
|
|
menu.unlimited_button.pressed.emit()
|
|
if not state.dev_unlimited_resources:
|
|
return _fail("the unlimited-resources toggle did not turn on")
|
|
for resource_type in state.resource_types():
|
|
state.bank_inventory.withdraw(resource_type, GameState.DEV_UNLIMITED_AMOUNT)
|
|
state.research_points = 0
|
|
state._process(0.0)
|
|
for resource_type in state.resource_types():
|
|
if state.banked_resource(resource_type) != GameState.DEV_UNLIMITED_AMOUNT:
|
|
return _fail("unlimited mode did not replenish %s" % resource_type)
|
|
if state.research_points != GameState.DEV_UNLIMITED_AMOUNT:
|
|
return _fail("unlimited mode did not replenish research points")
|
|
print("skill unlock and unlimited resources operate on persistent GameState")
|
|
|
|
# --- #202: every research and production price reads zero -------
|
|
# Unlimited resources goes off first: a bottomless bank and a free
|
|
# price are indistinguishable while both are on, and it is the
|
|
# price this checks.
|
|
menu.unlimited_button.pressed.emit()
|
|
if state.dev_unlimited_resources:
|
|
return _fail("the unlimited-resources toggle did not turn back off")
|
|
var charged: int = _priced_things()
|
|
if charged == 0:
|
|
return _fail("nothing in the game carries a price to make free")
|
|
menu.free_costs_button.pressed.emit()
|
|
if not state.dev_free_costs:
|
|
return _fail("the free-costs toggle did not turn on")
|
|
if _priced_things() != 0:
|
|
return _fail("%d prices survived the free-costs toggle" % _priced_things())
|
|
# The accessors are what every purchase, queue and price label
|
|
# reads, so an empty bank must now afford anything.
|
|
for resource_type in state.resource_types():
|
|
state.bank_inventory.set_amount(resource_type, 0)
|
|
state.fleet_inventory.set_amount(resource_type, 0)
|
|
state.research_points = 0
|
|
for definition in BUILDING_DEFINITIONS.all():
|
|
if not state.can_afford_resources(state.building_costs(String(definition.definition_id))):
|
|
return _fail("a broke fleet could not afford the free %s" % definition.definition_id)
|
|
print("free costs zeroed every research and production price")
|
|
|
|
menu.free_costs_button.pressed.emit()
|
|
if state.dev_free_costs:
|
|
return _fail("the free-costs toggle did not turn back off")
|
|
if _priced_things() != charged:
|
|
return _fail("turning free costs off did not restore every price")
|
|
print("prices come back exactly as they were when the toggle goes off")
|
|
|
|
var wave_before: int = state.wave_number
|
|
menu.wave_button.pressed.emit()
|
|
if menu.visible:
|
|
return _fail("starting a wave left the dev menu over the action")
|
|
if state.wave_number != wave_before + 1 or not state.wave_pending:
|
|
return _fail("the dev menu did not retain the immediate wave tool")
|
|
|
|
state.reset_new_game()
|
|
if state.dev_mode or state.dev_unlimited_resources or state.dev_free_costs:
|
|
return _fail("dev cheats leaked into a normal new game")
|
|
print("TEST_DEV_MENU: PASS")
|
|
quit(0)
|
|
return true
|
|
return false
|
|
|
|
|
|
# How many prices in the whole game are currently non-zero: skills, vessel
|
|
# unlocks and upgrades on the research side, buildings and vessel production
|
|
# on the other. Counted rather than sampled so a price added later is covered
|
|
# here without anyone remembering to add it.
|
|
func _priced_things() -> int:
|
|
var total: int = 0
|
|
for node in GameState.SKILL_TREE:
|
|
var id: String = String(node.id)
|
|
total += state.skill_costs(id).size() + state.skill_research_point_cost(id)
|
|
for definition in BUILDING_DEFINITIONS.all():
|
|
total += state.building_costs(String(definition.definition_id)).size()
|
|
for definition in VESSEL_DEFINITIONS.all():
|
|
total += state.product_costs(String(definition.definition_id)).size()
|
|
var unlock_id: String = String(definition.unlock_id)
|
|
if unlock_id != "":
|
|
total += state.research_costs(unlock_id).size() \
|
|
+ state.research_point_cost(unlock_id)
|
|
for upgrade in definition.upgrades:
|
|
var upgrade_id: String = String(upgrade.upgrade_id)
|
|
total += state.upgrade_cost(upgrade_id) \
|
|
+ state.research_point_cost(upgrade_id)
|
|
return total
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
print("TEST_DEV_MENU: FAIL (%s)" % message)
|
|
quit(1)
|
|
return true
|