extends RefCounted ## The network belongs to expedition progress, so it survives visits and system travel. const PARTS_PER_RICHNESS := 10 const CREDITS_PER_MANAGEMENT := 1000 const DEPOSIT_CHANCE := 0.60 const CREW_CHANCE := 0.40 const CABINS := ["cabin_1", "cabin_2", "cabin_3", "cabin_4", "cabin_5"] const FIRST_NAMES := ["Ada", "Mira", "Jan", "Leon", "Iga", "Oskar", "Nina", "Tomasz", "Sara", "Emil"] const LAST_NAMES := ["Nowak", "Voss", "Kowalski", "Orion", "Lis", "Ward", "Keller", "Wilk", "Stern", "Vale"] static func planet_roll(profile: Dictionary, salt: int) -> RandomNumberGenerator: var rng := RandomNumberGenerator.new() rng.seed = int(profile.get("seed", 0)) ^ str(profile.get("id", "planet")).hash() ^ salt return rng static func has_deep_deposit(profile: Dictionary) -> bool: return planet_roll(profile, 0x44454550).randf() < DEPOSIT_CHANCE # Strict level bands prevent early systems from rolling endgame deposits. static func richness_bounds(system: int) -> Vector2i: var levels := [1, 20, 40, 60, 80, 100, 120] var lows := [1, 8, 20, 35, 50, 65, 80] var highs := [10, 25, 40, 55, 70, 85, 100] var level := clampi(system, 1, 120) for index in range(1, levels.size()): if level <= int(levels[index]): var weight := float(level - int(levels[index-1])) / float(int(levels[index]) - int(levels[index-1])) return Vector2i(roundi(lerpf(lows[index-1], lows[index], weight)), roundi(lerpf(highs[index-1], highs[index], weight))) return Vector2i(80, 100) static func deposit_richness(profile: Dictionary, system: int) -> int: var bounds := richness_bounds(system) return planet_roll(profile, 0x52494348).randi_range(bounds.x, bounds.y) static func build_cost(profile: Dictionary, system: int) -> int: return deposit_richness(profile, system) * PARTS_PER_RICHNESS static func recruitment_cost(candidate: Dictionary) -> int: return clampi(int(candidate.get("management", 1)), 1, 100) * CREDITS_PER_MANAGEMENT static func station_richness(station: Dictionary) -> int: if not station.has("richness"): # Upgrade existing stations using their original planet identity and system. var identity := str(station.id).split(":") var profile := {"id":str(station.id), "seed":int(station.get("planet_seed", 0))} if identity.size() == 3 and identity[1] == "planet": profile = preload("res://scripts/planets/planet_profiles.gd").generate(int(identity[0]), int(identity[2]), str(station.planet_name)) station.richness = deposit_richness(profile, int(station.get("system", 1))) return clampi(int(station.richness), 1, 100) static func has_recruit(profile: Dictionary) -> bool: return planet_roll(profile, 0x43524557).randf() < CREW_CHANCE static func recruit(profile: Dictionary) -> Dictionary: var rng := planet_roll(profile, 0x50455253) var rating := clampi(1 + roundi(clampf(float(profile.get("difficulty", 0)), 0, 1) * 80) + rng.randi_range(0, 19), 1, 100) return {"id":str(profile.id)+":crew", "name":FIRST_NAMES[rng.randi_range(0,9)]+" "+LAST_NAMES[rng.randi_range(0,9)], "management":rating, "planet_id":str(profile.id), "planet_name":str(profile.name), "cabin":""} static func free_cabin(progress: Resource) -> String: for cabin in CABINS: if progress.freighter_rooms.get(cabin, "") != "crew_cabin": continue if cabin_crew(progress, cabin).is_empty(): return cabin return "" static func cabin_crew(progress: Resource, cabin: String) -> Dictionary: for member in progress.crew.values(): if str(member.cabin) == cabin: return member return {} static func hire(progress: Resource, captain: Resource, profile: Dictionary) -> bool: var candidate := recruit(profile) var cabin := free_cabin(progress) var cost := recruitment_cost(candidate) if not has_recruit(profile) or progress.recruited_planets.has(str(profile.id)) or cabin.is_empty() or captain.credits < cost: return false captain.credits -= cost candidate.cabin = cabin progress.crew[candidate.id] = candidate progress.recruited_planets[str(profile.id)] = true captain.emit_changed() progress.emit_changed() return true static func dismiss(progress: Resource, member_id: String) -> bool: if not progress.crew.has(member_id): return false for station in progress.mining_stations.values(): if str(station.get("crew_id", "")) == member_id: station.crew_id = "" progress.crew.erase(member_id) progress.emit_changed() return true static func build(progress: Resource, captain: Resource, profile: Dictionary, system: int) -> bool: var id := str(profile.id) var cost := build_cost(profile, system) if not has_deep_deposit(profile) or progress.mining_stations.has(id) or captain.parts < cost: return false captain.parts -= cost progress.mining_stations[id] = {"id":id, "planet_name":str(profile.name), "system":system, "planet_seed":int(profile.get("seed", 0)), "richness":deposit_richness(profile, system), "crew_id":"", "produced":0.0} captain.emit_changed() progress.emit_changed() return true static func assign(progress: Resource, station_id: String, member_id: String) -> bool: if not progress.mining_stations.has(station_id): return false if not member_id.is_empty() and not progress.crew.has(member_id): return false if not member_id.is_empty(): for station in progress.mining_stations.values(): if str(station.get("crew_id", "")) == member_id: station.crew_id = "" progress.mining_stations[station_id].crew_id = member_id progress.emit_changed() return true static func station_rate(progress: Resource, station: Dictionary) -> float: var id := str(station.get("crew_id", "")) if not progress.crew.has(id): return 0.0 return station_richness(station) * (1.0 + clampi(int(progress.crew[id].management), 1, 100) / 100.0) static func total_rate(progress: Resource) -> float: var total := 0.0 for station in progress.mining_stations.values(): total += station_rate(progress, station) return total static func advance(progress: Resource, seconds: float) -> float: if seconds <= 0: return 0 var gained := 0.0 for station in progress.mining_stations.values(): var amount := station_rate(progress, station) * seconds / 60.0 station.produced = float(station.get("produced", 0)) + amount gained += amount progress.deuter += gained return gained