Guide the fleet to the nearest unconquered scan target
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
This commit is contained in:
parent
fd792e8b63
commit
10d19a369b
9 changed files with 311 additions and 38 deletions
|
|
@ -2,14 +2,17 @@ extends Node2D
|
|||
|
||||
# Preview, not a test (underscore prefix keeps it out of run_tests.sh).
|
||||
# Renders the world map with buildings placed and an extractor built, so the
|
||||
# issue #11 building markers and the issue #9 extractor badge can actually be
|
||||
# LOOKED at. --headless draws no pixels, so no headless test can tell whether
|
||||
# a marker is visible - see tests/screenshot.sh.
|
||||
# issue #11 building markers, issue #9 extractor badge, and issue #200 scan
|
||||
# reticle can actually be LOOKED at. --headless draws no pixels, so no
|
||||
# headless test can tell whether a marker is visible - see tests/screenshot.sh.
|
||||
#
|
||||
# bash tests/screenshot.sh res://tests/_marker_preview.tscn /tmp/map.png 20 720x1280
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.reset_new_game()
|
||||
GameState.intro_active = false
|
||||
GameState.reset_quests(false)
|
||||
GameState.buildings.clear()
|
||||
GameState.extractors.clear()
|
||||
GameState.explored_chunks.clear()
|
||||
|
|
@ -18,6 +21,8 @@ func _ready() -> void:
|
|||
GameState.add_building(GameState.BUILDING_MOTHERSHIP, Vector2(900.0, -700.0))
|
||||
GameState.add_building(GameState.BUILDING_SHIP_CRAFTER, Vector2(-1100.0, 300.0))
|
||||
GameState.add_building(GameState.BUILDING_COMMAND_CENTER, Vector2(400.0, 1200.0))
|
||||
GameState.add_building(GameState.BUILDING_PLANET_SCANNER, Vector2(-350.0, -500.0))
|
||||
GameState.scan_for_planet(PlanetDefinitions.GAS_GIANT_ID, Vector2.ZERO)
|
||||
|
||||
# Reveal a patch of map, and put an extractor on whichever planet is
|
||||
# nearest the origin so its badge is in frame too.
|
||||
|
|
@ -42,4 +47,8 @@ func _ready() -> void:
|
|||
# open_map(), not `visible = true` - opening also recentres the view on
|
||||
# the fleet, and without that the map is drawn from its own origin in the
|
||||
# corner of the screen with everything near the fleet cropped off.
|
||||
main.get_node("UI/WorldMap").open_map()
|
||||
var world_map: Control = main.get_node("UI/WorldMap")
|
||||
world_map.open_map()
|
||||
var target: Dictionary = GameState.active_scanner_target()
|
||||
if not target.is_empty():
|
||||
world_map.focus_world_position(Vector2(target.world_position))
|
||||
|
|
|
|||
16
tests/_scanner_guidance_preview.gd
Normal file
16
tests/_scanner_guidance_preview.gd
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
extends Node2D
|
||||
|
||||
# Phone-sized visual fixture for the scanner's persistent flight direction.
|
||||
# bash tests/screenshot.sh res://tests/_scanner_guidance_preview.tscn /tmp/scanner_guidance.png 45 720x1280
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.reset_new_game()
|
||||
GameState.intro_active = false
|
||||
GameState.reset_quests(false)
|
||||
GameState.add_building(GameState.BUILDING_PLANET_SCANNER, Vector2.ZERO)
|
||||
GameState.scan_for_planet(PlanetDefinitions.GAS_GIANT_ID, Vector2.ZERO)
|
||||
var main: Node = load("res://world/Main.tscn").instantiate()
|
||||
add_child(main)
|
||||
main.get_node("EnemySpawner").set_process(false)
|
||||
main.get_node("AsteroidField").set_process(false)
|
||||
6
tests/_scanner_guidance_preview.tscn
Normal file
6
tests/_scanner_guidance_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/_scanner_guidance_preview.gd" id="1"]
|
||||
|
||||
[node name="ScannerGuidancePreview" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
|
|
@ -59,12 +59,29 @@ func _run() -> void:
|
|||
or not state.is_building_unlocked(GameState.BUILDING_PLANET_SCANNER):
|
||||
return _fail("Deep-Space Scanning did not unlock its building")
|
||||
state.add_building(GameState.BUILDING_PLANET_SCANNER, Vector2.ZERO)
|
||||
var explored_before_scan: Dictionary = state.explored_chunks.duplicate()
|
||||
var rocky: Dictionary = state.scan_for_planet(PlanetDefinitions.ROCKY_ID,
|
||||
Vector2.ZERO)
|
||||
if rocky.is_empty() or String(rocky.definition_id) != PlanetDefinitions.ROCKY_ID:
|
||||
return _fail("the scanner did not find a real rocky planet")
|
||||
if explored_before_scan.has(Vector2i(rocky.chunk)):
|
||||
return _fail("the scanner test's first target was already explored")
|
||||
if not state.explored_chunks.has(Vector2i(rocky.chunk)):
|
||||
return _fail("a scan result was not revealed on the persistent map")
|
||||
if String(state.active_scanner_target().planet_id) != String(rocky.planet_id):
|
||||
return _fail("the revealed planet was not retained as navigation target")
|
||||
if not _is_nearest_unconquered(state, rocky, PlanetDefinitions.ROCKY_ID):
|
||||
return _fail("the scanner did not select the physically closest rocky planet")
|
||||
# A conquered nearest match must be skipped on the next scan.
|
||||
state.planet_capture[String(rocky.planet_id)] = {
|
||||
"captured": [], "conquered": true,
|
||||
}
|
||||
var next_rocky: Dictionary = state.scan_for_planet(PlanetDefinitions.ROCKY_ID,
|
||||
Vector2.ZERO)
|
||||
if next_rocky.is_empty() or String(next_rocky.planet_id) == String(rocky.planet_id) \
|
||||
or state.is_planet_conquered(String(next_rocky.planet_id)):
|
||||
return _fail("scanner reused the nearest conquered rocky planet")
|
||||
print("scanner found the nearest unvisited world and skipped it after conquest")
|
||||
if PlanetDefinitions.BOSS_ID in state.scanner_available_planet_types():
|
||||
return _fail("boss signature is available before the four-world survey")
|
||||
|
||||
|
|
@ -83,6 +100,13 @@ func _run() -> void:
|
|||
Vector2.ZERO)
|
||||
if boss.is_empty() or Vector2i(boss.chunk) != WorldGen.OCTOPUS_BIOME_CENTER:
|
||||
return _fail("boss scan did not resolve the unique hostile region")
|
||||
var map_content: Node2D = load("res://world/WorldMapContent.gd").new()
|
||||
var marker: Dictionary = map_content.scanner_target_marker()
|
||||
if marker.is_empty() or String(marker.planet_id) != String(boss.planet_id) \
|
||||
or Vector2(marker.position).distance_to(
|
||||
Vector2(boss.world_position) * map_content.WORLD_TO_LOCAL) > 0.01:
|
||||
return _fail("the active scan has no matching destination point on the map")
|
||||
map_content.free()
|
||||
print("researched scanner reveals generated destinations; the fifth option waits for 4/4")
|
||||
|
||||
# #192: the scanner is taught immediately before the widened survey.
|
||||
|
|
@ -104,10 +128,27 @@ func _run() -> void:
|
|||
root.add_child(host)
|
||||
var fake_fleet := FakeFleet.new()
|
||||
fake_fleet.name = "Fleet"
|
||||
fake_fleet.position = WorldGen.chunk_origin(WorldGen.OCTOPUS_BIOME_CENTER) \
|
||||
+ Vector2(WorldGen.CHUNK_SIZE * 0.5, WorldGen.CHUNK_SIZE * 0.5)
|
||||
fake_fleet.position = Vector2.ZERO
|
||||
fake_fleet.add_to_group("player_fleet")
|
||||
host.add_child(fake_fleet)
|
||||
menu.close_menu()
|
||||
var guidance: Control = load("res://ui/ScannerGuidance.tscn").instantiate()
|
||||
root.add_child(guidance)
|
||||
guidance._process(0.0)
|
||||
if not guidance.visible or not guidance.get_node("TargetLabel").text.contains(
|
||||
String(boss.name).to_upper()):
|
||||
return _fail("the flight HUD did not point toward the scanned destination")
|
||||
var indicator: Control = guidance.get_node("Indicator")
|
||||
var indicator_center: Vector2 = indicator.position + indicator.size * 0.5
|
||||
var viewport_size: Vector2 = guidance.get_viewport_rect().size
|
||||
if indicator_center.x < 20.0 or indicator_center.y < 20.0 \
|
||||
or indicator_center.x > viewport_size.x - 20.0 \
|
||||
or indicator_center.y > viewport_size.y - 20.0:
|
||||
return _fail("scanner direction indicator was placed outside the viewport")
|
||||
guidance.queue_free()
|
||||
|
||||
fake_fleet.position = WorldGen.chunk_origin(WorldGen.OCTOPUS_BIOME_CENTER) \
|
||||
+ Vector2(WorldGen.CHUNK_SIZE * 0.5, WorldGen.CHUNK_SIZE * 0.5)
|
||||
var field: Node2D = load("res://world/OctopusField.gd").new()
|
||||
field.name = "OctopusField"
|
||||
host.add_child(field)
|
||||
|
|
@ -130,3 +171,24 @@ func _run() -> void:
|
|||
func _fail(message: String) -> void:
|
||||
print("FAIL: %s" % message)
|
||||
quit(1)
|
||||
|
||||
|
||||
func _is_nearest_unconquered(state: Node, result: Dictionary,
|
||||
definition_id: String) -> bool:
|
||||
var origin := Vector2.ZERO
|
||||
var result_distance: float = origin.distance_to(Vector2(result.world_position))
|
||||
var radius: int = int(result.distance_chunks) + 2
|
||||
for x in range(-radius, radius + 1):
|
||||
for y in range(-radius, radius + 1):
|
||||
var chunk := Vector2i(x, y)
|
||||
if not WorldGen.has_planet_at_chunk(chunk):
|
||||
continue
|
||||
var data: Dictionary = WorldGen.planet_data_at_chunk(chunk)
|
||||
if String(data.definition_id) != definition_id \
|
||||
or state.is_planet_conquered(String(data.planet_id)):
|
||||
continue
|
||||
var world_position: Vector2 = WorldGen.chunk_origin(chunk) \
|
||||
+ Vector2(data.local_pos)
|
||||
if origin.distance_to(world_position) < result_distance - 0.01:
|
||||
return false
|
||||
return true
|
||||
|
|
|
|||
Loading…
Reference in a new issue