import type { CelestialSnapshot, ShipSnapshot, SystemSnapshot } from "./contracts"; import type { CameraMode, OrbitalAnchor, Selectable, SelectionGroup, WorldState, } from "./viewerTypes"; import { formatGalaxyDistance } from "./viewerMath"; import { getShipBehaviorLabel, getShipOrderLabel } from "./shipAutomationPresentation"; export function describeSelectable(world: WorldState | undefined, item: Selectable): string { if (!world) { return item.kind; } if (item.kind === "ship") { return world.ships.get(item.id)?.name ?? item.id; } if (item.kind === "station") { return world.stations.get(item.id)?.label ?? item.id; } if (item.kind === "node") { return item.id; } if (item.kind === "celestial") { return `${world.celestials.get(item.id)?.kind ?? "celestial"} ${item.id}`; } if (item.kind === "claim") { return `claim ${item.id}`; } if (item.kind === "construction-site") { return `construction ${item.id}`; } if (item.kind === "planet") { return world.systems.get(item.systemId)?.planets[item.planetIndex]?.label ?? `${item.systemId}:${item.planetIndex}`; } if (item.kind === "moon") { const planet = world.systems.get(item.systemId)?.planets[item.planetIndex]; return planet?.moons[item.moonIndex]?.label ?? `moon ${item.moonIndex + 1}`; } return world.systems.get(item.id)?.label ?? item.id; } export function describeHoverLabel(world: WorldState | undefined, item: Selectable): string | undefined { if (!world) { return undefined; } if (item.kind === "ship") { const ship = world.ships.get(item.id); if (!ship) { return item.id; } const lines = [ ship.name, `Behavior ${describeShipBehavior(ship)}`, `State ${describeShipState(world, ship)}`, `Order ${describeShipOrder(ship)}`, ]; return lines.join("\n"); } if (item.kind === "station") { return world.stations.get(item.id)?.label ?? item.id; } if (item.kind === "system") { const system = world.systems.get(item.id); if (!system) { return item.id; } const starLabel = system.stars.length > 1 ? `${system.stars.length}× ${system.stars[0]?.kind}` : (system.stars[0]?.kind ?? "unknown"); const planetCount = system.planets.length; const shipCount = [...world.ships.values()].filter((s) => s.systemId === item.id).length; const stationCount = [...world.stations.values()].filter((s) => s.systemId === item.id).length; const lines = [ system.label, `${starLabel} · ${planetCount} planet${planetCount !== 1 ? "s" : ""}`, ]; const parts: string[] = []; if (shipCount > 0) { parts.push(`${shipCount} ship${shipCount !== 1 ? "s" : ""}`); } if (stationCount > 0) { parts.push(`${stationCount} station${stationCount !== 1 ? "s" : ""}`); } if (parts.length > 0) { lines.push(parts.join(" · ")); } return lines.join("\n"); } if (item.kind === "planet") { const system = world.systems.get(item.systemId); const planet = system?.planets[item.planetIndex]; return planet ? `${system?.label ?? item.systemId} / ${planet.label}` : `${item.systemId} / planet ${item.planetIndex + 1}`; } if (item.kind === "moon") { const system = world.systems.get(item.systemId); const planet = system?.planets[item.planetIndex]; const moon = planet?.moons[item.moonIndex]; if (moon) { return `${system?.label ?? item.systemId} / ${planet?.label ?? `planet ${item.planetIndex + 1}`} / ${moon.label}`; } return `${item.systemId} / planet ${item.planetIndex + 1} / moon ${item.moonIndex + 1}`; } if (item.kind === "node") { const node = world.nodes.get(item.id); if (!node) { return item.id; } const anchorPath = node.celestialId ? describeCelestialPathWithinSystem(world, node.systemId, node.celestialId) : undefined; return anchorPath ? `${anchorPath} / ${node.itemId}` : `${node.systemId} / ${node.itemId}`; } if (item.kind === "celestial") { const celestial = world.celestials.get(item.id); if (!celestial) { return item.id; } if (celestial.kind === "star") { const system = world.systems.get(celestial.systemId); return system ? `${system.label} star` : `${celestial.systemId} star`; } return describeCelestialPathWithinSystem(world, celestial.systemId, celestial.id) ?? `${celestial.systemId} / ${celestial.kind}`; } if (item.kind === "claim") { const claim = world.claims.get(item.id); const anchorPath = claim?.celestialId ? describeCelestialPathWithinSystem(world, claim.systemId, claim.celestialId) : undefined; return anchorPath ? `${anchorPath} claim` : `Claim ${item.id}`; } if (item.kind === "construction-site") { const site = world.constructionSites.get(item.id); const anchorPath = site?.celestialId ? describeCelestialPathWithinSystem(world, site.systemId, site.celestialId) : undefined; const siteLabel = site ? (site.blueprintId ?? site.targetDefinitionId) : item.id; return anchorPath ? `${anchorPath} / ${siteLabel}` : `Construction ${siteLabel}`; } return describeSelectable(world, item); } export function getSelectionGroup(item: Selectable): SelectionGroup { if (item.kind === "ship") { return "ships"; } if ( item.kind === "station" || item.kind === "node" || item.kind === "claim" || item.kind === "construction-site" ) { return "structures"; } return "celestials"; } export function resolveSelectableSystemId(world: WorldState | undefined, selection: Selectable): string | undefined { if (!world) { return undefined; } if (selection.kind === "ship") { return world.ships.get(selection.id)?.systemId; } if (selection.kind === "station") { return world.stations.get(selection.id)?.systemId; } if (selection.kind === "node") { return world.nodes.get(selection.id)?.systemId; } if (selection.kind === "celestial") { return world.celestials.get(selection.id)?.systemId; } if (selection.kind === "claim") { return world.claims.get(selection.id)?.systemId; } if (selection.kind === "construction-site") { return world.constructionSites.get(selection.id)?.systemId; } if (selection.kind === "planet") { return selection.systemId; } if (selection.kind === "moon") { return selection.systemId; } return selection.id; } export function resolveFocusedCelestialId(world: WorldState | undefined, selectedItems: Selectable[]): string | undefined { if (!world || selectedItems.length !== 1) { return undefined; } const selected = selectedItems[0]; if (selected.kind === "celestial") { return selected.id; } if (selected.kind === "ship") { return world.ships.get(selected.id)?.spatialState.currentCelestialId ?? world.ships.get(selected.id)?.celestialId ?? undefined; } if (selected.kind === "station") { return world.stations.get(selected.id)?.celestialId ?? undefined; } if (selected.kind === "claim") { return world.claims.get(selected.id)?.celestialId ?? undefined; } if (selected.kind === "construction-site") { return world.constructionSites.get(selected.id)?.celestialId ?? undefined; } return undefined; } export function describeOrbitalParent(world: WorldState | undefined, systemId?: string, anchor?: OrbitalAnchor): string { if (!world || !systemId) { return "unknown"; } const system = world.systems.get(systemId); if (!system) { return systemId; } if (!anchor || anchor.kind === "star") { return `${system.label} star`; } const planet = system.planets[anchor.planetIndex]; if (!planet) { return `${system.label} star`; } if (anchor.kind === "planet") { return planet.label; } return `${planet.label} moon ${anchor.moonIndex + 1}`; } export function renderSystemDetails( world: WorldState | undefined, system: SystemSnapshot, activeContext: boolean, cameraMode: CameraMode, cameraTargetShipId?: string, ): string { if (!world) { return ""; } let shipCount = 0; let stationCount = 0; let nodeCount = 0; let celestialCount = 0; let claimCount = 0; let constructionCount = 0; let moonCount = 0; for (const ship of world.ships.values()) { if (ship.systemId === system.id) { shipCount += 1; } } for (const station of world.stations.values()) { if (station.systemId === system.id) { stationCount += 1; } } for (const node of world.nodes.values()) { if (node.systemId === system.id) { nodeCount += 1; } } for (const celestial of world.celestials.values()) { if (celestial.systemId === system.id) { celestialCount += 1; } } for (const claim of world.claims.values()) { if (claim.systemId === system.id) { claimCount += 1; } } for (const site of world.constructionSites.values()) { if (site.systemId === system.id) { constructionCount += 1; } } for (const planet of system.planets) { moonCount += planet.moons.length; } const followText = activeContext && cameraMode === "follow" && cameraTargetShipId ? `
Camera locked to ${world.ships.get(cameraTargetShipId)?.name ?? cameraTargetShipId}
` : ""; return `${system.id}${activeContext ? " · active system" : ""}
${system.stars[0]?.kind ?? "unknown"} · ${system.stars.length} star${system.stars.length > 1 ? "s" : ""}
Planets ${system.planets.length}
Moons ${moonCount}
Ships ${shipCount}
Stations ${stationCount}
Celestials ${celestialCount}
Resource nodes ${nodeCount}
Claims ${claimCount}
Construction sites ${constructionCount}
Height ${formatGalaxyDistance(system.galaxyPosition.y)}
${system.planets.slice(0, 8).map((planet) => `${planet.label} (${planet.planetType})`).join("
")}