extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_mothership_replacement.gd # Only one mothership may exist. Building another replaces it, which moves # where a wiped-out fleet respawns and re-routes every extractor hauler, so # it must be confirmed rather than done silently. Verifies: # 1. the first mothership's info popup carries no destruction warning, and # confirming it builds one # 2. with one already up, the popup DOES warn, and builds nothing until # confirmed # 3. dismissing the popup cancels - confirming afterwards does nothing # 4. confirming replaces it: still exactly one, at the new position # 5. an unaffordable replacement offers no confirm button at all, and # cannot destroy the existing one var main_scene: Node var fleet: Node var game_state: Node var controller: Node var popup: Control var stage: int = 0 var elapsed: float = 0.0 var first_pos: Vector2 = Vector2(600.0, 200.0) var second_pos: Vector2 = Vector2(-900.0, 400.0) func _initialize() -> void: game_state = root.get_node("GameState") game_state.rocks = 200 game_state.buildings.clear() game_state.extractors.clear() main_scene = load("res://world/Main.tscn").instantiate() root.add_child(main_scene) fleet = main_scene.get_node("Fleet") controller = main_scene.get_node("BuildController") popup = main_scene.get_node("UI/BuildMenu/InfoPopup") func _place_fleet(at: Vector2) -> void: for ship in fleet.ships: ship.position = at fleet.move_to(at) func _count_motherships() -> int: var n: int = 0 for b in game_state.buildings: if b.type == GameState.BUILDING_MOTHERSHIP: n += 1 return n func _process(delta: float) -> bool: elapsed += delta match stage: 0: main_scene.get_node("AsteroidField").set_process(false) main_scene.get_node("EnemySpawner").set_process(false) _place_fleet(first_pos) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.2: return false # --- first one: informed, then built ------------------------- controller._on_build_pressed() controller._show_building_info(GameState.BUILDING_MOTHERSHIP) var first_text: String = popup.get_node("Panel/MessageLabel").text.to_lower() var first_warned: bool = "destroy" in first_text print("first: popup=%s warned=%s built_before_confirm=%d" % [ popup.visible, first_warned, _count_motherships() ]) if not popup.visible or first_warned or _count_motherships() != 0: print("TEST_MOTHERSHIP_REPLACEMENT: FAIL (first build warned, or built without confirming)") quit(1) return true controller._confirm_build() if _count_motherships() != 1: print("TEST_MOTHERSHIP_REPLACEMENT: FAIL (confirming the first one did not build it)") quit(1) return true # --- second: warns, builds nothing --------------------------- _place_fleet(second_pos) controller._on_build_pressed() controller._show_building_info(GameState.BUILDING_MOTHERSHIP) var warned: bool = popup.visible and "destroy" in popup.get_node("Panel/MessageLabel").text.to_lower() print("second press: warned=%s motherships=%d" % [warned, _count_motherships()]) if not warned or _count_motherships() != 1: print("TEST_MOTHERSHIP_REPLACEMENT: FAIL (no warning, or built without confirming)") quit(1) return true # --- dismissing cancels -------------------------------------- controller._close_info() controller._confirm_build() print("after cancel, confirm pressed anyway: motherships=%d at %s" % [ _count_motherships(), game_state.get_mothership_position() ]) if _count_motherships() != 1 or game_state.get_mothership_position().distance_to(first_pos) > 60.0: print("TEST_MOTHERSHIP_REPLACEMENT: FAIL (cancel did not drop the pending build)") quit(1) return true # --- confirming replaces ------------------------------------- var rocks_before: int = game_state.rocks controller._show_building_info(GameState.BUILDING_MOTHERSHIP) controller._confirm_build() var only_one: bool = _count_motherships() == 1 var moved: bool = game_state.get_mothership_position().distance_to(second_pos) < 60.0 var charged: bool = game_state.rocks == rocks_before - int(game_state.building_costs(GameState.BUILDING_MOTHERSHIP)[GameState.RESOURCE_ORE]) print("confirmed: motherships=%d moved=%s rocks %d->%d charged=%s" % [ _count_motherships(), moved, rocks_before, game_state.rocks, charged ]) if not (only_one and moved and charged): print("TEST_MOTHERSHIP_REPLACEMENT: FAIL (replacement did not take effect cleanly)") quit(1) return true # --- unaffordable replacement must not destroy the old one --- game_state.rocks = int(game_state.building_costs(GameState.BUILDING_MOTHERSHIP)[GameState.RESOURCE_ORE]) - 1 _place_fleet(Vector2(2500.0, -800.0)) controller._on_build_pressed() controller._show_building_info(GameState.BUILDING_MOTHERSHIP) # With nothing affordable there is no confirm button to press; # pressing it anyway must still do nothing. var offered: bool = popup.get_node("Panel/BuildButton").visible print("unaffordable: confirm offered=%s" % offered) if offered: print("TEST_MOTHERSHIP_REPLACEMENT: FAIL (offered a build it cannot pay for)") quit(1) return true controller._confirm_build() var survived: bool = _count_motherships() == 1 var still_there: bool = game_state.get_mothership_position() != null \ and game_state.get_mothership_position().distance_to(second_pos) < 60.0 print("unaffordable: motherships=%d kept_old_position=%s" % [ _count_motherships(), still_there ]) if survived and still_there: print("TEST_MOTHERSHIP_REPLACEMENT: PASS") quit(0) else: print("TEST_MOTHERSHIP_REPLACEMENT: FAIL (destroyed the old one without affording a new one)") quit(1) return true return false