extends RefCounted const POI := preload("res://scripts/levels/exploration_poi.gd") static func spawn(game: Node2D, data: Dictionary, player: CharacterBody2D) -> void: # Snapshot once per system. Learning a rank later must not add wrecks on reload. if not GameState.exploration_wreck_bonuses.has(GameState.system_seed): GameState.exploration_wreck_bonuses[GameState.system_seed] = roundi(GameState.captain.get_skill_bonus("explorer")) var wreck_bonus := int(GameState.exploration_wreck_bonuses[GameState.system_seed]) var rng := RandomNumberGenerator.new() rng.seed = GameState.system_seed ^ 0x504f49 var occupied: Array[Vector2] = [] var wreck_count := rng.randi_range(2, 4) for index in wreck_count + rng.randi_range(6, 10): var kind := "wreck" if index < wreck_count else "asteroid" var candidate := Vector2.ZERO var found := false for attempt in 200: candidate = Vector2(rng.randf_range(-2300, 2300), rng.randf_range(-1500, 1500)) if candidate.length() < 650 or candidate.distance_to(player.position) < 450: continue var clear := true for planet in data["planets"]: if candidate.distance_to(planet["position"]) < float(planet["radius"]) + 220: clear = false for point in occupied: if candidate.distance_to(point) < 180: clear = false if clear: found = true break if found: occupied.append(candidate) _add(game, player, kind, candidate, index, rng.randi()) # Rare per system, at most one ruin on an uninhabited planet. if rng.randf() < 0.08: var candidates: Array[int] = [] for index in data["planets"].size(): if not data["planets"][index]["protected"]: candidates.append(index) if not candidates.is_empty(): var index := candidates[rng.randi_range(0, candidates.size() - 1)] var planet := game.get_node("CelestialBodies/Planet%d" % (index + 1)) _add(game, player, "ruin", planet.position, 100, rng.randi(), float(planet.get("radius")) + 100.0) # Separate stream and IDs preserve the base POIs, asteroid count and ruin chance. var bonus_rng := RandomNumberGenerator.new() bonus_rng.seed = GameState.system_seed ^ 0x4558504c for index in wreck_bonus: for attempt in 1000: var candidate := Vector2(bonus_rng.randf_range(-2300, 2300), bonus_rng.randf_range(-1500, 1500)) if candidate.length() < 650 or candidate.distance_to(player.position) < 450: continue var clear := true for planet in data["planets"]: if candidate.distance_to(planet["position"]) < float(planet["radius"]) + 220: clear = false for point in occupied: if candidate.distance_to(point) < 180: clear = false if clear: occupied.append(candidate) _add(game, player, "wreck", candidate, 1000 + index, bonus_rng.randi()) break static func _add(game: Node2D, player: CharacterBody2D, kind: String, position: Vector2, index: int, reward_seed: int, approach_radius: float = 115.0) -> void: var id := "%d:%d" % [GameState.system_seed, index] if GameState.explored_pois.has(id): return var poi := StaticBody2D.new() poi.set_script(POI) poi.set("kind", kind) poi.set("poi_id", id) poi.set("reward_seed", reward_seed) poi.set("player", player) poi.set("approach_radius", approach_radius) poi.position = position game.add_child(poi)