feat: rework modules, items and fuel
This commit is contained in:
@@ -6,6 +6,12 @@ export interface StationActionProgressSnapshot {
|
||||
progress: number;
|
||||
}
|
||||
|
||||
export interface StationStorageUsageSnapshot {
|
||||
storageClass: string;
|
||||
used: number;
|
||||
capacity: number;
|
||||
}
|
||||
|
||||
export interface StationSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -19,10 +25,6 @@ export interface StationSnapshot {
|
||||
dockedShips: number;
|
||||
dockedShipIds: string[];
|
||||
dockingPads: number;
|
||||
fuelStored: number;
|
||||
fuelCapacity: number;
|
||||
energyStored: number;
|
||||
energyCapacity: number;
|
||||
currentProcesses: StationActionProgressSnapshot[];
|
||||
inventory: InventoryEntry[];
|
||||
factionId: string;
|
||||
@@ -32,11 +34,12 @@ export interface StationSnapshot {
|
||||
populationCapacity: number;
|
||||
workforceRequired: number;
|
||||
workforceEffectiveRatio: number;
|
||||
storageUsage: StationStorageUsageSnapshot[];
|
||||
installedModules: string[];
|
||||
marketOrderIds: string[];
|
||||
}
|
||||
|
||||
export interface StationDelta extends StationSnapshot {}
|
||||
export interface StationDelta extends StationSnapshot { }
|
||||
|
||||
export interface ClaimSnapshot {
|
||||
id: string;
|
||||
@@ -50,7 +53,7 @@ export interface ClaimSnapshot {
|
||||
activatesAtUtc: string;
|
||||
}
|
||||
|
||||
export interface ClaimDelta extends ClaimSnapshot {}
|
||||
export interface ClaimDelta extends ClaimSnapshot { }
|
||||
|
||||
export interface ConstructionSiteSnapshot {
|
||||
id: string;
|
||||
@@ -72,4 +75,4 @@ export interface ConstructionSiteSnapshot {
|
||||
marketOrderIds: string[];
|
||||
}
|
||||
|
||||
export interface ConstructionSiteDelta extends ConstructionSiteSnapshot {}
|
||||
export interface ConstructionSiteDelta extends ConstructionSiteSnapshot { }
|
||||
|
||||
@@ -21,7 +21,6 @@ export interface ShipSnapshot {
|
||||
cargoCapacity: number;
|
||||
cargoItemId?: string | null;
|
||||
workerPopulation: number;
|
||||
energyStored: number;
|
||||
travelSpeed: number;
|
||||
travelSpeedUnit: string;
|
||||
inventory: InventoryEntry[];
|
||||
@@ -32,7 +31,7 @@ export interface ShipSnapshot {
|
||||
spatialState: ShipSpatialStateSnapshot;
|
||||
}
|
||||
|
||||
export interface ShipDelta extends ShipSnapshot {}
|
||||
export interface ShipDelta extends ShipSnapshot { }
|
||||
|
||||
export interface ShipActionProgressSnapshot {
|
||||
label: string;
|
||||
|
||||
@@ -26,7 +26,6 @@ export function renderFactionStrip(
|
||||
|
||||
return ships
|
||||
.map((ship) => {
|
||||
const fuel = inventoryAmount(ship.inventory, "fuel");
|
||||
const cargo = ship.cargoItemId
|
||||
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
||||
: 0;
|
||||
@@ -54,7 +53,7 @@ export function renderFactionStrip(
|
||||
</div>
|
||||
</div>
|
||||
<p>${shipLocation.system}${shipLocation.local ? `<br>${shipLocation.local}` : ""}</p>
|
||||
<p>Fuel ${fuel.toFixed(1)} · Cap ${ship.energyStored.toFixed(1)}${ship.cargoCapacity > 0 ? ` · Cargo ${cargo.toFixed(0)}` : ""}</p>
|
||||
<p>Cargo ${cargo.toFixed(0)}</p>
|
||||
<p>State ${shipState}</p>
|
||||
${shipAction ? `
|
||||
<div class="ship-action-progress">
|
||||
|
||||
@@ -37,6 +37,94 @@ interface SystemPanelParams {
|
||||
cameraTargetShipId?: string;
|
||||
}
|
||||
|
||||
function laneModuleId(lane: string): string | undefined {
|
||||
switch (lane) {
|
||||
case "refinery":
|
||||
return "refinery-stack";
|
||||
case "fabrication":
|
||||
return "fabricator-array";
|
||||
case "components":
|
||||
return "component-factory";
|
||||
case "shipyard":
|
||||
return "ship-factory";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function formatModuleListWithConstruction(
|
||||
world: WorldState,
|
||||
stationId: string,
|
||||
installedModules: string[],
|
||||
currentProcesses: { lane: string; label: string; progress: number }[],
|
||||
): string {
|
||||
const processByModule = new Map<string, { label: string; progress: number }[]>();
|
||||
for (const process of currentProcesses) {
|
||||
const moduleId = laneModuleId(process.lane);
|
||||
if (!moduleId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = processByModule.get(moduleId) ?? [];
|
||||
existing.push({ label: process.label, progress: process.progress });
|
||||
processByModule.set(moduleId, existing);
|
||||
}
|
||||
|
||||
const renderedProcessCount = new Map<string, number>();
|
||||
const moduleLines = installedModules.map((moduleId) => {
|
||||
const processIndex = renderedProcessCount.get(moduleId) ?? 0;
|
||||
const processes = processByModule.get(moduleId) ?? [];
|
||||
const process = processes[processIndex];
|
||||
renderedProcessCount.set(moduleId, processIndex + 1);
|
||||
if (!process) {
|
||||
return moduleId;
|
||||
}
|
||||
|
||||
return `${moduleId} -> ${process.label} (${Math.round(process.progress * 100)}%)`;
|
||||
});
|
||||
const activeSites = [...world.constructionSites.values()]
|
||||
.filter((site) => site.stationId === stationId && site.state !== "completed")
|
||||
.sort((left, right) => left.targetDefinitionId.localeCompare(right.targetDefinitionId));
|
||||
|
||||
for (const site of activeSites) {
|
||||
const moduleId = site.blueprintId ?? site.targetDefinitionId;
|
||||
const progress = Math.round(site.progress * 100);
|
||||
const tooltip = site.requiredItems.length > 0
|
||||
? site.requiredItems
|
||||
.map((entry) => `${entry.itemId}: ${entry.amount.toFixed(0)} required / ${inventoryAmount(site.stationId ? (world.stations.get(site.stationId)?.inventory ?? []) : site.deliveredItems, entry.itemId).toFixed(0)} available`)
|
||||
.join("\n")
|
||||
: "No material requirements";
|
||||
const escapedTooltip = tooltip
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("\"", """)
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">");
|
||||
moduleLines.push(`<span title="${escapedTooltip}">${moduleId} (${progress}% constructing)</span>`);
|
||||
}
|
||||
|
||||
return moduleLines.length > 0 ? moduleLines.join("<br>") : "none";
|
||||
}
|
||||
|
||||
function formatStorageClassLabel(storageClass: string): string {
|
||||
return storageClass
|
||||
.split("-")
|
||||
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function formatStorageUsage(storageUsage: { storageClass: string; used: number; capacity: number }[]): string {
|
||||
if (storageUsage.length === 0) {
|
||||
return "none";
|
||||
}
|
||||
|
||||
return storageUsage
|
||||
.map((entry) => {
|
||||
const percentUsed = entry.capacity > 0 ? Math.round((entry.used / entry.capacity) * 100) : 0;
|
||||
return `${formatStorageClassLabel(entry.storageClass)} ${percentUsed}% used (${entry.used.toFixed(0)} / ${entry.capacity.toFixed(0)})`;
|
||||
})
|
||||
.join("<br>");
|
||||
}
|
||||
|
||||
function renderSystemOwnership(world: WorldState, systemId: string): string {
|
||||
const claims = [...world.claims.values()].filter((claim) =>
|
||||
claim.systemId === systemId && claim.state !== "destroyed");
|
||||
@@ -108,7 +196,6 @@ export function updateDetailPanel(
|
||||
return;
|
||||
}
|
||||
const parent = describeSelectionParent(selected);
|
||||
const fuelStored = inventoryAmount(ship.inventory, "fuel");
|
||||
const cargoUsed = ship.cargoItemId
|
||||
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
||||
: 0;
|
||||
@@ -130,7 +217,6 @@ export function updateDetailPanel(
|
||||
</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>Speed ${formatShipSpeed(ship)}</p>
|
||||
@@ -145,17 +231,12 @@ 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 moduleList = formatModuleListWithConstruction(world, station.id, station.installedModules, station.currentProcesses);
|
||||
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 stationInventory = station.inventory;
|
||||
const stationStorageUsage = formatStorageUsage(station.storageUsage);
|
||||
const stationProcesses = station.currentProcesses;
|
||||
const stationProcessingHtml = stationProcesses.length > 0
|
||||
? stationProcesses.map((process) => `
|
||||
@@ -175,14 +256,12 @@ export function updateDetailPanel(
|
||||
<p>${station.category} · ${station.systemId}</p>
|
||||
<p>Parent ${parent}</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>Modules ${moduleList}</p>
|
||||
<p>Storage ${stationStorageUsage}</p>
|
||||
<p>Inventory ${formatInventory(stationInventory)}</p>
|
||||
<p>History available in the separate history window.</p>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -332,8 +332,6 @@ function describeControllerTask(taskKind: string): string {
|
||||
return "docking";
|
||||
case "unload":
|
||||
return "transfer";
|
||||
case "refuel":
|
||||
return "refuel";
|
||||
case "deliver-construction":
|
||||
return "material delivery";
|
||||
case "build-construction-site":
|
||||
|
||||
Reference in New Issue
Block a user