extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_extractor_building.gd # Extractors are built from SPACE, over a planet - not from the planet's # surface. Verifies: # 1. the option is greyed out when no planet is in reach, but still # pressable, and explains itself instead of silently failing # 2. it lights up once the fleet is over a planet # 3. its info popup names the planet and offers a confirm, and confirming # charges the cost and registers the extractor against that planet # 4. a second one on the same planet is refused, with no confirm offered var main_scene: Node var fleet: Node var game_state: Node var controller: Node var button: IconButton var popup: Control var target_chunk: Vector2i var stage: int = 0 var elapsed: float = 0.0 func _initialize() -> void: var found: bool = false for x in range(-15, 15): for y in range(-15, 15): var chunk: Vector2i = Vector2i(x, y) # #75: this test is about the planet-proximity/duplicate rules, # not resource type - skip a research_lab specifically, since it # yields nothing and is correctly refused (see # test_research_lab_extractor_gate.gd for that behaviour). if WorldGen.has_planet_at_chunk(chunk) \ and WorldGen.planet_data_at_chunk(chunk).definition_id != "research_lab": target_chunk = chunk found = true break if found: break if not found: print("FAIL: no planet chunk to test with") quit(1) return game_state = root.get_node("GameState") game_state.rocks = 200 game_state.buildings.clear() game_state.extractors.clear() game_state.planet_capture.clear() # This test is about the planet-proximity/duplicate rules, not conquest # (see test_planet_conquest_gate), so pre-conquer the target planet. var target_planet_id: String = "chunk:%d:%d" % [target_chunk.x, target_chunk.y] game_state.register_capture_points(target_planet_id, 1) game_state.mark_point_captured(target_planet_id, 0) # Extractors are gated behind research (see test_skill_tree). This test # is about the planet-proximity rule, so grant the research up front. game_state.reset_progression() for node in GameState.SKILL_TREE: game_state.unlocked.append(node.id) 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") button = main_scene.get_node("UI/BuildMenu/ExtractorButton") popup = main_scene.get_node("UI/BuildMenu/InfoPopup") 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) # Far from any planet: greyed, and pressing it explains why. controller._on_build_pressed() if button.enabled: print("TEST_EXTRACTOR_BUILDING: FAIL (enabled with no planet in reach)") quit(1) return true controller._show_building_info(GameState.BUILDING_EXTRACTOR) var explained: bool = popup.visible and "planet" in popup.get_node("Panel/MessageLabel").text.to_lower() var offered: bool = popup.get_node("Panel/BuildButton").visible print("no planet: greyed=%s popup_shown=%s confirm_offered=%s" % [ not button.enabled, explained, offered ]) if not explained or offered: print("TEST_EXTRACTOR_BUILDING: FAIL (greyed press did not explain itself, or offered a build)") quit(1) return true if not game_state.extractors.is_empty(): print("TEST_EXTRACTOR_BUILDING: FAIL (built one without a planet)") quit(1) return true popup.visible = false # Fly over the planet. var near: Vector2 = WorldGen.chunk_origin(target_chunk) + Vector2.ONE * WorldGen.CHUNK_SIZE * 0.5 for ship in fleet.ships: ship.position = near fleet.move_to(near) elapsed = 0.0 stage = 1 return false 1: var planet_field: Node = main_scene.get_node("PlanetField") if elapsed < planet_field.check_interval + 0.2: return false if not planet_field.populated_chunks.has(target_chunk): print("TEST_EXTRACTOR_BUILDING: FAIL (planet never spawned)") quit(1) return true var planet: Node2D = planet_field.populated_chunks[target_chunk][0] for ship in fleet.ships: ship.position = planet.position fleet.move_to(planet.position) elapsed = 0.0 stage = 2 return false 2: if elapsed < 0.3: return false controller._on_build_pressed() if not button.enabled: print("TEST_EXTRACTOR_BUILDING: FAIL (still greyed while over a planet)") quit(1) return true var rocks_before: int = game_state.rocks controller._show_building_info(GameState.BUILDING_EXTRACTOR) var planet_field2: Node = main_scene.get_node("PlanetField") var planet2: Node2D = planet_field2.populated_chunks[target_chunk][0] # The popup must name the planet it is about to mine, and nothing # may be spent until it is confirmed. var names_planet: bool = planet2.planet_name in popup.get_node("Panel/MessageLabel").text var spent_early: bool = game_state.rocks != rocks_before print("popup: names_planet=%s confirm_offered=%s spent_before_confirm=%s" % [ names_planet, popup.get_node("Panel/BuildButton").visible, spent_early ]) if not names_planet or spent_early or not popup.get_node("Panel/BuildButton").visible: print("TEST_EXTRACTOR_BUILDING: FAIL (popup did not offer an informed build)") quit(1) return true controller._confirm_build() var built: bool = game_state.has_extractor_at(planet2.planet_id) var charged: bool = game_state.rocks == rocks_before - GameState.EXTRACTOR_COST print("over planet '%s': enabled=%s built=%s rocks %d->%d charged=%s" % [ planet2.planet_name, button.enabled, built, rocks_before, game_state.rocks, charged ]) if not (built and charged): print("TEST_EXTRACTOR_BUILDING: FAIL (did not build/charge over a planet)") quit(1) return true # A second one on the same planet must be refused, not charged. var rocks_now: int = game_state.rocks controller._on_build_pressed() controller._show_building_info(GameState.BUILDING_EXTRACTOR) var re_offered: bool = popup.get_node("Panel/BuildButton").visible controller._confirm_build() # must do nothing even if pressed var still_one: bool = game_state.extractors.size() == 1 and not re_offered var not_charged: bool = game_state.rocks == rocks_now print("second attempt: extractors=%d rocks=%d confirm_offered=%s refused=%s" % [ game_state.extractors.size(), game_state.rocks, re_offered, still_one and not_charged ]) if still_one and not_charged: print("TEST_EXTRACTOR_BUILDING: PASS") quit(0) else: print("TEST_EXTRACTOR_BUILDING: FAIL (allowed a duplicate extractor)") quit(1) return true return false