2026-09-03 20:05:33 +02:00
|
|
|
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")
|
2026-09-04 22:34:36 +02:00
|
|
|
const BUILDING_DEFINITIONS = preload("res://buildings/BuildingDefinitions.gd")
|
2026-09-03 20:05:33 +02:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
if state.max_fleet_size() != GameState.DEFAULT_FORMATION.size():
|
|
|
|
|
return _fail("unlock-all did not expose the complete fleet capacity")
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
2026-09-04 22:34:36 +02:00
|
|
|
# --- #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")
|
|
|
|
|
|
2026-09-03 20:05:33 +02:00
|
|
|
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()
|
2026-09-04 22:34:36 +02:00
|
|
|
if state.dev_mode or state.dev_unlimited_resources or state.dev_free_costs:
|
2026-09-03 20:05:33 +02:00
|
|
|
return _fail("dev cheats leaked into a normal new game")
|
|
|
|
|
print("TEST_DEV_MENU: PASS")
|
|
|
|
|
quit(0)
|
|
|
|
|
return true
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
2026-09-04 22:34:36 +02:00
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
|
2026-09-03 20:05:33 +02:00
|
|
|
func _fail(message: String) -> bool:
|
|
|
|
|
print("TEST_DEV_MENU: FAIL (%s)" % message)
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|