Refactor universe sim and observer HUD

This commit is contained in:
2026-03-12 00:53:52 -04:00
parent 5979a74d46
commit fbdf8d0d5a
11 changed files with 1832 additions and 622 deletions

View File

@@ -6,14 +6,27 @@ import {
import { describeFleetOrder } from "../fleet/runtime";
import { getShipCargoAmount } from "../state/inventory";
import type {
FactionInstance,
FleetInstance,
PlanetInstance,
ShipInstance,
SolarSystemInstance,
StationInstance,
ViewLevel,
} from "../types";
export function getSelectionTitle(selection: ShipInstance[], selectedStation?: StationInstance) {
export function getSelectionTitle(
selection: ShipInstance[],
selectedStation?: StationInstance,
selectedSystem?: SolarSystemInstance,
selectedPlanet?: PlanetInstance,
) {
if (selectedPlanet) {
return selectedPlanet.definition.label;
}
if (selectedSystem) {
return selectedSystem.definition.label;
}
if (selectedStation) {
return selectedStation.definition.label;
}
@@ -26,19 +39,114 @@ export function getSelectionTitle(selection: ShipInstance[], selectedStation?: S
return `${selection.length} Ships Selected`;
}
export function getSelectionStripLabels(
selection: ShipInstance[],
selectedStation?: StationInstance,
selectedSystem?: SolarSystemInstance,
selectedPlanet?: PlanetInstance,
) {
if (selectedPlanet) {
return [selectedPlanet.definition.label];
}
if (selectedSystem) {
return [selectedSystem.definition.label];
}
if (selectedStation) {
return [selectedStation.definition.label];
}
if (selection.length === 0) {
return [];
}
return selection.map((ship) => ship.definition.label);
}
export function getSelectionCardsMarkup(
selection: ShipInstance[],
selectedStation: StationInstance | undefined,
selectedSystem: SolarSystemInstance | undefined,
selectedPlanet: PlanetInstance | undefined,
) {
if (selectedPlanet) {
return renderCard(
selectedPlanet.definition.label,
[
selectedPlanet.systemId,
`Orbit ${Math.round(selectedPlanet.definition.orbitRadius)}`,
`Size ${selectedPlanet.definition.size}`,
selectedPlanet.definition.hasRing ? "Ringed" : "No ring",
],
);
}
if (selectedSystem) {
return renderCard(
selectedSystem.definition.label,
[
selectedSystem.strategicValue,
`${selectedSystem.planets.length} planets`,
`${selectedSystem.definition.resourceNodes.length} nodes`,
`${selectedSystem.controllingFactionId ?? "Contested"} ${Math.round(selectedSystem.controlProgress)}%`,
],
);
}
if (selectedStation) {
return renderCard(
selectedStation.definition.label,
[
selectedStation.factionId,
selectedStation.definition.category,
`HP ${Math.round(selectedStation.health)}/${selectedStation.maxHealth}`,
`Dock ${selectedStation.dockedShipIds.size}/${selectedStation.definition.dockingCapacity}`,
],
);
}
if (selection.length === 0) {
return `<span class="selection-strip-empty">No active selection</span>`;
}
return selection
.map((ship) =>
renderCard(ship.definition.label, [
ship.factionId,
ship.state,
ship.order.kind,
`HP ${Math.round(ship.health)}/${ship.maxHealth}`,
]),
)
.join("");
}
export function getSelectionDetails(
selection: ShipInstance[],
selectedStation: StationInstance | undefined,
selectedSystem: SolarSystemInstance | undefined,
selectedPlanet: PlanetInstance | undefined,
systems: SolarSystemInstance[],
viewLevel: ViewLevel,
ships: ShipInstance[],
fleets: FleetInstance[],
factions: FactionInstance[],
) {
if (selectedPlanet) {
return `${selectedPlanet.definition.label}${selectedPlanet.systemId}\nOrbit Radius: ${Math.round(selectedPlanet.definition.orbitRadius)}\nSize: ${selectedPlanet.definition.size}\nOrbit Speed: ${selectedPlanet.definition.orbitSpeed.toFixed(2)}\nTilt: ${selectedPlanet.definition.tilt.toFixed(2)}\nRing: ${selectedPlanet.definition.hasRing ? "Yes" : "No"}`;
}
if (selectedSystem) {
return `${selectedSystem.definition.label}\nType: ${selectedSystem.strategicValue}\nControl: ${selectedSystem.controllingFactionId ?? "Contested"} ${Math.round(selectedSystem.controlProgress)}%\nPlanets: ${selectedSystem.planets.length}\nResource Nodes: ${selectedSystem.definition.resourceNodes.length}\nGravity Well: ${Math.round(selectedSystem.gravityWellRadius)}`;
}
if (selectedStation) {
return describeStation(selectedStation, ships, fleets);
}
if (selection.length === 0) {
return `Systems online: ${systems.map((system) => system.definition.label).join(", ")}\nFleets active: ${fleets.length}\n\nOrders: Move, Patrol, Escort, Mine\nView: ${viewLevel}`;
const central = systems
.filter((system) => system.strategicValue === "central")
.map((system) => `${system.definition.label}: ${system.controllingFactionId ?? "contested"} ${Math.round(system.controlProgress)}%`)
.join("\n");
const factionLines = factions
.filter((faction) => faction.definition.kind === "empire")
.map(
(faction) =>
`${faction.definition.label}: systems ${faction.ownedSystemIds.size} • mined ${Math.round(faction.oreMined)} • built ${faction.shipsBuilt} ships • losses ${faction.shipsLost}`,
)
.join("\n");
return `Observer Mode\nSystems online: ${systems.length}\nFleets tracked: ${fleets.length}\nView: ${viewLevel}\n\nCentral systems:\n${central}\n\nEmpires:\n${factionLines}`;
}
return selection
@@ -49,7 +157,7 @@ export function getSelectionDetails(
ship.definition.dockingCapacity && ship.definition.dockingCapacity > 0
? `\nHangar: ${ship.dockedShipIds.size}/${ship.definition.dockingCapacity} for ${(ship.definition.dockingClasses ?? []).join(", ")}`
: "";
return `${ship.definition.label}${ship.systemId}\nClass: ${ship.definition.shipClass}\nState: ${ship.state}${dockedAt ? ` @ ${dockedAt}` : ""}\nOrder: ${ship.order.kind}\nFleet: ${ship.fleetId ?? "Independent"}${ship.isFleetCommander ? " • Commander" : ship.isWingLeader ? " • Wing Leader" : ""}\nBehavior: ${ship.behavior}\nCargo: ${Math.round(getShipCargoAmount(ship))}/${ship.definition.cargoCapacity || 0} ${getItemLabel(ship.cargoItemId)}\nFuel: ${ship.fuel.toFixed(0)}/${ship.maxFuel}\nEnergy: ${ship.energy.toFixed(0)}/${ship.maxEnergy}\nHold Type: ${ship.definition.cargoKind ?? "none"}${hangarStatus}\nModules: ${ship.definition.modules.map(getModuleLabel).join(", ")}`;
return `${ship.definition.label}${ship.systemId}\nFaction: ${ship.factionId}\nClass: ${ship.definition.shipClass}\nState: ${ship.state}${dockedAt ? ` @ ${dockedAt}` : ""}\nOrder: ${ship.order.kind}\nFleet: ${ship.fleetId ?? "Independent"}${ship.isFleetCommander ? " • Commander" : ship.isWingLeader ? " • Wing Leader" : ""}\nBehavior: ${ship.behavior}\nHealth: ${Math.round(ship.health)}/${ship.maxHealth}\nCargo: ${Math.round(getShipCargoAmount(ship))}/${ship.definition.cargoCapacity || 0} ${getItemLabel(ship.cargoItemId)}\nFuel: ${ship.fuel.toFixed(0)}/${ship.maxFuel}\nEnergy: ${ship.energy.toFixed(0)}/${ship.maxEnergy}\nHold Type: ${ship.definition.cargoKind ?? "none"}${hangarStatus}\nModules: ${ship.definition.modules.map(getModuleLabel).join(", ")}`;
},
)
.join("\n\n");
@@ -88,7 +196,7 @@ export function describeStation(station: StationInstance, ships: ShipInstance[],
? "Fabricating industrial parts and equipment"
: "Managing local trade traffic";
return `${station.definition.label}${station.systemId}\nRole: ${station.definition.category}\nActivity: ${activity}\nLocal Fleets: ${localFleets}\nDocking: ${station.dockedShipIds.size}/${station.definition.dockingCapacity}\nFuel: ${station.fuel.toFixed(0)}/${station.maxFuel}\nEnergy: ${station.energy.toFixed(0)}/${station.maxEnergy}\nBulk Solid: ${Math.round(station.inventory["bulk-solid"])}\nContainer: ${Math.round(station.inventory.container)}\nManufactured: ${Math.round(station.inventory.manufactured)}\nModules: ${station.modules.map(getModuleLabel).join(", ")}\n${productionStatus}Radius: ${station.definition.radius}`;
return `${station.definition.label}${station.systemId}\nFaction: ${station.factionId}\nRole: ${station.definition.category}\nActivity: ${activity}\nLocal Fleets: ${localFleets}\nDocking: ${station.dockedShipIds.size}/${station.definition.dockingCapacity}\nHealth: ${Math.round(station.health)}/${station.maxHealth}\nFuel: ${station.fuel.toFixed(0)}/${station.maxFuel}\nEnergy: ${station.energy.toFixed(0)}/${station.maxEnergy}\nBulk Solid: ${Math.round(station.inventory["bulk-solid"])}\nContainer: ${Math.round(station.inventory.container)}\nManufactured: ${Math.round(station.inventory.manufactured)}\nModules: ${station.modules.map(getModuleLabel).join(", ")}\n${productionStatus}Radius: ${station.definition.radius}`;
}
export function getFleetWindowMarkup(
@@ -178,6 +286,15 @@ function describeShipNode(ship: ShipInstance): string {
return `${ship.definition.shipClass}${ship.state}${ship.order.kind}${ship.behavior}`;
}
function renderCard(title: string, lines: string[]) {
return `
<article class="selection-strip-card">
<span class="selection-strip-card-title">${title}</span>
${lines.map((line) => `<span class="selection-strip-card-line">${line}</span>`).join("")}
</article>
`;
}
function collectWingShipIds(fleet: FleetInstance, rootWingId: string): string[] {
const wingIds = new Set<string>([rootWingId]);
let changed = true;