feat: production chain
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { SystemSnapshot } from "./contracts";
|
||||
import type { ShipSnapshot, SpatialNodeSnapshot, SystemSnapshot } from "./contracts";
|
||||
import type {
|
||||
CameraMode,
|
||||
OrbitalAnchor,
|
||||
@@ -214,3 +214,205 @@ export function renderSystemDetails(
|
||||
${followText}
|
||||
`;
|
||||
}
|
||||
|
||||
export function describeShipState(world: WorldState | undefined, ship: ShipSnapshot): string {
|
||||
const baseState = ship.state;
|
||||
if (baseState === "capacitor-starved") {
|
||||
return `${baseState} while ${describeControllerTask(ship.controllerTaskKind)}`;
|
||||
}
|
||||
|
||||
if (!world || (baseState !== "ftl" && baseState !== "spooling-ftl" && baseState !== "warping" && baseState !== "spooling-warp")) {
|
||||
return baseState;
|
||||
}
|
||||
|
||||
const destinationNodeId = ship.spatialState.destinationNodeId ?? ship.spatialState.transit?.destinationNodeId;
|
||||
if (!destinationNodeId) {
|
||||
return baseState;
|
||||
}
|
||||
|
||||
const destinationNode = world.spatialNodes.get(destinationNodeId);
|
||||
if (!destinationNode) {
|
||||
return `${baseState} -> ${destinationNodeId}`;
|
||||
}
|
||||
|
||||
if (baseState === "warping" || baseState === "spooling-warp") {
|
||||
const destinationPath = describeSpatialNodePathWithinSystem(world, destinationNode.systemId, destinationNodeId);
|
||||
return `${baseState} -> ${destinationPath ?? destinationNodeId}`;
|
||||
}
|
||||
|
||||
const destinationSystem = world.systems.get(destinationNode.systemId);
|
||||
return `${baseState} -> ${destinationSystem?.label ?? destinationNode.systemId}`;
|
||||
}
|
||||
|
||||
function describeControllerTask(taskKind: string): string {
|
||||
switch (taskKind) {
|
||||
case "travel":
|
||||
return "travel";
|
||||
case "extract":
|
||||
return "mining";
|
||||
case "dock":
|
||||
return "docking";
|
||||
case "unload":
|
||||
return "transfer";
|
||||
case "refuel":
|
||||
return "refuel";
|
||||
case "deliver-construction":
|
||||
return "material delivery";
|
||||
case "build-construction-site":
|
||||
return "site construction";
|
||||
case "construct-module":
|
||||
return "module construction";
|
||||
case "undock":
|
||||
return "undocking";
|
||||
case "load-workers":
|
||||
return "worker loading";
|
||||
case "unload-workers":
|
||||
return "worker unloading";
|
||||
default:
|
||||
return taskKind;
|
||||
}
|
||||
}
|
||||
|
||||
export function describeShipCurrentAction(ship: ShipSnapshot): { label: string; progress: number } | undefined {
|
||||
if (!ship.currentAction) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
label: ship.currentAction.label,
|
||||
progress: Math.max(0, Math.min(ship.currentAction.progress, 1)),
|
||||
};
|
||||
}
|
||||
|
||||
export function describeShipLocation(world: WorldState | undefined, ship: ShipSnapshot): { system: string; local?: string } {
|
||||
const systemId = ship.spatialState.currentSystemId || ship.systemId;
|
||||
const system = world?.systems.get(systemId);
|
||||
const systemLabel = system?.label ?? systemId;
|
||||
if (!world || !system) {
|
||||
return { system: systemLabel };
|
||||
}
|
||||
|
||||
if (ship.dockedStationId) {
|
||||
const station = world.stations.get(ship.dockedStationId);
|
||||
if (station) {
|
||||
const anchorPath = station.anchorNodeId
|
||||
? describeSpatialNodePathWithinSystem(world, station.systemId, station.anchorNodeId)
|
||||
: undefined;
|
||||
return {
|
||||
system: systemLabel,
|
||||
local: anchorPath ? `${anchorPath}/${station.label}` : station.label,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const currentNodeId = ship.spatialState.currentNodeId ?? ship.nodeId;
|
||||
if (currentNodeId) {
|
||||
const nodePath = describeSpatialNodePathWithinSystem(world, systemId, currentNodeId);
|
||||
if (nodePath) {
|
||||
return { system: systemLabel, local: nodePath };
|
||||
}
|
||||
}
|
||||
|
||||
const currentBubbleId = ship.spatialState.currentBubbleId ?? ship.bubbleId;
|
||||
if (currentBubbleId) {
|
||||
const bubble = world.localBubbles.get(currentBubbleId);
|
||||
if (bubble?.nodeId) {
|
||||
const nodePath = describeSpatialNodePathWithinSystem(world, systemId, bubble.nodeId);
|
||||
if (nodePath) {
|
||||
return { system: systemLabel, local: nodePath };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { system: systemLabel };
|
||||
}
|
||||
|
||||
export function describeActiveSpace(
|
||||
world: WorldState | undefined,
|
||||
zoomLevel: "local" | "system" | "universe",
|
||||
activeSystemId: string | undefined,
|
||||
selectedItems: Selectable[],
|
||||
): string {
|
||||
if (!world || zoomLevel === "universe") {
|
||||
return "deep-space";
|
||||
}
|
||||
|
||||
const activeSystem = activeSystemId ? world.systems.get(activeSystemId) : undefined;
|
||||
if (!activeSystem) {
|
||||
return "deep-space";
|
||||
}
|
||||
|
||||
if (zoomLevel !== "local") {
|
||||
return activeSystem.label;
|
||||
}
|
||||
|
||||
const bubbleId = resolveFocusedBubbleId(world, selectedItems);
|
||||
if (bubbleId) {
|
||||
const bubble = world.localBubbles.get(bubbleId);
|
||||
const localPath = bubble?.nodeId
|
||||
? describeSpatialNodePathWithinSystem(world, activeSystem.id, bubble.nodeId)
|
||||
: undefined;
|
||||
return localPath
|
||||
? `${activeSystem.label} / ${localPath}`
|
||||
: activeSystem.label;
|
||||
}
|
||||
|
||||
const selected = selectedItems.length === 1 ? selectedItems[0] : undefined;
|
||||
if (selected?.kind === "planet" && selected.systemId === activeSystem.id) {
|
||||
const planet = activeSystem.planets[selected.planetIndex];
|
||||
return planet
|
||||
? `${activeSystem.label} / ${planet.label}`
|
||||
: activeSystem.label;
|
||||
}
|
||||
|
||||
return activeSystem.label;
|
||||
}
|
||||
|
||||
export function describeSpatialNodePathWithinSystem(world: WorldState, systemId: string, nodeId: string): string | undefined {
|
||||
const node = world.spatialNodes.get(nodeId);
|
||||
const system = world.systems.get(systemId);
|
||||
if (!node || !system) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (node.parentNodeId) {
|
||||
const parentPath = describeSpatialNodePathWithinSystem(world, systemId, node.parentNodeId);
|
||||
const segment = describeSpatialNodeSegment(world, system, node);
|
||||
return parentPath ? `${parentPath}/${segment}` : segment;
|
||||
}
|
||||
|
||||
if (node.kind === "star") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return describeSpatialNodeSegment(world, system, node);
|
||||
}
|
||||
|
||||
function describeSpatialNodeSegment(world: WorldState, system: SystemSnapshot, node: SpatialNodeSnapshot): string {
|
||||
const moonMatch = node.id.match(/-planet-(\d+)-moon-(\d+)$/);
|
||||
if (moonMatch) {
|
||||
const moonIndex = Number.parseInt(moonMatch[2], 10);
|
||||
return `Moon ${moonIndex}`;
|
||||
}
|
||||
|
||||
const lagrangeMatch = node.id.match(/-planet-\d+-(l[1-5])$/);
|
||||
if (lagrangeMatch) {
|
||||
return lagrangeMatch[1].toUpperCase();
|
||||
}
|
||||
|
||||
const planetMatch = node.id.match(/-planet-(\d+)$/);
|
||||
if (planetMatch) {
|
||||
const planetIndex = Number.parseInt(planetMatch[1], 10) - 1;
|
||||
return system.planets[planetIndex]?.label ?? `Planet ${planetMatch[1]}`;
|
||||
}
|
||||
|
||||
if (node.kind === "station" && node.occupyingStructureId) {
|
||||
return world.stations.get(node.occupyingStructureId)?.label ?? node.occupyingStructureId;
|
||||
}
|
||||
|
||||
if (node.kind === "resource-site") {
|
||||
return node.orbitReferenceId ?? "Resource Site";
|
||||
}
|
||||
|
||||
return node.orbitReferenceId ?? node.kind;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user