import type { ShipSnapshot, SpatialNodeSnapshot, SystemSnapshot } from "./contracts"; import type { CameraMode, OrbitalAnchor, Selectable, SelectionGroup, WorldState, } from "./viewerTypes"; import { formatGalaxyDistance } from "./viewerMath"; export function describeSelectable(world: WorldState | undefined, item: Selectable): string { if (!world) { return item.kind; } if (item.kind === "ship") { return world.ships.get(item.id)?.label ?? 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 === "spatial-node") { return `${world.spatialNodes.get(item.id)?.kind ?? "node"} ${item.id}`; } if (item.kind === "bubble") { return `bubble ${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}`; } 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") { return world.ships.get(item.id)?.label ?? item.id; } if (item.kind === "station") { return world.stations.get(item.id)?.label ?? item.id; } if (item.kind === "system") { return world.systems.get(item.id)?.label ?? item.id; } 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 === "node") { const node = world.nodes.get(item.id); if (!node) { return item.id; } const anchorPath = node.anchorNodeId ? describeSpatialNodePathWithinSystem(world, node.systemId, node.anchorNodeId) : undefined; return anchorPath ? `${anchorPath} / ${node.itemId}` : `${node.systemId} / ${node.itemId}`; } if (item.kind === "spatial-node") { const node = world.spatialNodes.get(item.id); if (!node) { return item.id; } if (node.kind === "star") { const system = world.systems.get(node.systemId); return system ? `${system.label} star` : `${node.systemId} star`; } return describeSpatialNodePathWithinSystem(world, node.systemId, node.id) ?? `${node.systemId} / ${node.kind}`; } if (item.kind === "bubble") { const bubble = world.localBubbles.get(item.id); const anchorPath = bubble?.nodeId ? describeSpatialNodePathWithinSystem(world, bubble.systemId, bubble.nodeId) : undefined; return anchorPath ? `${anchorPath} bubble` : `Bubble ${item.id}`; } if (item.kind === "claim") { const claim = world.claims.get(item.id); const anchorPath = claim?.nodeId ? describeSpatialNodePathWithinSystem(world, claim.systemId, claim.nodeId) : undefined; return anchorPath ? `${anchorPath} claim` : `Claim ${item.id}`; } if (item.kind === "construction-site") { const site = world.constructionSites.get(item.id); const anchorPath = site?.nodeId ? describeSpatialNodePathWithinSystem(world, site.systemId, site.nodeId) : 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 === "spatial-node" || item.kind === "bubble" || 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 === "spatial-node") { return world.spatialNodes.get(selection.id)?.systemId; } if (selection.kind === "bubble") { return world.localBubbles.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; } return selection.id; } export function resolveFocusedBubbleId(world: WorldState | undefined, selectedItems: Selectable[]): string | undefined { if (!world || selectedItems.length !== 1) { return undefined; } const selected = selectedItems[0]; if (selected.kind === "bubble") { return selected.id; } if (selected.kind === "ship") { return world.ships.get(selected.id)?.bubbleId ?? world.ships.get(selected.id)?.spatialState.currentBubbleId ?? undefined; } if (selected.kind === "station") { return world.stations.get(selected.id)?.bubbleId ?? undefined; } if (selected.kind === "spatial-node") { return world.spatialNodes.get(selected.id)?.bubbleId ?? undefined; } if (selected.kind === "claim") { return world.claims.get(selected.id)?.bubbleId ?? undefined; } if (selected.kind === "construction-site") { return world.constructionSites.get(selected.id)?.bubbleId ?? 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 spatialNodeCount = 0; let bubbleCount = 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 node of world.spatialNodes.values()) { if (node.systemId === system.id) { spatialNodeCount += 1; } } for (const bubble of world.localBubbles.values()) { if (bubble.systemId === system.id) { bubbleCount += 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.moonCount; } const followText = activeContext && cameraMode === "follow" && cameraTargetShipId ? `
Camera locked to ${world.ships.get(cameraTargetShipId)?.label ?? cameraTargetShipId}
` : ""; return `${system.id}${activeContext ? " · active system" : ""}
${system.starKind} · ${system.starCount} star${system.starCount > 1 ? "s" : ""}
Planets ${system.planets.length}
Moons ${moonCount}
Ships ${shipCount}
Stations ${stationCount}
Spatial nodes ${spatialNodeCount}
Resource nodes ${nodeCount}
Bubbles ${bubbleCount}
Claims ${claimCount}
Construction sites ${constructionCount}
Height ${formatGalaxyDistance(system.galaxyPosition.y)}
${system.planets.slice(0, 8).map((planet) => `${planet.label} (${planet.planetType})`).join("
")}