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
|
|
@ -202,6 +202,9 @@ var dev_mode: bool = false:
|
|||
# every scene swap) so exploration progress isn't lost every time the
|
||||
# player lands on and leaves a planet.
|
||||
var explored_chunks: Dictionary = {}
|
||||
# The scanner's latest unconquered destination. Unlike explored_chunks this
|
||||
# is one intentional navigation target, shared by the map and flight HUD.
|
||||
var scanner_target: Dictionary = {}
|
||||
|
||||
# The four ordinary destinations teach the scanner enough signatures to
|
||||
# identify the hostile boss world. Kept explicit so adding a sixth authored
|
||||
|
|
@ -791,6 +794,7 @@ func reset_new_game() -> void:
|
|||
dev_unlimited_resources = false
|
||||
dev_mode = false
|
||||
explored_chunks.clear()
|
||||
scanner_target.clear()
|
||||
bank_inventory = INVENTORY.new()
|
||||
fleet_inventory = INVENTORY.new()
|
||||
buildings.clear()
|
||||
|
|
@ -822,56 +826,95 @@ func scanner_available_planet_types() -> PackedStringArray:
|
|||
return available
|
||||
|
||||
|
||||
# Finds the nearest generated planet of this type, reveals its neighbourhood
|
||||
# on the persistent map, and returns the same identity PlanetField will spawn.
|
||||
# The search is ring-shaped, so the first hit is nearest in chunk distance and
|
||||
# results do not depend on dictionary iteration order.
|
||||
# Finds the nearest unconquered generated planet of this type, reveals its
|
||||
# neighbourhood on the persistent map, and retains it as the flight target.
|
||||
# Rings bound the work, but candidates are compared by their actual generated
|
||||
# world position: the first planet in the first matching chunk ring is not
|
||||
# necessarily the physically closest one when local positions differ.
|
||||
func scan_for_planet(definition_id: String, origin_world_position: Vector2) -> Dictionary:
|
||||
if not structure_exists(BUILDING_PLANET_SCANNER) \
|
||||
or definition_id not in scanner_available_planet_types():
|
||||
return {}
|
||||
var origin: Vector2i = WORLD_GEN.chunk_coords(origin_world_position)
|
||||
var best: Dictionary = {}
|
||||
var best_distance: float = INF
|
||||
for radius in range(0, 257):
|
||||
if radius == 0:
|
||||
var center_match: Dictionary = _scanner_match_at(origin,
|
||||
var center: Dictionary = _scanner_candidate_at(origin,
|
||||
definition_id, radius)
|
||||
if not center_match.is_empty():
|
||||
return center_match
|
||||
continue
|
||||
# Only the perimeter is new at each radius. The old square-with-a-
|
||||
# condition loop revisited every inner chunk for every radius, turning
|
||||
# a wide scan cubic precisely when the phone had the most work to do.
|
||||
for x in range(-radius, radius + 1):
|
||||
for y in [-radius, radius]:
|
||||
var horizontal: Dictionary = _scanner_match_at(
|
||||
origin + Vector2i(x, y), definition_id, radius)
|
||||
if not horizontal.is_empty():
|
||||
return horizontal
|
||||
for y in range(-radius + 1, radius):
|
||||
for x in [-radius, radius]:
|
||||
var vertical: Dictionary = _scanner_match_at(
|
||||
origin + Vector2i(x, y), definition_id, radius)
|
||||
if not vertical.is_empty():
|
||||
return vertical
|
||||
return {}
|
||||
if not center.is_empty():
|
||||
best = center
|
||||
best_distance = origin_world_position.distance_to(
|
||||
Vector2(center.world_position))
|
||||
else:
|
||||
# Only the perimeter is new at each radius. Every matching candidate
|
||||
# on it is considered before deciding whether the search can stop.
|
||||
for x in range(-radius, radius + 1):
|
||||
for y in [-radius, radius]:
|
||||
var horizontal: Dictionary = _scanner_candidate_at(
|
||||
origin + Vector2i(x, y), definition_id, radius)
|
||||
var horizontal_distance: float = INF if horizontal.is_empty() else \
|
||||
origin_world_position.distance_to(Vector2(horizontal.world_position))
|
||||
if horizontal_distance < best_distance:
|
||||
best = horizontal
|
||||
best_distance = horizontal_distance
|
||||
for y in range(-radius + 1, radius):
|
||||
for x in [-radius, radius]:
|
||||
var vertical: Dictionary = _scanner_candidate_at(
|
||||
origin + Vector2i(x, y), definition_id, radius)
|
||||
var vertical_distance: float = INF if vertical.is_empty() else \
|
||||
origin_world_position.distance_to(Vector2(vertical.world_position))
|
||||
if vertical_distance < best_distance:
|
||||
best = vertical
|
||||
best_distance = vertical_distance
|
||||
if not best.is_empty() and best_distance <= _scanner_unsearched_distance(
|
||||
origin, origin_world_position, radius):
|
||||
return _accept_scanner_target(best)
|
||||
return _accept_scanner_target(best) if not best.is_empty() else {}
|
||||
|
||||
|
||||
func _scanner_match_at(chunk: Vector2i, definition_id: String,
|
||||
func _scanner_candidate_at(chunk: Vector2i, definition_id: String,
|
||||
distance_chunks: int) -> Dictionary:
|
||||
if not WORLD_GEN.has_planet_at_chunk(chunk):
|
||||
return {}
|
||||
var data: Dictionary = WORLD_GEN.planet_data_at_chunk(chunk)
|
||||
if String(data.get("definition_id", "")) != definition_id:
|
||||
if String(data.get("definition_id", "")) != definition_id \
|
||||
or is_planet_conquered(String(data.get("planet_id", ""))):
|
||||
return {}
|
||||
for reveal_y in range(-1, 2):
|
||||
for reveal_x in range(-1, 2):
|
||||
explored_chunks[chunk + Vector2i(reveal_x, reveal_y)] = true
|
||||
data["chunk"] = chunk
|
||||
data["world_position"] = WORLD_GEN.chunk_origin(chunk) + Vector2(data.local_pos)
|
||||
data["distance_chunks"] = distance_chunks
|
||||
return data
|
||||
|
||||
|
||||
func _scanner_unsearched_distance(origin_chunk: Vector2i,
|
||||
origin_world_position: Vector2, radius: int) -> float:
|
||||
var top_left: Vector2 = WORLD_GEN.chunk_origin(
|
||||
origin_chunk - Vector2i(radius, radius))
|
||||
var bottom_right: Vector2 = WORLD_GEN.chunk_origin(
|
||||
origin_chunk + Vector2i(radius + 1, radius + 1))
|
||||
return minf(minf(origin_world_position.x - top_left.x,
|
||||
bottom_right.x - origin_world_position.x), minf(
|
||||
origin_world_position.y - top_left.y,
|
||||
bottom_right.y - origin_world_position.y))
|
||||
|
||||
|
||||
func _accept_scanner_target(data: Dictionary) -> Dictionary:
|
||||
var chunk := Vector2i(data.chunk)
|
||||
for reveal_y in range(-1, 2):
|
||||
for reveal_x in range(-1, 2):
|
||||
explored_chunks[chunk + Vector2i(reveal_x, reveal_y)] = true
|
||||
scanner_target = data.duplicate(true)
|
||||
return data
|
||||
|
||||
|
||||
func active_scanner_target() -> Dictionary:
|
||||
if scanner_target.is_empty() \
|
||||
or is_planet_conquered(String(scanner_target.get("planet_id", ""))):
|
||||
return {}
|
||||
return scanner_target
|
||||
|
||||
|
||||
# Amounts a dev game opens with: enough of everything to reach any part of
|
||||
# the game immediately, without being so large that costs stop registering.
|
||||
const DEV_RESOURCE_AMOUNT: int = 2000
|
||||
|
|
|
|||
Loading…
Reference in a new issue