feat: production chain
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { formatInventory, formatVector } from "./viewerMath";
|
||||
import { describeOrbitalParent, describeSelectable, getSelectionGroup, renderSystemDetails } from "./viewerSelection";
|
||||
import { formatInventory, formatVector, inventoryAmount } from "./viewerMath";
|
||||
import { describeOrbitalParent, describeSelectable, describeShipCurrentAction, describeShipState, describeSpatialNodePathWithinSystem, getSelectionGroup, renderSystemDetails } from "./viewerSelection";
|
||||
import type {
|
||||
CameraMode,
|
||||
HistoryWindowState,
|
||||
@@ -31,6 +31,29 @@ interface SystemPanelParams {
|
||||
cameraTargetShipId?: string;
|
||||
}
|
||||
|
||||
function renderSystemOwnership(world: WorldState, systemId: string): string {
|
||||
const claims = [...world.claims.values()].filter((claim) =>
|
||||
claim.systemId === systemId && claim.state !== "destroyed");
|
||||
if (claims.length === 0) {
|
||||
return "Ownership none";
|
||||
}
|
||||
|
||||
const ownershipByFaction = new Map<string, number>();
|
||||
for (const claim of claims) {
|
||||
ownershipByFaction.set(claim.factionId, (ownershipByFaction.get(claim.factionId) ?? 0) + 1);
|
||||
}
|
||||
|
||||
return [...ownershipByFaction.entries()]
|
||||
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
||||
.map(([factionId, count]) => {
|
||||
const faction = world.factions.get(factionId);
|
||||
const label = faction?.label ?? factionId;
|
||||
const share = Math.round((count / claims.length) * 100);
|
||||
return `${label} ${count}/${claims.length} (${share}%)`;
|
||||
})
|
||||
.join("<br>");
|
||||
}
|
||||
|
||||
export function updateDetailPanel(
|
||||
detailTitleEl: HTMLHeadingElement,
|
||||
detailBodyEl: HTMLDivElement,
|
||||
@@ -79,12 +102,30 @@ export function updateDetailPanel(
|
||||
return;
|
||||
}
|
||||
const parent = describeSelectionParent(selected);
|
||||
const cargoUsed = ship.inventory.reduce((sum, entry) => sum + entry.amount, 0);
|
||||
const fuelStored = inventoryAmount(ship.inventory, "fuel");
|
||||
const cargoUsed = ship.cargoItemId
|
||||
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
||||
: 0;
|
||||
const cargoLabel = ship.cargoItemId ?? "none";
|
||||
const shipState = describeShipState(world, ship);
|
||||
const shipAction = describeShipCurrentAction(ship);
|
||||
detailTitleEl.textContent = ship.label;
|
||||
detailBodyEl.innerHTML = `
|
||||
<p>Parent ${parent}</p>
|
||||
<p>State ${ship.state}</p>
|
||||
<p>Energy ${ship.energyStored.toFixed(0)}<br>Cargo ${cargoUsed.toFixed(0)} / ${ship.cargoCapacity.toFixed(0)}</p>
|
||||
<p>State ${shipState}</p>
|
||||
${shipAction ? `
|
||||
<div class="detail-progress">
|
||||
<div class="detail-progress-label">
|
||||
<span>${shipAction.label}</span>
|
||||
<span>${Math.round(shipAction.progress * 100)}%</span>
|
||||
</div>
|
||||
<div class="detail-progress-track">
|
||||
<div class="detail-progress-fill" style="width: ${(shipAction.progress * 100).toFixed(1)}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
` : ""}
|
||||
<p>Fuel ${fuelStored.toFixed(1)}<br>Capacitor ${ship.energyStored.toFixed(1)}</p>
|
||||
<p>Cargo ${cargoLabel} ${cargoUsed.toFixed(0)} / ${ship.cargoCapacity.toFixed(0)}</p>
|
||||
<p>Inventory ${formatInventory(ship.inventory)}</p>
|
||||
<p>Velocity ${formatVector(ship.localVelocity)}</p>
|
||||
<p>Camera ${cameraMode === "follow" && cameraTargetShipId === ship.id ? "camera-follow" : "tactical"}<br>Press C to toggle follow</p>
|
||||
@@ -98,12 +139,43 @@ export function updateDetailPanel(
|
||||
return;
|
||||
}
|
||||
const parent = describeSelectionParent(selected);
|
||||
const installedModules = station.installedModules.length > 0
|
||||
? station.installedModules.join("<br>")
|
||||
: "none";
|
||||
const activeConstruction = [...world.constructionSites.values()]
|
||||
.filter((site) => site.stationId === station.id && site.state !== "completed")
|
||||
.map((site) => `${site.blueprintId ?? site.targetDefinitionId} (${site.state})`)
|
||||
.join("<br>") || "none";
|
||||
const dockedShipLabels = station.dockedShipIds.length > 0
|
||||
? station.dockedShipIds.map((shipId) => world.ships.get(shipId)?.label ?? shipId).join("<br>")
|
||||
: "none";
|
||||
const stationInventory = station.inventory.filter((entry) => entry.itemId !== "fuel");
|
||||
const stationProcesses = station.currentProcesses;
|
||||
const stationProcessingHtml = stationProcesses.length > 0
|
||||
? stationProcesses.map((process) => `
|
||||
<div class="detail-progress">
|
||||
<div class="detail-progress-label">
|
||||
<span>${process.label}</span>
|
||||
<span>${Math.round(process.progress * 100)}%</span>
|
||||
</div>
|
||||
<div class="detail-progress-track">
|
||||
<div class="detail-progress-fill" style="width: ${(process.progress * 100).toFixed(1)}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join("")
|
||||
: "";
|
||||
detailTitleEl.textContent = station.label;
|
||||
detailBodyEl.innerHTML = `
|
||||
<p>${station.category} · ${station.systemId}</p>
|
||||
<p>Parent ${parent}</p>
|
||||
<p>Energy ${station.energyStored.toFixed(0)}<br>Docked ${station.dockedShips} / ${station.dockingPads}</p>
|
||||
<p>Inventory ${formatInventory(station.inventory)}</p>
|
||||
${stationProcessingHtml}
|
||||
<p>Fuel ${station.fuelStored.toFixed(1)} / ${station.fuelCapacity.toFixed(1)}<br>Capacitor ${station.energyStored.toFixed(1)} / ${station.energyCapacity.toFixed(1)}</p>
|
||||
<p>Docked ${station.dockedShips} / ${station.dockingPads}
|
||||
<br>
|
||||
${dockedShipLabels}</p>
|
||||
<p>Modules ${installedModules}</p>
|
||||
<p>Constructing ${activeConstruction}</p>
|
||||
<p>Inventory ${formatInventory(stationInventory)}</p>
|
||||
<p>History available in the separate history window.</p>
|
||||
`;
|
||||
return;
|
||||
@@ -115,11 +187,23 @@ export function updateDetailPanel(
|
||||
return;
|
||||
}
|
||||
const parent = describeSelectionParent(selected);
|
||||
const nodeLevel = node.maxOre > 0
|
||||
? Math.max(0, Math.min(node.oreRemaining / node.maxOre, 1))
|
||||
: 0;
|
||||
detailTitleEl.textContent = `Node ${node.id}`;
|
||||
detailBodyEl.innerHTML = `
|
||||
<p>${node.systemId}</p>
|
||||
<p>Parent ${parent}</p>
|
||||
<p>Source ${node.sourceKind}<br>Resource ${node.itemId}</p>
|
||||
<div class="detail-progress">
|
||||
<div class="detail-progress-label">
|
||||
<span>Level</span>
|
||||
<span>${Math.round(nodeLevel * 100)}%</span>
|
||||
</div>
|
||||
<div class="detail-progress-track">
|
||||
<div class="detail-progress-fill" style="width: ${(nodeLevel * 100).toFixed(1)}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Stock ${node.oreRemaining.toFixed(0)} / ${node.maxOre.toFixed(0)}</p>
|
||||
`;
|
||||
return;
|
||||
@@ -240,7 +324,9 @@ export function updateSystemPanel(params: SystemPanelParams) {
|
||||
}
|
||||
|
||||
systemTitleEl.textContent = activeSystem.label;
|
||||
systemBodyEl.innerHTML = renderSystemDetails(world, activeSystem, true, cameraMode, cameraTargetShipId);
|
||||
systemBodyEl.innerHTML = `
|
||||
<p>${renderSystemOwnership(world, activeSystem.id)}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
export function describeSelectionParent(
|
||||
@@ -270,8 +356,13 @@ export function describeSelectionParent(
|
||||
}
|
||||
if (selection.kind === "station") {
|
||||
const station = world.stations.get(selection.id);
|
||||
const visual = station ? stationVisuals.get(selection.id) : undefined;
|
||||
return describeOrbitalParent(world, station?.systemId, visual?.anchor);
|
||||
if (!station) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
return station.anchorNodeId
|
||||
? describeSpatialNodePathWithinSystem(world, station.systemId, station.anchorNodeId) ?? `${station.systemId} network`
|
||||
: "unknown";
|
||||
}
|
||||
if (selection.kind === "node") {
|
||||
const node = world.nodes.get(selection.id);
|
||||
|
||||
Reference in New Issue
Block a user