diff --git a/CaptureLabel.gd b/CaptureLabel.gd index c52f8fb..d3f6ef3 100644 --- a/CaptureLabel.gd +++ b/CaptureLabel.gd @@ -5,6 +5,12 @@ extends Label # entirely when the current planet has no capture points (e.g. no setup # resolved for it) rather than showing "0/0". +# A direct script preload, not the bare "PlanetDefinitions" global class_name +# - the same load-order hazard PlanetSurfaceController's setup_host comment +# already documents: the global identifier is only guaranteed registered +# after an editor/import pass, a direct preload never depends on that. +const PLANET_DEFINITIONS = preload("res://PlanetDefinitions.gd") + var _was_conquered: bool = false @@ -18,7 +24,14 @@ func _process(_delta: float) -> void: var conquered: bool = GameState.is_planet_conquered(planet_id) if conquered: - text = "CONQUERED" + # #76: a research_lab's whole reward is invisible otherwise - the + # research point balance lives in a HUD number the player has no + # reason to be watching at the exact moment it changes. Naming the + # reward right on the same flash that already announces conquest + # is the same "explain it where it happens" idiom BuildController + # uses for a refused build. + text = "CONQUERED - +1 RESEARCH POINT" \ + if GameState.planet_definition_id == PLANET_DEFINITIONS.RESEARCH_LAB_ID else "CONQUERED" if not _was_conquered: _flash() else: diff --git a/WorldGen.gd b/WorldGen.gd index b95837d..1fb1c58 100644 --- a/WorldGen.gd +++ b/WorldGen.gd @@ -22,10 +22,12 @@ const GAS_GIANT_THRESHOLD: float = 0.55 # chance independent of biome - not a second smooth region to tune against # the first. const BLACK_HOLE_CHANCE: float = 0.05 -# #63: findable without excessive exploration - research points gate mid/ -# late skill-tree nodes and vessel research, so a player needs to run into -# one of these reasonably often, not just as a rare curiosity. -const RESEARCH_LAB_CHANCE: float = 0.08 +# #63/#74: findable without excessive exploration - research points gate +# mid/late skill-tree nodes and vessel research, so a player needs to run +# into one of these reasonably often, not just as a rare curiosity. Raised +# from 0.08 after playtesting found that rate too scarce for how central +# research points had become to progression. +const RESEARCH_LAB_CHANCE: float = 0.16 # A predictable early gas source for play-testing. The origin chunk is # deterministically normal space. Its explicit in-chunk position puts it diff --git a/tests/test_extractor_building.gd b/tests/test_extractor_building.gd index 656c2c5..002046e 100644 --- a/tests/test_extractor_building.gd +++ b/tests/test_extractor_building.gd @@ -27,7 +27,12 @@ func _initialize() -> void: for x in range(-15, 15): for y in range(-15, 15): var chunk: Vector2i = Vector2i(x, y) - if WorldGen.has_planet_at_chunk(chunk): + # #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 diff --git a/tests/test_planet_conquest_gate.gd b/tests/test_planet_conquest_gate.gd index 93b69ba..5002345 100644 --- a/tests/test_planet_conquest_gate.gd +++ b/tests/test_planet_conquest_gate.gd @@ -29,7 +29,12 @@ func _initialize() -> void: for x in range(-15, 15): for y in range(-15, 15): var chunk: Vector2i = Vector2i(x, y) - if WorldGen.has_planet_at_chunk(chunk): + # #75: this test is about the conquest gate, not resource type - + # skip a research_lab specifically, since it yields nothing and + # is correctly refused regardless of conquest (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 diff --git a/tests/test_research_lab_extractor_gate.gd b/tests/test_research_lab_extractor_gate.gd new file mode 100644 index 0000000..98cf839 --- /dev/null +++ b/tests/test_research_lab_extractor_gate.gd @@ -0,0 +1,114 @@ +extends SceneTree + +# Headless gameplay test: run with +# godot --headless -s res://tests/test_research_lab_extractor_gate.gd +# Forgejo #75: a research_lab planet yields no resources at all (#63) - an +# extractor placed there would sit on an empty resource id forever. The +# build menu must refuse the placement and explain why, the same "explain +# the refusal" idiom used for every other build gate. Mirrors +# test_extractor_building.gd's own chunk-discovery/settle/confirm shape. + +var main_scene: Node +var fleet: Node +var game_state: Node +var controller: Node +var target_chunk: Vector2i +var target_planet_id: String +var stage: int = 0 +var elapsed: float = 0.0 + + +func _initialize() -> void: + var found: bool = false + for x in range(-40, 40): + for y in range(-40, 40): + var chunk: Vector2i = Vector2i(x, y) + if not WorldGen.has_planet_at_chunk(chunk): + continue + if WorldGen.planet_data_at_chunk(chunk).definition_id == "research_lab": + target_chunk = chunk + found = true + break + if found: + break + if not found: + print("TEST_RESEARCH_LAB_EXTRACTOR_GATE: FAIL (no research_lab chunk found 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() + target_planet_id = "chunk:%d:%d" % [target_chunk.x, target_chunk.y] + # This test is about the resource-id gate, not conquest itself (see + # test_planet_conquest_gate) - pre-conquer the target directly. + game_state.register_capture_points(target_planet_id, 1, "research_lab") + game_state.mark_point_captured(target_planet_id, 0) + game_state.reset_progression() + for node in GameState.SKILL_TREE: + game_state.unlocked.append(node.id) + + main_scene = load("res://Main.tscn").instantiate() + root.add_child(main_scene) + fleet = main_scene.get_node("Fleet") + controller = main_scene.get_node("BuildController") + + +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) + + 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): + return _fail("the research_lab planet never spawned") + var planet: Node2D = planet_field.populated_chunks[target_chunk][0] + if String(planet.resource_type) != "": + return _fail("test setup error: chosen planet's resource_type is '%s', expected empty" \ + % planet.resource_type) + 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 controller.build_menu.visible: + return _fail("the build menu did not open over the planet") + + var before: int = game_state.extractors.size() + controller._show_building_info(GameState.BUILDING_EXTRACTOR) + controller._confirm_build() + if game_state.extractors.size() != before: + return _fail("an extractor was built on a research_lab planet") + if not ("nothing to extract" in controller.build_status.text): + return _fail("refusal did not explain itself, status was '%s'" % controller.build_status.text) + print("extractor refused on a research_lab planet, explained: '%s'" % controller.build_status.text) + + print("TEST_RESEARCH_LAB_EXTRACTOR_GATE: PASS") + quit(0) + return true + return false + + +func _fail(message: String) -> bool: + print("TEST_RESEARCH_LAB_EXTRACTOR_GATE: FAIL (%s)" % message) + quit(1) + return true