Randoloot/scripts/levels/exploration_generator.gd
2026-10-17 16:51:07 +02:00

82 lines
3.4 KiB
GDScript

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 asteroid_rng := RandomNumberGenerator.new()
asteroid_rng.seed = int(GameState.asteroid_retry_seeds.get(GameState.system_seed, GameState.system_seed)) ^ 0x41535452
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 point_rng := asteroid_rng if kind == "asteroid" else rng
var candidate := Vector2.ZERO
var found := false
for attempt in 200:
candidate = Vector2(point_rng.randf_range(-2300, 2300), point_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, point_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)