|
|
|
|
@@ -85,6 +85,99 @@ const factionMap = computed(() =>
|
|
|
|
|
new Map(gmStore.factions.map((f) => [f.id, f.label])),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function titleCaseToken(value: string | null | undefined) {
|
|
|
|
|
if (!value) return "—";
|
|
|
|
|
return value
|
|
|
|
|
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
|
|
|
.replace(/[-_]+/g, " ")
|
|
|
|
|
.replace(/\s+/g, " ")
|
|
|
|
|
.trim()
|
|
|
|
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function compactNumber(value: number | null | undefined, digits = 0) {
|
|
|
|
|
if (value == null || Number.isNaN(value)) return "—";
|
|
|
|
|
return value.toFixed(digits);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function compactRate(value: number | null | undefined) {
|
|
|
|
|
if (value == null || Number.isNaN(value)) return "—";
|
|
|
|
|
const sign = value > 0.001 ? "+" : "";
|
|
|
|
|
return `${sign}${value.toFixed(2)}/s`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getLeadObjective(faction: FactionSnapshot) {
|
|
|
|
|
return [...(faction.objectives ?? [])]
|
|
|
|
|
.sort((left, right) => right.priority - left.priority)
|
|
|
|
|
.find((objective) => objective.state !== "Complete" && objective.state !== "Cancelled")
|
|
|
|
|
?? faction.objectives?.[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getLeadStep(faction: FactionSnapshot) {
|
|
|
|
|
const objective = getLeadObjective(faction);
|
|
|
|
|
return [...(objective?.steps ?? [])]
|
|
|
|
|
.sort((left, right) => right.priority - left.priority)
|
|
|
|
|
.find((step) => step.status !== "Complete" && step.status !== "Cancelled")
|
|
|
|
|
?? objective?.steps?.[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getLeadTask(faction: FactionSnapshot) {
|
|
|
|
|
return [...(faction.issuedTasks ?? [])]
|
|
|
|
|
.sort((left, right) => right.priority - left.priority)
|
|
|
|
|
.find((task) => task.state !== "Complete" && task.state !== "Cancelled")
|
|
|
|
|
?? faction.issuedTasks?.[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function describeCommodityState(faction: FactionSnapshot, itemId: string, shortLabel: string) {
|
|
|
|
|
const signal = faction.blackboard?.commoditySignals.find((entry) => entry.itemId === itemId);
|
|
|
|
|
if (!signal) return `${shortLabel} —`;
|
|
|
|
|
return `${shortLabel} ${titleCaseToken(signal.level)} ${compactRate(signal.projectedNetRatePerSecond)}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function describeFactionStrategicState(faction: FactionSnapshot) {
|
|
|
|
|
const objective = getLeadObjective(faction);
|
|
|
|
|
if (!objective) return "No objectives";
|
|
|
|
|
return `${titleCaseToken(objective.kind)} · ${titleCaseToken(objective.state)}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function describeFactionLeadStep(faction: FactionSnapshot) {
|
|
|
|
|
const step = getLeadStep(faction);
|
|
|
|
|
if (!step) return "No steps";
|
|
|
|
|
const target = step.commodityId ?? step.moduleId ?? step.targetFactionId ?? step.targetSiteId;
|
|
|
|
|
return target
|
|
|
|
|
? `${titleCaseToken(step.kind)} · ${titleCaseToken(step.status)} · ${target}`
|
|
|
|
|
: `${titleCaseToken(step.kind)} · ${titleCaseToken(step.status)}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function describeFactionLeadTask(faction: FactionSnapshot) {
|
|
|
|
|
const task = getLeadTask(faction);
|
|
|
|
|
if (!task) return "No tasks";
|
|
|
|
|
const target = task.shipRole ?? task.commodityId ?? task.moduleId ?? task.targetFactionId ?? task.targetSiteId;
|
|
|
|
|
return target
|
|
|
|
|
? `${titleCaseToken(task.kind)} · ${titleCaseToken(task.state)} · ${target}`
|
|
|
|
|
: `${titleCaseToken(task.kind)} · ${titleCaseToken(task.state)}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function describeFactionPriority(faction: FactionSnapshot) {
|
|
|
|
|
const priority = [...(faction.strategicPriorities ?? [])]
|
|
|
|
|
.sort((left, right) => right.priority - left.priority)[0];
|
|
|
|
|
return priority ? `${titleCaseToken(priority.goalName)} · ${compactNumber(priority.priority, 0)}` : "—";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function describeFactionEconomy(faction: FactionSnapshot) {
|
|
|
|
|
return [
|
|
|
|
|
describeCommodityState(faction, "refinedmetals", "RM"),
|
|
|
|
|
describeCommodityState(faction, "hullparts", "HP"),
|
|
|
|
|
describeCommodityState(faction, "claytronics", "CL"),
|
|
|
|
|
].join(" | ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function describeFactionThreat(faction: FactionSnapshot) {
|
|
|
|
|
const blackboard = faction.blackboard;
|
|
|
|
|
if (!blackboard) return "—";
|
|
|
|
|
return `Enemy ships ${blackboard.enemyShipCount} · stations ${blackboard.enemyStationCount}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Ships table ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
type ShipRow = {
|
|
|
|
|
@@ -94,7 +187,10 @@ type ShipRow = {
|
|
|
|
|
faction: string;
|
|
|
|
|
system: string;
|
|
|
|
|
state: string;
|
|
|
|
|
objective: string;
|
|
|
|
|
behavior: string;
|
|
|
|
|
phase: string;
|
|
|
|
|
action: string;
|
|
|
|
|
task: string;
|
|
|
|
|
cargo: number;
|
|
|
|
|
health: number;
|
|
|
|
|
@@ -107,9 +203,12 @@ const shipRows = computed<ShipRow[]>(() =>
|
|
|
|
|
class: s.class,
|
|
|
|
|
faction: factionMap.value.get(s.factionId) ?? s.factionId,
|
|
|
|
|
system: s.systemId,
|
|
|
|
|
state: s.state,
|
|
|
|
|
behavior: s.defaultBehaviorKind + (s.behaviorPhase ? ` · ${s.behaviorPhase}` : ""),
|
|
|
|
|
task: s.controllerTaskKind,
|
|
|
|
|
state: titleCaseToken(s.state),
|
|
|
|
|
objective: s.commanderObjective ? titleCaseToken(s.commanderObjective) : "—",
|
|
|
|
|
behavior: titleCaseToken(s.defaultBehaviorKind),
|
|
|
|
|
phase: s.behaviorPhase ? titleCaseToken(s.behaviorPhase) : "—",
|
|
|
|
|
action: s.currentAction ? `${s.currentAction.label} ${Math.round(s.currentAction.progress * 100)}%` : "—",
|
|
|
|
|
task: titleCaseToken(s.controllerTaskKind),
|
|
|
|
|
cargo: s.inventory.reduce((sum, e) => sum + e.amount, 0),
|
|
|
|
|
health: Math.round(s.health),
|
|
|
|
|
})),
|
|
|
|
|
@@ -121,8 +220,11 @@ const shipColumns = [
|
|
|
|
|
shipColumnHelper.accessor("class", { header: "Class" }),
|
|
|
|
|
shipColumnHelper.accessor("faction", { header: "Faction" }),
|
|
|
|
|
shipColumnHelper.accessor("system", { header: "System" }),
|
|
|
|
|
shipColumnHelper.accessor("state", { header: "State" }),
|
|
|
|
|
shipColumnHelper.accessor("state", { header: "Ship State" }),
|
|
|
|
|
shipColumnHelper.accessor("objective", { header: "Commander Objective" }),
|
|
|
|
|
shipColumnHelper.accessor("behavior", { header: "Behavior" }),
|
|
|
|
|
shipColumnHelper.accessor("phase", { header: "Phase" }),
|
|
|
|
|
shipColumnHelper.accessor("action", { header: "Current Action" }),
|
|
|
|
|
shipColumnHelper.accessor("task", { header: "Task" }),
|
|
|
|
|
shipColumnHelper.accessor("cargo", { header: "Cargo" }),
|
|
|
|
|
shipColumnHelper.accessor("health", { header: "HP" }),
|
|
|
|
|
@@ -130,7 +232,7 @@ const shipColumns = [
|
|
|
|
|
|
|
|
|
|
const shipFilter = ref("");
|
|
|
|
|
const shipSorting = ref<SortingState>([]);
|
|
|
|
|
const shipOrder = useColumnOrder(["label", "class", "faction", "system", "state", "behavior", "task", "cargo", "health"]);
|
|
|
|
|
const shipOrder = useColumnOrder(["label", "class", "faction", "system", "state", "objective", "behavior", "phase", "action", "task", "cargo", "health"]);
|
|
|
|
|
|
|
|
|
|
const shipTable = useVueTable({
|
|
|
|
|
get data() { return shipRows.value; },
|
|
|
|
|
@@ -156,11 +258,14 @@ type StationRow = {
|
|
|
|
|
id: string;
|
|
|
|
|
label: string;
|
|
|
|
|
category: string;
|
|
|
|
|
objective: string;
|
|
|
|
|
faction: string;
|
|
|
|
|
system: string;
|
|
|
|
|
process: string;
|
|
|
|
|
workforce: string;
|
|
|
|
|
docked: string;
|
|
|
|
|
orders: number;
|
|
|
|
|
cargo: number;
|
|
|
|
|
population: number;
|
|
|
|
|
modules: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -169,11 +274,16 @@ const stationRows = computed<StationRow[]>(() =>
|
|
|
|
|
id: s.id,
|
|
|
|
|
label: s.label,
|
|
|
|
|
category: s.category,
|
|
|
|
|
objective: titleCaseToken(s.objective),
|
|
|
|
|
faction: factionMap.value.get(s.factionId) ?? s.factionId,
|
|
|
|
|
system: s.systemId,
|
|
|
|
|
process: s.currentProcesses.length > 0
|
|
|
|
|
? s.currentProcesses.map((process) => `${process.label} ${Math.round(process.progress * 100)}%`).join(" | ")
|
|
|
|
|
: "Idle",
|
|
|
|
|
workforce: `${Math.round(s.population)} / ${Math.round(s.populationCapacity)} · ${Math.round(s.workforceEffectiveRatio * 100)}%`,
|
|
|
|
|
docked: `${s.dockedShips} / ${s.dockingPads}`,
|
|
|
|
|
orders: s.marketOrderIds.length,
|
|
|
|
|
cargo: s.inventory.reduce((sum, e) => sum + e.amount, 0),
|
|
|
|
|
population: Math.round(s.population),
|
|
|
|
|
modules: s.installedModules.length,
|
|
|
|
|
})),
|
|
|
|
|
);
|
|
|
|
|
@@ -182,17 +292,20 @@ const stationColumnHelper = createColumnHelper<StationRow>();
|
|
|
|
|
const stationColumns = [
|
|
|
|
|
stationColumnHelper.accessor("label", { header: "Name" }),
|
|
|
|
|
stationColumnHelper.accessor("category", { header: "Category" }),
|
|
|
|
|
stationColumnHelper.accessor("objective", { header: "Objective" }),
|
|
|
|
|
stationColumnHelper.accessor("faction", { header: "Faction" }),
|
|
|
|
|
stationColumnHelper.accessor("system", { header: "System" }),
|
|
|
|
|
stationColumnHelper.accessor("process", { header: "Production" }),
|
|
|
|
|
stationColumnHelper.accessor("workforce", { header: "Workforce" }),
|
|
|
|
|
stationColumnHelper.accessor("docked", { header: "Docked" }),
|
|
|
|
|
stationColumnHelper.accessor("orders", { header: "Orders" }),
|
|
|
|
|
stationColumnHelper.accessor("cargo", { header: "Cargo" }),
|
|
|
|
|
stationColumnHelper.accessor("population", { header: "Pop" }),
|
|
|
|
|
stationColumnHelper.accessor("modules", { header: "Modules" }),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const stationFilter = ref("");
|
|
|
|
|
const stationSorting = ref<SortingState>([]);
|
|
|
|
|
const stationOrder = useColumnOrder(["label", "category", "faction", "system", "docked", "cargo", "population", "modules"]);
|
|
|
|
|
const stationOrder = useColumnOrder(["label", "category", "objective", "faction", "system", "process", "workforce", "docked", "orders", "cargo", "modules"]);
|
|
|
|
|
|
|
|
|
|
const stationTable = useVueTable({
|
|
|
|
|
get data() { return stationRows.value; },
|
|
|
|
|
@@ -217,32 +330,41 @@ const stationTable = useVueTable({
|
|
|
|
|
type FactionRow = {
|
|
|
|
|
id: string;
|
|
|
|
|
label: string;
|
|
|
|
|
planCycle: number;
|
|
|
|
|
priority: string;
|
|
|
|
|
strategicState: string;
|
|
|
|
|
leadStep: string;
|
|
|
|
|
leadTask: string;
|
|
|
|
|
warReadiness: string;
|
|
|
|
|
economy: string;
|
|
|
|
|
threat: string;
|
|
|
|
|
fleets: string;
|
|
|
|
|
systems: string;
|
|
|
|
|
credits: number;
|
|
|
|
|
population: number;
|
|
|
|
|
military: number;
|
|
|
|
|
miners: number;
|
|
|
|
|
transport: number;
|
|
|
|
|
constructors: number;
|
|
|
|
|
systems: string;
|
|
|
|
|
ore: number;
|
|
|
|
|
shipsBuilt: number;
|
|
|
|
|
shipsLost: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const factionRows = computed<FactionRow[]>(() =>
|
|
|
|
|
gmStore.factions.map((f) => {
|
|
|
|
|
const gs = f.goapState;
|
|
|
|
|
const assessment = f.strategicAssessment;
|
|
|
|
|
const blackboard = f.blackboard;
|
|
|
|
|
return {
|
|
|
|
|
id: f.id,
|
|
|
|
|
label: f.label,
|
|
|
|
|
planCycle: blackboard?.planCycle ?? 0,
|
|
|
|
|
priority: describeFactionPriority(f),
|
|
|
|
|
strategicState: describeFactionStrategicState(f),
|
|
|
|
|
leadStep: describeFactionLeadStep(f),
|
|
|
|
|
leadTask: describeFactionLeadTask(f),
|
|
|
|
|
warReadiness: `Industry ${blackboard?.hasWarIndustrySupplyChain ? "yes" : "no"} · Shipyard ${blackboard?.hasShipyard ? "yes" : "no"}${blackboard?.hasActiveExpansionProject ? ` · Expanding ${blackboard.activeExpansionCommodityId ?? blackboard.activeExpansionModuleId ?? "site"}` : ""}`,
|
|
|
|
|
economy: describeFactionEconomy(f),
|
|
|
|
|
threat: describeFactionThreat(f),
|
|
|
|
|
fleets: assessment ? `M ${assessment.militaryShipCount}/${blackboard?.targetWarshipCount ?? 0} · Mn ${assessment.minerShipCount} · Tr ${assessment.transportShipCount} · Cn ${assessment.constructorShipCount}` : "—",
|
|
|
|
|
systems: assessment ? `${assessment.controlledSystemCount} / ${assessment.targetSystemCount}` : "—",
|
|
|
|
|
credits: Math.round(f.credits),
|
|
|
|
|
population: Math.round(f.populationTotal),
|
|
|
|
|
military: gs?.militaryShipCount ?? 0,
|
|
|
|
|
miners: gs?.minerShipCount ?? 0,
|
|
|
|
|
transport: gs?.transportShipCount ?? 0,
|
|
|
|
|
constructors: gs?.constructorShipCount ?? 0,
|
|
|
|
|
systems: gs ? `${gs.controlledSystemCount} / ${gs.targetSystemCount}` : "—",
|
|
|
|
|
ore: gs ? Math.round(gs.oreStockpile) : 0,
|
|
|
|
|
shipsBuilt: f.shipsBuilt,
|
|
|
|
|
shipsLost: f.shipsLost,
|
|
|
|
|
};
|
|
|
|
|
@@ -252,21 +374,25 @@ const factionRows = computed<FactionRow[]>(() =>
|
|
|
|
|
const factionColumnHelper = createColumnHelper<FactionRow>();
|
|
|
|
|
const factionColumns = [
|
|
|
|
|
factionColumnHelper.accessor("label", { header: "Faction" }),
|
|
|
|
|
factionColumnHelper.accessor("planCycle", { header: "Cycle" }),
|
|
|
|
|
factionColumnHelper.accessor("priority", { header: "Top Priority" }),
|
|
|
|
|
factionColumnHelper.accessor("strategicState", { header: "Objective" }),
|
|
|
|
|
factionColumnHelper.accessor("leadStep", { header: "Lead Step" }),
|
|
|
|
|
factionColumnHelper.accessor("leadTask", { header: "Issued Task" }),
|
|
|
|
|
factionColumnHelper.accessor("warReadiness", { header: "Campaign State" }),
|
|
|
|
|
factionColumnHelper.accessor("economy", { header: "Economy" }),
|
|
|
|
|
factionColumnHelper.accessor("threat", { header: "Threat" }),
|
|
|
|
|
factionColumnHelper.accessor("fleets", { header: "Fleets" }),
|
|
|
|
|
factionColumnHelper.accessor("systems", { header: "Systems" }),
|
|
|
|
|
factionColumnHelper.accessor("credits", { header: "Credits" }),
|
|
|
|
|
factionColumnHelper.accessor("population", { header: "Pop" }),
|
|
|
|
|
factionColumnHelper.accessor("military", { header: "Military" }),
|
|
|
|
|
factionColumnHelper.accessor("miners", { header: "Miners" }),
|
|
|
|
|
factionColumnHelper.accessor("transport", { header: "Transport" }),
|
|
|
|
|
factionColumnHelper.accessor("constructors", { header: "Constructors" }),
|
|
|
|
|
factionColumnHelper.accessor("systems", { header: "Systems" }),
|
|
|
|
|
factionColumnHelper.accessor("ore", { header: "Ore" }),
|
|
|
|
|
factionColumnHelper.accessor("shipsBuilt", { header: "Built" }),
|
|
|
|
|
factionColumnHelper.accessor("shipsLost", { header: "Lost" }),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const factionFilter = ref("");
|
|
|
|
|
const factionSorting = ref<SortingState>([]);
|
|
|
|
|
const factionOrder = useColumnOrder(["label", "credits", "population", "military", "miners", "transport", "constructors", "systems", "ore", "shipsBuilt", "shipsLost"]);
|
|
|
|
|
const factionOrder = useColumnOrder(["label", "planCycle", "priority", "strategicState", "leadStep", "leadTask", "warReadiness", "economy", "threat", "fleets", "systems", "credits", "population", "shipsBuilt", "shipsLost"]);
|
|
|
|
|
|
|
|
|
|
const factionTable = useVueTable({
|
|
|
|
|
get data() { return factionRows.value; },
|
|
|
|
|
@@ -350,7 +476,7 @@ function isStationSelected(id: string) {
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<GmWindow
|
|
|
|
|
title="Ships"
|
|
|
|
|
title="AI States"
|
|
|
|
|
:initial-width="980"
|
|
|
|
|
:initial-height="560"
|
|
|
|
|
:initial-x="80"
|
|
|
|
|
|