Refactor runtime bootstrap and ship control flows

This commit is contained in:
2026-04-03 01:12:26 -04:00
parent 0bb72bee35
commit 706e1cda8f
129 changed files with 9588 additions and 3548 deletions

View File

@@ -14,16 +14,22 @@ import {
upsertPlayerDirective,
upsertPlayerPolicy,
} from "../../api";
import { getShipBehaviorLabel, getShipOrderLabel } from "../../shipAutomationPresentation";
import type { PlayerFactionSnapshot } from "../../contractsPlayerFaction";
import { useGmStore } from "../../ui/stores/gmStore";
import { useAuthStore } from "../../ui/stores/authStore";
import { usePlayerFactionStore } from "../../ui/stores/playerFactionStore";
import { useShipAutomationCatalogStore } from "../../ui/stores/shipAutomationCatalogStore";
import { useViewerSelectionStore } from "../../ui/stores/viewerSelection";
const gmStore = useGmStore();
const authStore = useAuthStore();
const playerStore = usePlayerFactionStore();
const automationStore = useShipAutomationCatalogStore();
const selectionStore = useViewerSelectionStore();
const { selectedEntityId, selectedEntityKind } = storeToRefs(selectionStore);
const { playerFaction } = storeToRefs(playerStore);
const { session } = storeToRefs(authStore);
const statusMessage = ref("");
const errorMessage = ref("");
@@ -129,56 +135,22 @@ const selectedOrganizationSummary = computed(() => {
return lines;
});
const behaviorOptions = [
"idle",
"hold-position",
"follow-ship",
"patrol",
"police",
"protect-position",
"protect-ship",
"protect-station",
"local-auto-mine",
"advanced-auto-mine",
"expert-auto-mine",
"local-auto-trade",
"advanced-auto-trade",
"fill-shortages",
"find-build-tasks",
"revisit-known-stations",
"supply-fleet",
"dock-and-wait",
"fly-and-wait",
"fly-to-object",
"auto-salvage",
"repeat-orders",
];
const behaviorOptions = computed(() =>
(automationStore.catalog?.behaviors ?? [])
.filter((entry) => entry.supportStatus !== "InternalOnly")
.map((entry) => entry.id),
);
onMounted(async () => {
if (playerStore.playerFaction) {
return;
}
try {
setPlayerSnapshot(await fetchPlayerFaction());
} catch {
// The world snapshot path normally provides this; ignore here and let the parent lifecycle recover.
}
await automationStore.load();
await loadPlayerSnapshotIfNeeded();
});
const orderOptions = [
"move",
"dock-at-station",
"dock-and-wait",
"fly-and-wait",
"fly-to-object",
"follow-ship",
"trade-route",
"mine-and-deliver",
"build-at-site",
"attack-target",
"hold-position",
];
const orderOptions = computed(() =>
(automationStore.catalog?.orders ?? [])
.filter((entry) => entry.supportStatus !== "InternalOnly")
.map((entry) => entry.id),
);
const orgForm = reactive({
kind: "fleet",
@@ -278,7 +250,7 @@ const behaviorForm = reactive({
homeSystemId: "",
areaSystemId: "",
targetEntityId: "",
preferredItemId: "",
itemId: "",
preferredNodeId: "",
preferredConstructionSiteId: "",
preferredModuleId: "",
@@ -353,6 +325,15 @@ watch(player, (value) => {
}
}, { immediate: true });
watch(session, async (value) => {
if (!value) {
playerStore.setPlayerFaction(null);
return;
}
await loadPlayerSnapshotIfNeeded();
});
watch(selectedShip, (ship) => {
if (!ship) {
return;
@@ -362,7 +343,7 @@ watch(selectedShip, (ship) => {
behaviorForm.homeSystemId = ship.defaultBehavior.homeSystemId ?? ship.systemId;
behaviorForm.areaSystemId = ship.defaultBehavior.areaSystemId ?? ship.systemId;
behaviorForm.targetEntityId = ship.defaultBehavior.targetEntityId ?? "";
behaviorForm.preferredItemId = ship.defaultBehavior.preferredItemId ?? "";
behaviorForm.itemId = ship.defaultBehavior.itemId ?? "";
behaviorForm.preferredNodeId = ship.defaultBehavior.preferredNodeId ?? "";
behaviorForm.preferredConstructionSiteId = ship.defaultBehavior.preferredConstructionSiteId ?? "";
behaviorForm.preferredModuleId = ship.defaultBehavior.preferredModuleId ?? "";
@@ -426,6 +407,18 @@ function setPlayerSnapshot(snapshot: PlayerFactionSnapshot) {
playerStore.setPlayerFaction(snapshot);
}
async function loadPlayerSnapshotIfNeeded() {
if (!authStore.isAuthenticated || playerStore.playerFaction) {
return;
}
try {
setPlayerSnapshot(await fetchPlayerFaction());
} catch {
// Player domain may not exist yet for the current world; leave the panel empty.
}
}
async function runAction(action: () => Promise<void>, successMessage: string) {
busy.value = true;
errorMessage.value = "";
@@ -618,7 +611,7 @@ async function submitDirectBehavior() {
homeStationId: null,
areaSystemId: behaviorForm.areaSystemId || null,
targetEntityId: behaviorForm.targetEntityId || null,
preferredItemId: behaviorForm.preferredItemId || null,
itemId: behaviorForm.itemId || null,
preferredNodeId: behaviorForm.preferredNodeId || null,
preferredConstructionSiteId: behaviorForm.preferredConstructionSiteId || null,
preferredModuleId: behaviorForm.preferredModuleId || null,
@@ -690,14 +683,14 @@ async function submitDirectOrder() {
<h4>Selected Asset Control</h4>
<div v-if="selectedShip || selectedStation" class="player-card-list">
<div class="player-card">
<strong>{{ selectedShip?.label ?? selectedStation?.label }}</strong>
<strong>{{ selectedShip?.name ?? selectedStation?.label }}</strong>
<span>{{ selectedShip ? "Ship" : "Station" }} · {{ selectedShip?.systemId ?? selectedStation?.systemId }}</span>
<span v-if="selectedAssignment">Assignment {{ selectedAssignment.role }} · {{ selectedAssignment.directiveId ?? "no directive" }}</span>
<span v-else>No player assignment</span>
</div>
<div v-if="selectedShip" class="player-card">
<strong>Behavior</strong>
<span>{{ titleCase(selectedShip.defaultBehavior.kind) }}</span>
<span>{{ getShipBehaviorLabel(selectedShip.defaultBehavior.kind) }}</span>
<span>Orders {{ selectedShip.orderQueue.length }} · Plan {{ selectedShip.activePlan?.kind ?? "none" }}</span>
<span>Command {{ titleCase(selectedShip.controlSourceKind) }}<template v-if="selectedShip.controlReason"> · {{ selectedShip.controlReason }}</template></span>
<span v-if="selectedShip.lastReplanReason">Replan {{ selectedShip.lastReplanReason }}</span>
@@ -708,11 +701,11 @@ async function submitDirectOrder() {
<form v-if="selectedShip" class="player-form" @submit.prevent="submitDirectBehavior">
<h5>Direct Ship Behavior</h5>
<label><span>Behavior</span><select v-model="behaviorForm.kind"><option v-for="option in behaviorOptions" :key="option" :value="option">{{ option }}</option></select></label>
<label><span>Behavior</span><select v-model="behaviorForm.kind"><option v-for="option in behaviorOptions" :key="option" :value="option">{{ getShipBehaviorLabel(option) }}</option></select></label>
<label><span>Home System</span><input v-model="behaviorForm.homeSystemId" type="text"></label>
<label><span>Area System</span><input v-model="behaviorForm.areaSystemId" type="text"></label>
<label><span>Target Entity</span><input v-model="behaviorForm.targetEntityId" type="text"></label>
<label><span>Preferred Item</span><input v-model="behaviorForm.preferredItemId" type="text"></label>
<label><span>Item</span><input v-model="behaviorForm.itemId" type="text"></label>
<label><span>Preferred Node</span><input v-model="behaviorForm.preferredNodeId" type="text"></label>
<label><span>Construction Site</span><input v-model="behaviorForm.preferredConstructionSiteId" type="text"></label>
<label><span>Module</span><input v-model="behaviorForm.preferredModuleId" type="text"></label>
@@ -725,7 +718,7 @@ async function submitDirectOrder() {
<form v-if="selectedShip" class="player-form" @submit.prevent="submitDirectOrder">
<h5>Direct Ship Order</h5>
<label><span>Order</span><select v-model="orderForm.kind"><option v-for="option in orderOptions" :key="option" :value="option">{{ option }}</option></select></label>
<label><span>Order</span><select v-model="orderForm.kind"><option v-for="option in orderOptions" :key="option" :value="option">{{ getShipOrderLabel(option) }}</option></select></label>
<label><span>Label</span><input v-model="orderForm.label" type="text"></label>
<label><span>Target System</span><input v-model="orderForm.targetSystemId" type="text"></label>
<label><span>Target Entity</span><input v-model="orderForm.targetEntityId" type="text"></label>
@@ -793,7 +786,7 @@ async function submitDirectOrder() {
<form class="player-form" @submit.prevent="submitCoreAutomation">
<h5>Core Automation</h5>
<label><span>Label</span><input v-model="automationForm.label" type="text"></label>
<label><span>Behavior</span><select v-model="automationForm.behaviorKind"><option v-for="option in behaviorOptions" :key="option" :value="option">{{ option }}</option></select></label>
<label><span>Behavior</span><select v-model="automationForm.behaviorKind"><option v-for="option in behaviorOptions" :key="option" :value="option">{{ getShipBehaviorLabel(option) }}</option></select></label>
<label><span>Order Staging</span><input v-model="automationForm.stagingOrderKind" type="text"></label>
<label><span>Preferred Item</span><input v-model="automationForm.preferredItemId" type="text"></label>
<label><span>System Range</span><input v-model.number="automationForm.maxSystemRange" type="number" min="0" step="1"></label>
@@ -852,7 +845,7 @@ async function submitDirectOrder() {
<label><span>Kind</span><input v-model="directiveForm.kind" type="text"></label>
<label><span>Scope Kind</span><input v-model="directiveForm.scopeKind" type="text"></label>
<label><span>Scope Id</span><input v-model="directiveForm.scopeId" type="text" :placeholder="selectedEntityId ?? ''"></label>
<label><span>Behavior</span><select v-model="directiveForm.behaviorKind"><option v-for="option in behaviorOptions" :key="option" :value="option">{{ option }}</option></select></label>
<label><span>Behavior</span><select v-model="directiveForm.behaviorKind"><option v-for="option in behaviorOptions" :key="option" :value="option">{{ getShipBehaviorLabel(option) }}</option></select></label>
<label><span>Staging Order</span><input v-model="directiveForm.stagingOrderKind" type="text"></label>
<label><span>Target System</span><input v-model="directiveForm.targetSystemId" type="text"></label>
<label><span>Target Entity</span><input v-model="directiveForm.targetEntityId" type="text"></label>