ui/phase-14: rename planet end-to-end + order read-back

Wires the first end-to-end command through the full pipeline:
inspector rename action → local order draft → user.games.order
submit → optimistic overlay on map / inspector → server hydration
on cache miss via the new user.games.order.get message type.

Backend: GET /api/v1/user/games/{id}/orders forwards to engine
GET /api/v1/order. Gateway parses the engine PUT response into the
extended UserGamesOrderResponse FBS envelope and adds
executeUserGamesOrderGet for the read-back path. Frontend ports
ValidateTypeName to TS, lands the inline rename editor + Submit
button, and exposes a renderedReport context so consumers see the
overlay-applied snapshot.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-09 11:50:09 +02:00
parent 381e41b325
commit f80c623a74
86 changed files with 7505 additions and 138 deletions
+42
View File
@@ -10,6 +10,11 @@
// sets for `LocalPlanet`, `OtherPlanet`, `UninhabitedPlanet`, and
// `UnidentifiedPlanet`, and the wrapper preserves that nullability
// instead of inventing zero values.
//
// Phase 14 adds `applyOrderOverlay`: every applied / submitting
// rename in the local draft swaps the planet name on the rendered
// report so the player sees their intent reflected immediately,
// without waiting for the next turn cutoff.
import { Builder, ByteBuffer } from "flatbuffers";
@@ -19,6 +24,7 @@ import {
GameReportRequest,
Report,
} from "../proto/galaxy/fbs/report";
import type { CommandStatus, OrderCommand } from "../sync/order-types";
const MESSAGE_TYPE = "user.games.report";
@@ -205,6 +211,42 @@ export function uuidToHiLo(value: string): [bigint, bigint] {
return [hi, lo];
}
/**
* applyOrderOverlay returns a copy of `report` with every applied or
* still-in-flight (`submitting`) command from `commands` projected on
* top. Phase 14 understands `planetRename` only — every other variant
* passes through. The function is pure: callers re-derive the
* overlay whenever the draft or the report change.
*
* `statuses` maps command id → status. Entries with `applied` or
* `submitting` participate in the overlay; everything else (`draft`,
* `valid`, `invalid`, `rejected`) is treated as "not yet committed
* by the player" and skipped. This matches the order-composer model:
* the player sees their own committed intent, not their unfinished
* edits.
*/
export function applyOrderOverlay(
report: GameReport,
commands: OrderCommand[],
statuses: Record<string, CommandStatus>,
): GameReport {
if (commands.length === 0) return report;
let mutatedPlanets: ReportPlanet[] | null = null;
for (const cmd of commands) {
const status = statuses[cmd.id];
if (status !== "applied" && status !== "submitting") continue;
if (cmd.kind !== "planetRename") continue;
const idx = report.planets.findIndex((p) => p.number === cmd.planetNumber);
if (idx < 0) continue;
if (mutatedPlanets === null) {
mutatedPlanets = [...report.planets];
}
mutatedPlanets[idx] = { ...mutatedPlanets[idx]!, name: cmd.name };
}
if (mutatedPlanets === null) return report;
return { ...report, planets: mutatedPlanets };
}
function decodeErrorMessage(payload: Uint8Array): { code: string; message: string } {
if (payload.length === 0) {
return { code: "internal_error", message: "empty error payload" };
+12 -1
View File
@@ -36,8 +36,15 @@ preference the store already manages.
SELECTION_CONTEXT_KEY,
type SelectionStore,
} from "$lib/selection.svelte";
import {
RENDERED_REPORT_CONTEXT_KEY,
type RenderedReportSource,
} from "$lib/rendered-report.svelte";
const store = getContext<GameStateStore | undefined>(GAME_STATE_CONTEXT_KEY);
const renderedReport = getContext<RenderedReportSource | undefined>(
RENDERED_REPORT_CONTEXT_KEY,
);
const selection = getContext<SelectionStore | undefined>(SELECTION_CONTEXT_KEY);
let canvasEl: HTMLCanvasElement | null = $state(null);
@@ -52,7 +59,11 @@ preference the store already manages.
let mounted = false;
$effect(() => {
const report = store?.report;
// Read the overlay-applied report so the map labels reflect
// pending renames immediately. Falls back to raw report when
// the rendered source is missing (e.g. component used outside
// the in-game shell layout).
const report = renderedReport?.report ?? store?.report;
const status = store?.status ?? "idle";
// Track the wrap mode so the renderer remounts when Phase 29's
// toggle UI flips it; the read here also subscribes the effect.
@@ -0,0 +1,34 @@
// Exposes the per-game `GalaxyClient` instance through a Svelte
// context so command-driven UI (the order-tab submit button,
// later phases' inspector actions) can issue gateway calls without
// re-instantiating the client. The handle is intentionally a thin
// reactive wrapper: the layout populates `client` after the boot
// `Promise.all` resolves, and consumers read the latest value
// through the getter — `null` while the boot is in flight, set to
// the live client once the keypair / gateway public key are loaded.
import type { GalaxyClient } from "../api/galaxy-client";
/**
* GALAXY_CLIENT_CONTEXT_KEY is the Svelte context key the in-game
* shell layout uses to expose its bound `GalaxyClient` to
* descendants. The order-tab submit button reads this to call
* `submitOrder`.
*/
export const GALAXY_CLIENT_CONTEXT_KEY = Symbol("galaxy-client");
export interface GalaxyClientHandle {
readonly client: GalaxyClient | null;
}
export class GalaxyClientHolder implements GalaxyClientHandle {
#client: GalaxyClient | null = $state(null);
get client(): GalaxyClient | null {
return this.#client;
}
set(client: GalaxyClient | null): void {
this.#client = client;
}
}
+8 -1
View File
@@ -41,10 +41,17 @@ export class GameStateStore {
report: GameReport | null = $state(null);
wrapMode: WrapMode = $state("torus");
error: string | null = $state(null);
/**
* currentTurn mirrors the engine's turn number for the running
* game (lifted from the lobby record on `setGame`). Phase 14
* exposes it so the layout can pass it to
* `OrderDraftStore.hydrateFromServer` after both stores boot;
* later phases (history mode, calc) will read it directly.
*/
currentTurn = $state(0);
private client: GalaxyClient | null = null;
private cache: Cache | null = null;
private currentTurn = 0;
private destroyed = false;
private visibilityListener: (() => void) | null = null;
+22
View File
@@ -120,6 +120,17 @@ const en = {
"game.sidebar.empty.inspector": "select an object on the map",
"game.sidebar.empty.order": "order is empty",
"game.sidebar.order.command_delete": "delete",
"game.sidebar.order.submit": "submit",
"game.sidebar.order.submit_in_flight": "submitting…",
"game.sidebar.order.status.draft": "draft",
"game.sidebar.order.status.valid": "valid",
"game.sidebar.order.status.invalid": "invalid",
"game.sidebar.order.status.submitting": "submitting",
"game.sidebar.order.status.applied": "applied",
"game.sidebar.order.status.rejected": "rejected",
"game.sidebar.order.label.placeholder": "{label}",
"game.sidebar.order.label.planet_rename": "rename planet {planet} → {name}",
"game.sidebar.order.error.batch_failed": "submit failed: {message}",
"game.bottom_tabs.map": "map",
"game.bottom_tabs.calc": "calc",
"game.bottom_tabs.order": "order",
@@ -144,6 +155,17 @@ const en = {
"game.inspector.planet.production_none": "none",
"game.inspector.planet.unidentified_no_data": "no data — only the location is known",
"game.inspector.sheet_close": "close",
"game.inspector.planet.action.rename": "rename",
"game.inspector.planet.rename.title": "rename planet",
"game.inspector.planet.rename.confirm": "save",
"game.inspector.planet.rename.cancel": "cancel",
"game.inspector.planet.rename.invalid.empty": "name cannot be empty",
"game.inspector.planet.rename.invalid.too_long": "name is too long (30 characters max)",
"game.inspector.planet.rename.invalid.starts_with_special": "name cannot start with a special character",
"game.inspector.planet.rename.invalid.ends_with_special": "name cannot end with a special character",
"game.inspector.planet.rename.invalid.consecutive_specials": "too many special characters in a row",
"game.inspector.planet.rename.invalid.whitespace": "name cannot contain spaces",
"game.inspector.planet.rename.invalid.disallowed_character": "name contains disallowed characters",
} as const;
export default en;
+22
View File
@@ -121,6 +121,17 @@ const ru: Record<keyof typeof en, string> = {
"game.sidebar.empty.inspector": "выберите объект на карте",
"game.sidebar.empty.order": "приказ пуст",
"game.sidebar.order.command_delete": "удалить",
"game.sidebar.order.submit": "отправить",
"game.sidebar.order.submit_in_flight": "отправка…",
"game.sidebar.order.status.draft": "черновик",
"game.sidebar.order.status.valid": "готова",
"game.sidebar.order.status.invalid": "ошибка",
"game.sidebar.order.status.submitting": "отправка",
"game.sidebar.order.status.applied": "принята",
"game.sidebar.order.status.rejected": "отклонена",
"game.sidebar.order.label.placeholder": "{label}",
"game.sidebar.order.label.planet_rename": "переименовать планету {planet} → {name}",
"game.sidebar.order.error.batch_failed": "ошибка отправки: {message}",
"game.bottom_tabs.map": "карта",
"game.bottom_tabs.calc": "калк",
"game.bottom_tabs.order": "приказ",
@@ -145,6 +156,17 @@ const ru: Record<keyof typeof en, string> = {
"game.inspector.planet.production_none": "не задано",
"game.inspector.planet.unidentified_no_data": "нет данных — известно только местоположение",
"game.inspector.sheet_close": "закрыть",
"game.inspector.planet.action.rename": "переименовать",
"game.inspector.planet.rename.title": "переименование планеты",
"game.inspector.planet.rename.confirm": "сохранить",
"game.inspector.planet.rename.cancel": "отмена",
"game.inspector.planet.rename.invalid.empty": "имя не может быть пустым",
"game.inspector.planet.rename.invalid.too_long": "имя слишком длинное (максимум 30 символов)",
"game.inspector.planet.rename.invalid.starts_with_special": "имя не может начинаться со спецсимвола",
"game.inspector.planet.rename.invalid.ends_with_special": "имя не может заканчиваться спецсимволом",
"game.inspector.planet.rename.invalid.consecutive_specials": "слишком много спецсимволов подряд",
"game.inspector.planet.rename.invalid.whitespace": "имя не может содержать пробелы",
"game.inspector.planet.rename.invalid.disallowed_character": "имя содержит недопустимые символы",
};
export default ru;
+208 -14
View File
@@ -1,23 +1,29 @@
<!--
Phase 13 read-only planet inspector. Renders the documented field
set for the planet kind in question:
Planet inspector. Renders the documented field set for each planet
kind (local / other / uninhabited / unidentified) and exposes a
Rename action on owned (`local`) planets that opens an inline
editor. The editor runs the same `validateEntityName` rules as the
server-side validator (parity with `pkg/util/string.go`) and, on
confirm, appends a `planetRename` command to the local order draft
through the `OrderDraftStore` provided via context.
- `local` / `other` carry the full economy: name, owner (other only),
coordinates, size, population, colonists, industry, both stockpiles,
natural resources, current production, free production potential.
- `uninhabited` keeps name, coordinates, size, both stockpiles, and
natural resources — the engine does not project industry or
population for unowned planets.
- `unidentified` is reduced to coordinates plus a no-data hint.
The component is purely presentational: the parent supplies a
`ReportPlanet` snapshot resolved from `GameStateStore`, no store
lookups happen here. Phase 14 will extend the same component with a
`Rename` action; the read-only layout stays the structural baseline.
The read-only path stays unchanged for non-`local` planets. The
inline editor lives directly inside this component per PLAN.md
Phase 14 — a separate file would be over-abstraction for one input
field with five buttons.
-->
<script lang="ts">
import { getContext, tick } from "svelte";
import type { ReportPlanet } from "../../api/game-state";
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
import {
ORDER_DRAFT_CONTEXT_KEY,
OrderDraftStore,
} from "../../sync/order-draft.svelte";
import {
validateEntityName,
type EntityNameInvalidReason,
} from "$lib/util/entity-name";
type Props = {
planet: ReportPlanet;
@@ -31,6 +37,34 @@ lookups happen here. Phase 14 will extend the same component with a
unidentified: "game.inspector.planet.kind.unidentified",
};
const invalidReasonKeyMap: Record<EntityNameInvalidReason, TranslationKey> = {
empty: "game.inspector.planet.rename.invalid.empty",
too_long: "game.inspector.planet.rename.invalid.too_long",
starts_with_special:
"game.inspector.planet.rename.invalid.starts_with_special",
ends_with_special: "game.inspector.planet.rename.invalid.ends_with_special",
consecutive_specials:
"game.inspector.planet.rename.invalid.consecutive_specials",
whitespace: "game.inspector.planet.rename.invalid.whitespace",
disallowed_character:
"game.inspector.planet.rename.invalid.disallowed_character",
};
const draft = getContext<OrderDraftStore | undefined>(
ORDER_DRAFT_CONTEXT_KEY,
);
let renameOpen = $state(false);
let renameInput = $state("");
let inputEl: HTMLInputElement | null = $state(null);
const renameValidation = $derived(validateEntityName(renameInput));
const renameInvalidMessage = $derived(
renameValidation.ok
? ""
: i18n.t(invalidReasonKeyMap[renameValidation.reason]),
);
const kindLabel = $derived(i18n.t(kindKeyMap[planet.kind]));
const coordinates = $derived(
`(${formatNumber(planet.x)}, ${formatNumber(planet.y)})`,
@@ -47,6 +81,44 @@ lookups happen here. Phase 14 will extend the same component with a
}
return value;
}
async function openRename(): Promise<void> {
renameInput = planet.name;
renameOpen = true;
await tick();
inputEl?.focus();
inputEl?.select();
}
function cancelRename(): void {
renameOpen = false;
renameInput = "";
}
async function confirmRename(): Promise<void> {
const result = validateEntityName(renameInput);
if (!result.ok || draft === undefined) return;
await draft.add({
kind: "planetRename",
id: crypto.randomUUID(),
planetNumber: planet.number,
name: result.value,
});
renameOpen = false;
renameInput = "";
}
function onKeyDown(event: KeyboardEvent): void {
if (event.key === "Escape") {
event.preventDefault();
cancelRename();
return;
}
if (event.key === "Enter") {
event.preventDefault();
void confirmRename();
}
}
</script>
<section
@@ -60,8 +132,65 @@ lookups happen here. Phase 14 will extend the same component with a
{#if planet.kind !== "unidentified"}
<h3 class="name" data-testid="inspector-planet-name">{planet.name}</h3>
{/if}
{#if planet.kind === "local" && !renameOpen}
<button
type="button"
class="action"
data-testid="inspector-planet-rename-action"
onclick={openRename}
>
{i18n.t("game.inspector.planet.action.rename")}
</button>
{/if}
</header>
{#if planet.kind === "local" && renameOpen}
<div class="rename" data-testid="inspector-planet-rename">
<label class="rename-label" for="planet-rename-input">
{i18n.t("game.inspector.planet.rename.title")}
</label>
<input
id="planet-rename-input"
type="text"
class="rename-input"
data-testid="inspector-planet-rename-input"
bind:value={renameInput}
bind:this={inputEl}
onkeydown={onKeyDown}
aria-invalid={renameValidation.ok ? "false" : "true"}
aria-describedby={renameValidation.ok ? undefined : "planet-rename-error"}
/>
{#if !renameValidation.ok}
<p
id="planet-rename-error"
class="rename-error"
data-testid="inspector-planet-rename-error"
>
{renameInvalidMessage}
</p>
{/if}
<div class="rename-actions">
<button
type="button"
class="rename-cancel"
data-testid="inspector-planet-rename-cancel"
onclick={cancelRename}
>
{i18n.t("game.inspector.planet.rename.cancel")}
</button>
<button
type="button"
class="rename-confirm"
data-testid="inspector-planet-rename-confirm"
disabled={!renameValidation.ok || draft === undefined}
onclick={() => void confirmRename()}
>
{i18n.t("game.inspector.planet.rename.confirm")}
</button>
</div>
</div>
{/if}
<dl class="fields">
{#if planet.kind === "other" && planet.owner !== null}
<div class="field" data-testid="inspector-planet-field-owner">
@@ -194,4 +323,69 @@ lookups happen here. Phase 14 will extend the same component with a
color: #888;
font-size: 0.85rem;
}
.action {
align-self: flex-start;
margin-top: 0.25rem;
font: inherit;
font-size: 0.85rem;
padding: 0.2rem 0.55rem;
background: transparent;
color: #aab;
border: 1px solid #2a3150;
border-radius: 3px;
cursor: pointer;
}
.action:hover {
color: #e8eaf6;
border-color: #6d8cff;
}
.rename {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.rename-label {
font-size: 0.85rem;
color: #aab;
}
.rename-input {
font: inherit;
padding: 0.3rem 0.5rem;
background: #0a0e1a;
color: #e8eaf6;
border: 1px solid #2a3150;
border-radius: 3px;
}
.rename-input[aria-invalid="true"] {
border-color: #d97a7a;
}
.rename-error {
margin: 0;
font-size: 0.8rem;
color: #d97a7a;
}
.rename-actions {
display: flex;
gap: 0.4rem;
}
.rename-cancel,
.rename-confirm {
font: inherit;
font-size: 0.85rem;
padding: 0.25rem 0.65rem;
background: transparent;
color: #aab;
border: 1px solid #2a3150;
border-radius: 3px;
cursor: pointer;
}
.rename-confirm:not(:disabled):hover,
.rename-cancel:hover {
color: #e8eaf6;
border-color: #6d8cff;
}
.rename-confirm:disabled {
cursor: not-allowed;
opacity: 0.5;
}
</style>
@@ -0,0 +1,52 @@
// Provides a derived view of the server `GameReport` overlaid with
// the player's local order draft. Every consumer that needs to
// render the player's current intent (inspector, map, mobile sheet)
// subscribes through this context instead of reading `gameState.report`
// directly.
//
// Lifetime matches the in-game shell layout: one source per game,
// rebuilt on layout remount. The source itself is a thin reactive
// wrapper — the actual overlay computation lives in
// `applyOrderOverlay` (api/game-state.ts) and runs lazily on every
// access through the `report` getter.
import {
applyOrderOverlay,
type GameReport,
} from "../api/game-state";
import type { GameStateStore } from "./game-state.svelte";
import type { OrderDraftStore } from "../sync/order-draft.svelte";
/**
* RENDERED_REPORT_CONTEXT_KEY is the Svelte context key the in-game
* shell layout uses to expose a `RenderedReportSource` instance to
* descendants. Consumers read the latest overlay through `source.report`
* (a reactive getter) and re-render when the underlying stores
* change.
*/
export const RENDERED_REPORT_CONTEXT_KEY = Symbol("rendered-report");
export interface RenderedReportSource {
readonly report: GameReport | null;
}
/**
* createRenderedReportSource binds the live `GameStateStore` and
* `OrderDraftStore` to a getter that returns the overlay-applied
* report on every read. The getter is reactive: Svelte tracks the
* underlying `$state` accesses inside `applyOrderOverlay`, so any
* change to the report or the draft re-runs every dependent
* `$derived` block.
*/
export function createRenderedReportSource(
gameState: GameStateStore,
orderDraft: OrderDraftStore,
): RenderedReportSource {
return {
get report(): GameReport | null {
const raw = gameState.report;
if (raw === null) return null;
return applyOrderOverlay(raw, orderDraft.commands, orderDraft.statuses);
},
};
}
@@ -14,18 +14,18 @@ from the Phase 10 stub.
<script lang="ts">
import { getContext } from "svelte";
import { i18n } from "$lib/i18n/index.svelte";
import {
GAME_STATE_CONTEXT_KEY,
type GameStateStore,
} from "$lib/game-state.svelte";
import {
SELECTION_CONTEXT_KEY,
type SelectionStore,
} from "$lib/selection.svelte";
import {
RENDERED_REPORT_CONTEXT_KEY,
type RenderedReportSource,
} from "$lib/rendered-report.svelte";
import Planet from "$lib/inspectors/planet.svelte";
const gameState = getContext<GameStateStore | undefined>(
GAME_STATE_CONTEXT_KEY,
const renderedReport = getContext<RenderedReportSource | undefined>(
RENDERED_REPORT_CONTEXT_KEY,
);
const selection = getContext<SelectionStore | undefined>(
SELECTION_CONTEXT_KEY,
@@ -34,7 +34,7 @@ from the Phase 10 stub.
const selectedPlanet = $derived.by(() => {
const sel = selection?.selected;
if (sel === undefined || sel === null || sel.kind !== "planet") return null;
const report = gameState?.report;
const report = renderedReport?.report;
if (report === undefined || report === null) return null;
return report.planets.find((p) => p.number === sel.id) ?? null;
});
+202 -19
View File
@@ -1,31 +1,143 @@
<!--
Order composer tool. Resolves the per-game `OrderDraftStore` from
context (set by `routes/games/[id]/+layout.svelte`) and renders the
draft as a vertical, top-to-bottom command list. Empty state shows
the i18n empty-state copy; non-empty state shows an ordered list of
rows, each with a stable `data-testid` plus a per-row delete button.
Order composer tool. Resolves the per-game `OrderDraftStore`,
`GameStateStore`, and `GalaxyClient` from context (all set by
`routes/games/[id]/+layout.svelte`) and renders the local draft as
a vertical list with per-row status, a delete button, and a Submit
button at the bottom.
Phase 12 has no UI for adding commands — Phase 14 lands the first
end-to-end command (`planetRename`) and the inspector affordance
that pushes it into the draft. Tests exercise the skeleton through
`__galaxyDebug.seedOrderDraft` (Playwright) and via direct store
construction (Vitest).
Phase 14 wires the first end-to-end command: clicking Submit calls
`submitOrder` for every entry in `valid` status, flips the in-flight
rows to `submitting`, then merges the per-command verdict back into
the draft once the gateway responds. The optimistic overlay in
`renderedReport` continues to show the player's intent while the
order is in flight, so the inspector and the map reflect the new
name even before the server applies it at turn cutoff.
Tests exercise the skeleton through `__galaxyDebug.seedOrderDraft`
(Playwright) and via direct store / mocked-client construction
(Vitest).
-->
<script lang="ts">
import { getContext } from "svelte";
import { i18n } from "$lib/i18n/index.svelte";
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
import {
ORDER_DRAFT_CONTEXT_KEY,
OrderDraftStore,
} from "../../sync/order-draft.svelte";
import {
GAME_STATE_CONTEXT_KEY,
type GameStateStore,
} from "$lib/game-state.svelte";
import {
GALAXY_CLIENT_CONTEXT_KEY,
type GalaxyClientHandle,
} from "$lib/galaxy-client-context.svelte";
import type { CommandStatus, OrderCommand } from "../../sync/order-types";
import { submitOrder } from "../../sync/submit";
const draft = getContext<OrderDraftStore | undefined>(
ORDER_DRAFT_CONTEXT_KEY,
);
const gameState = getContext<GameStateStore | undefined>(
GAME_STATE_CONTEXT_KEY,
);
const galaxyClient = getContext<GalaxyClientHandle | undefined>(
GALAXY_CLIENT_CONTEXT_KEY,
);
function describe(cmd: { kind: string; label?: string }): string {
if (cmd.kind === "placeholder") return cmd.label ?? cmd.kind;
return cmd.kind;
const statusKeyMap: Record<CommandStatus, TranslationKey> = {
draft: "game.sidebar.order.status.draft",
valid: "game.sidebar.order.status.valid",
invalid: "game.sidebar.order.status.invalid",
submitting: "game.sidebar.order.status.submitting",
applied: "game.sidebar.order.status.applied",
rejected: "game.sidebar.order.status.rejected",
};
let submitInFlight = $state(false);
let submitError = $state<string | null>(null);
const submittable = $derived.by(() => {
if (draft === undefined) return [] as OrderCommand[];
return draft.commands.filter(
(cmd) => draft.statuses[cmd.id] === "valid",
);
});
const hasInvalid = $derived.by(() => {
if (draft === undefined) return false;
return draft.commands.some((cmd) => draft.statuses[cmd.id] === "invalid");
});
const submitDisabled = $derived(
draft === undefined ||
galaxyClient === undefined ||
galaxyClient.client === null ||
submitInFlight ||
submittable.length === 0 ||
hasInvalid,
);
function describe(cmd: OrderCommand): string {
switch (cmd.kind) {
case "placeholder":
return i18n.t("game.sidebar.order.label.placeholder", {
label: cmd.label,
});
case "planetRename":
return i18n.t("game.sidebar.order.label.planet_rename", {
planet: String(cmd.planetNumber),
name: cmd.name,
});
}
}
function statusOf(cmd: OrderCommand): CommandStatus {
return draft?.statuses[cmd.id] ?? "draft";
}
async function submit(): Promise<void> {
if (
draft === undefined ||
galaxyClient === undefined ||
galaxyClient.client === null ||
gameState === undefined
)
return;
if (submittable.length === 0 || hasInvalid) return;
const ids = submittable.map((cmd) => cmd.id);
const snapshot = submittable.slice();
submitInFlight = true;
submitError = null;
draft.markSubmitting(ids);
try {
const result = await submitOrder(
galaxyClient.client,
gameState.gameId,
snapshot,
{ updatedAt: draft.updatedAt },
);
if (result.ok) {
draft.applyResults({
results: result.results,
updatedAt: result.updatedAt,
});
if (gameState !== undefined) {
await gameState.refresh();
}
} else {
draft.markRejected(ids);
submitError = i18n.t("game.sidebar.order.error.batch_failed", {
message: result.message,
});
}
} catch (err) {
draft.revertSubmittingToValid();
submitError =
err instanceof Error ? err.message : "submit failed";
} finally {
submitInFlight = false;
}
}
</script>
@@ -38,11 +150,22 @@ construction (Vitest).
{:else}
<ol class="commands" data-testid="order-list">
{#each draft.commands as cmd, index (cmd.id)}
<li class="command" data-testid="order-command-{index}">
{@const status = statusOf(cmd)}
<li
class="command"
data-testid="order-command-{index}"
data-command-status={status}
>
<span class="index" aria-hidden="true">{index + 1}.</span>
<span class="label" data-testid="order-command-label-{index}">
{describe(cmd)}
</span>
<span
class="status status-{status}"
data-testid="order-command-status-{index}"
>
{i18n.t(statusKeyMap[status])}
</span>
<button
type="button"
class="delete"
@@ -54,6 +177,20 @@ construction (Vitest).
</li>
{/each}
</ol>
<button
type="button"
class="submit"
data-testid="order-submit"
disabled={submitDisabled}
onclick={() => void submit()}
>
{submitInFlight
? i18n.t("game.sidebar.order.submit_in_flight")
: i18n.t("game.sidebar.order.submit")}
</button>
{#if submitError !== null}
<p class="error" data-testid="order-submit-error">{submitError}</p>
{/if}
{/if}
</section>
@@ -72,14 +209,15 @@ construction (Vitest).
}
.commands {
list-style: none;
margin: 0;
margin: 0 0 0.75rem;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.command {
display: flex;
display: grid;
grid-template-columns: auto 1fr auto auto;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 0.5rem;
@@ -88,17 +226,40 @@ construction (Vitest).
border-radius: 4px;
}
.index {
min-width: 1.5rem;
color: #aab;
font-variant-numeric: tabular-nums;
}
.label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 0.1rem 0.4rem;
border-radius: 999px;
border: 1px solid #2a3150;
color: #aab;
}
.status-applied {
color: #8be9a3;
border-color: #2f6d3f;
}
.status-rejected {
color: #d97a7a;
border-color: #6d2f2f;
}
.status-invalid {
color: #d6b86c;
border-color: #6d562f;
}
.status-submitting {
color: #6d8cff;
border-color: #2f3f6d;
}
.delete {
font: inherit;
font-size: 0.85rem;
@@ -113,4 +274,26 @@ construction (Vitest).
color: #e8eaf6;
border-color: #6d8cff;
}
.submit {
font: inherit;
font-size: 0.9rem;
padding: 0.4rem 1rem;
background: #1d2440;
color: #e8eaf6;
border: 1px solid #2a3150;
border-radius: 3px;
cursor: pointer;
}
.submit:not(:disabled):hover {
border-color: #6d8cff;
}
.submit:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.error {
margin: 0.5rem 0 0;
color: #d97a7a;
font-size: 0.85rem;
}
</style>
+98
View File
@@ -0,0 +1,98 @@
// TS port of `pkg/util/string.go.ValidateTypeName` — every entity
// name (planet, ship class, science, …) the player edits goes
// through this validator before reaching the order draft, so the
// client-side check is identical to the server-side one. A
// locally-valid name is always accepted at the wire level; an
// invalid name never produces a network round-trip.
const MAX_LENGTH = 30;
const ALLOWED_SPECIALS = new Set<string>("!@#$%^*-_=+~()[]{}");
const SPECIAL_RUN_LIMIT = 2;
/**
* EntityNameInvalidReason is the closed enumeration of reasons a
* name can fail validation. The values are stable identifiers so
* the inspector tooltip and the order-tab status row can map them
* to localised copy via `i18n.t("game.order.invalid." + reason)`.
*/
export type EntityNameInvalidReason =
| "empty"
| "too_long"
| "starts_with_special"
| "ends_with_special"
| "consecutive_specials"
| "whitespace"
| "disallowed_character";
export type EntityNameValidation =
| { ok: true; value: string }
| { ok: false; reason: EntityNameInvalidReason };
/**
* validateEntityName mirrors `ValidateTypeName` exactly: the input
* is trimmed, must be non-empty, must fit in 30 runes, must not
* start or end with a special character, and must contain only
* letters, digits, combining marks, or the allowed specials with at
* most two in a row. Returns the trimmed value on success or a
* structured reason on failure.
*/
export function validateEntityName(input: string): EntityNameValidation {
const trimmed = input.trim();
if (trimmed.length === 0) {
return { ok: false, reason: "empty" };
}
const runes = Array.from(trimmed);
if (runes.length > MAX_LENGTH) {
return { ok: false, reason: "too_long" };
}
const first = runes[0]!;
const last = runes[runes.length - 1]!;
if (ALLOWED_SPECIALS.has(first)) {
return { ok: false, reason: "starts_with_special" };
}
if (ALLOWED_SPECIALS.has(last)) {
return { ok: false, reason: "ends_with_special" };
}
let specialRun = 0;
for (const rune of runes) {
if (isWhitespace(rune)) {
return { ok: false, reason: "whitespace" };
}
if (isLetter(rune) || isDigit(rune) || isCombiningMark(rune)) {
specialRun = 0;
continue;
}
if (ALLOWED_SPECIALS.has(rune)) {
specialRun += 1;
if (specialRun > SPECIAL_RUN_LIMIT) {
return { ok: false, reason: "consecutive_specials" };
}
continue;
}
return { ok: false, reason: "disallowed_character" };
}
return { ok: true, value: trimmed };
}
function isWhitespace(rune: string): boolean {
// Matches Go's `unicode.IsSpace`.
return /\s/u.test(rune);
}
function isLetter(rune: string): boolean {
return /\p{L}/u.test(rune);
}
function isDigit(rune: string): boolean {
return /\p{N}/u.test(rune);
}
function isCombiningMark(rune: string): boolean {
return /\p{M}/u.test(rune);
}
+40
View File
@@ -0,0 +1,40 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
export { CommandFleetMerge, CommandFleetMergeT } from './order/command-fleet-merge.js';
export { CommandFleetSend, CommandFleetSendT } from './order/command-fleet-send.js';
export { CommandItem, CommandItemT } from './order/command-item.js';
export { CommandPayload } from './order/command-payload.js';
export { CommandPlanetProduce, CommandPlanetProduceT } from './order/command-planet-produce.js';
export { CommandPlanetRename, CommandPlanetRenameT } from './order/command-planet-rename.js';
export { CommandPlanetRouteRemove, CommandPlanetRouteRemoveT } from './order/command-planet-route-remove.js';
export { CommandPlanetRouteSet, CommandPlanetRouteSetT } from './order/command-planet-route-set.js';
export { CommandRaceQuit, CommandRaceQuitT } from './order/command-race-quit.js';
export { CommandRaceRelation, CommandRaceRelationT } from './order/command-race-relation.js';
export { CommandRaceVote, CommandRaceVoteT } from './order/command-race-vote.js';
export { CommandScienceCreate, CommandScienceCreateT } from './order/command-science-create.js';
export { CommandScienceRemove, CommandScienceRemoveT } from './order/command-science-remove.js';
export { CommandShipClassCreate, CommandShipClassCreateT } from './order/command-ship-class-create.js';
export { CommandShipClassMerge, CommandShipClassMergeT } from './order/command-ship-class-merge.js';
export { CommandShipClassRemove, CommandShipClassRemoveT } from './order/command-ship-class-remove.js';
export { CommandShipGroupBreak, CommandShipGroupBreakT } from './order/command-ship-group-break.js';
export { CommandShipGroupDismantle, CommandShipGroupDismantleT } from './order/command-ship-group-dismantle.js';
export { CommandShipGroupJoinFleet, CommandShipGroupJoinFleetT } from './order/command-ship-group-join-fleet.js';
export { CommandShipGroupLoad, CommandShipGroupLoadT } from './order/command-ship-group-load.js';
export { CommandShipGroupMerge, CommandShipGroupMergeT } from './order/command-ship-group-merge.js';
export { CommandShipGroupSend, CommandShipGroupSendT } from './order/command-ship-group-send.js';
export { CommandShipGroupTransfer, CommandShipGroupTransferT } from './order/command-ship-group-transfer.js';
export { CommandShipGroupUnload, CommandShipGroupUnloadT } from './order/command-ship-group-unload.js';
export { CommandShipGroupUpgrade, CommandShipGroupUpgradeT } from './order/command-ship-group-upgrade.js';
export { PlanetProduction } from './order/planet-production.js';
export { PlanetRouteLoadType } from './order/planet-route-load-type.js';
export { Relation } from './order/relation.js';
export { ShipGroupCargo } from './order/ship-group-cargo.js';
export { ShipGroupUpgradeTech } from './order/ship-group-upgrade-tech.js';
export { UserGamesCommand, UserGamesCommandT } from './order/user-games-command.js';
export { UserGamesCommandResponse, UserGamesCommandResponseT } from './order/user-games-command-response.js';
export { UserGamesOrder, UserGamesOrderT } from './order/user-games-order.js';
export { UserGamesOrderGet, UserGamesOrderGetT } from './order/user-games-order-get.js';
export { UserGamesOrderGetResponse, UserGamesOrderGetResponseT } from './order/user-games-order-get-response.js';
export { UserGamesOrderResponse, UserGamesOrderResponseT } from './order/user-games-order-response.js';
@@ -0,0 +1,95 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandFleetMerge implements flatbuffers.IUnpackableObject<CommandFleetMergeT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandFleetMerge {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandFleetMerge(bb:flatbuffers.ByteBuffer, obj?:CommandFleetMerge):CommandFleetMerge {
return (obj || new CommandFleetMerge()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandFleetMerge(bb:flatbuffers.ByteBuffer, obj?:CommandFleetMerge):CommandFleetMerge {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandFleetMerge()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
target():string|null
target(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
target(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandFleetMerge(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, nameOffset, 0);
}
static addTarget(builder:flatbuffers.Builder, targetOffset:flatbuffers.Offset) {
builder.addFieldOffset(1, targetOffset, 0);
}
static endCommandFleetMerge(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandFleetMerge(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset, targetOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandFleetMerge.startCommandFleetMerge(builder);
CommandFleetMerge.addName(builder, nameOffset);
CommandFleetMerge.addTarget(builder, targetOffset);
return CommandFleetMerge.endCommandFleetMerge(builder);
}
unpack(): CommandFleetMergeT {
return new CommandFleetMergeT(
this.name(),
this.target()
);
}
unpackTo(_o: CommandFleetMergeT): void {
_o.name = this.name();
_o.target = this.target();
}
}
export class CommandFleetMergeT implements flatbuffers.IGeneratedObject {
constructor(
public name: string|Uint8Array|null = null,
public target: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const name = (this.name !== null ? builder.createString(this.name!) : 0);
const target = (this.target !== null ? builder.createString(this.target!) : 0);
return CommandFleetMerge.createCommandFleetMerge(builder,
name,
target
);
}
}
@@ -0,0 +1,92 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandFleetSend implements flatbuffers.IUnpackableObject<CommandFleetSendT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandFleetSend {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandFleetSend(bb:flatbuffers.ByteBuffer, obj?:CommandFleetSend):CommandFleetSend {
return (obj || new CommandFleetSend()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandFleetSend(bb:flatbuffers.ByteBuffer, obj?:CommandFleetSend):CommandFleetSend {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandFleetSend()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
destination():bigint {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
static startCommandFleetSend(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, nameOffset, 0);
}
static addDestination(builder:flatbuffers.Builder, destination:bigint) {
builder.addFieldInt64(1, destination, BigInt('0'));
}
static endCommandFleetSend(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandFleetSend(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset, destination:bigint):flatbuffers.Offset {
CommandFleetSend.startCommandFleetSend(builder);
CommandFleetSend.addName(builder, nameOffset);
CommandFleetSend.addDestination(builder, destination);
return CommandFleetSend.endCommandFleetSend(builder);
}
unpack(): CommandFleetSendT {
return new CommandFleetSendT(
this.name(),
this.destination()
);
}
unpackTo(_o: CommandFleetSendT): void {
_o.name = this.name();
_o.destination = this.destination();
}
}
export class CommandFleetSendT implements flatbuffers.IGeneratedObject {
constructor(
public name: string|Uint8Array|null = null,
public destination: bigint = BigInt('0')
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const name = (this.name !== null ? builder.createString(this.name!) : 0);
return CommandFleetSend.createCommandFleetSend(builder,
name,
this.destination
);
}
}
@@ -0,0 +1,170 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { CommandFleetMerge, CommandFleetMergeT } from './command-fleet-merge.js';
import { CommandFleetSend, CommandFleetSendT } from './command-fleet-send.js';
import { CommandPayload, unionToCommandPayload, unionListToCommandPayload } from './command-payload.js';
import { CommandPlanetProduce, CommandPlanetProduceT } from './command-planet-produce.js';
import { CommandPlanetRename, CommandPlanetRenameT } from './command-planet-rename.js';
import { CommandPlanetRouteRemove, CommandPlanetRouteRemoveT } from './command-planet-route-remove.js';
import { CommandPlanetRouteSet, CommandPlanetRouteSetT } from './command-planet-route-set.js';
import { CommandRaceQuit, CommandRaceQuitT } from './command-race-quit.js';
import { CommandRaceRelation, CommandRaceRelationT } from './command-race-relation.js';
import { CommandRaceVote, CommandRaceVoteT } from './command-race-vote.js';
import { CommandScienceCreate, CommandScienceCreateT } from './command-science-create.js';
import { CommandScienceRemove, CommandScienceRemoveT } from './command-science-remove.js';
import { CommandShipClassCreate, CommandShipClassCreateT } from './command-ship-class-create.js';
import { CommandShipClassMerge, CommandShipClassMergeT } from './command-ship-class-merge.js';
import { CommandShipClassRemove, CommandShipClassRemoveT } from './command-ship-class-remove.js';
import { CommandShipGroupBreak, CommandShipGroupBreakT } from './command-ship-group-break.js';
import { CommandShipGroupDismantle, CommandShipGroupDismantleT } from './command-ship-group-dismantle.js';
import { CommandShipGroupJoinFleet, CommandShipGroupJoinFleetT } from './command-ship-group-join-fleet.js';
import { CommandShipGroupLoad, CommandShipGroupLoadT } from './command-ship-group-load.js';
import { CommandShipGroupMerge, CommandShipGroupMergeT } from './command-ship-group-merge.js';
import { CommandShipGroupSend, CommandShipGroupSendT } from './command-ship-group-send.js';
import { CommandShipGroupTransfer, CommandShipGroupTransferT } from './command-ship-group-transfer.js';
import { CommandShipGroupUnload, CommandShipGroupUnloadT } from './command-ship-group-unload.js';
import { CommandShipGroupUpgrade, CommandShipGroupUpgradeT } from './command-ship-group-upgrade.js';
export class CommandItem implements flatbuffers.IUnpackableObject<CommandItemT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandItem {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandItem(bb:flatbuffers.ByteBuffer, obj?:CommandItem):CommandItem {
return (obj || new CommandItem()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandItem(bb:flatbuffers.ByteBuffer, obj?:CommandItem):CommandItem {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandItem()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
cmdId():string|null
cmdId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
cmdId(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
cmdApplied():boolean|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : null;
}
cmdErrorCode():bigint|null {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : null;
}
payloadType():CommandPayload {
const offset = this.bb!.__offset(this.bb_pos, 10);
return offset ? this.bb!.readUint8(this.bb_pos + offset) : CommandPayload.NONE;
}
payload<T extends flatbuffers.Table>(obj:any):any|null {
const offset = this.bb!.__offset(this.bb_pos, 12);
return offset ? this.bb!.__union(obj, this.bb_pos + offset) : null;
}
static startCommandItem(builder:flatbuffers.Builder) {
builder.startObject(5);
}
static addCmdId(builder:flatbuffers.Builder, cmdIdOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, cmdIdOffset, 0);
}
static addCmdApplied(builder:flatbuffers.Builder, cmdApplied:boolean) {
builder.addFieldInt8(1, +cmdApplied, null);
}
static addCmdErrorCode(builder:flatbuffers.Builder, cmdErrorCode:bigint) {
builder.addFieldInt64(2, cmdErrorCode, null);
}
static addPayloadType(builder:flatbuffers.Builder, payloadType:CommandPayload) {
builder.addFieldInt8(3, payloadType, CommandPayload.NONE);
}
static addPayload(builder:flatbuffers.Builder, payloadOffset:flatbuffers.Offset) {
builder.addFieldOffset(4, payloadOffset, 0);
}
static endCommandItem(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
builder.requiredField(offset, 12) // payload
return offset;
}
static createCommandItem(builder:flatbuffers.Builder, cmdIdOffset:flatbuffers.Offset, cmdApplied:boolean|null, cmdErrorCode:bigint|null, payloadType:CommandPayload, payloadOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandItem.startCommandItem(builder);
CommandItem.addCmdId(builder, cmdIdOffset);
if (cmdApplied !== null)
CommandItem.addCmdApplied(builder, cmdApplied);
if (cmdErrorCode !== null)
CommandItem.addCmdErrorCode(builder, cmdErrorCode);
CommandItem.addPayloadType(builder, payloadType);
CommandItem.addPayload(builder, payloadOffset);
return CommandItem.endCommandItem(builder);
}
unpack(): CommandItemT {
return new CommandItemT(
this.cmdId(),
this.cmdApplied(),
this.cmdErrorCode(),
this.payloadType(),
(() => {
const temp = unionToCommandPayload(this.payloadType(), this.payload.bind(this));
if(temp === null) { return null; }
return temp.unpack()
})()
);
}
unpackTo(_o: CommandItemT): void {
_o.cmdId = this.cmdId();
_o.cmdApplied = this.cmdApplied();
_o.cmdErrorCode = this.cmdErrorCode();
_o.payloadType = this.payloadType();
_o.payload = (() => {
const temp = unionToCommandPayload(this.payloadType(), this.payload.bind(this));
if(temp === null) { return null; }
return temp.unpack()
})();
}
}
export class CommandItemT implements flatbuffers.IGeneratedObject {
constructor(
public cmdId: string|Uint8Array|null = null,
public cmdApplied: boolean|null = null,
public cmdErrorCode: bigint|null = null,
public payloadType: CommandPayload = CommandPayload.NONE,
public payload: CommandFleetMergeT|CommandFleetSendT|CommandPlanetProduceT|CommandPlanetRenameT|CommandPlanetRouteRemoveT|CommandPlanetRouteSetT|CommandRaceQuitT|CommandRaceRelationT|CommandRaceVoteT|CommandScienceCreateT|CommandScienceRemoveT|CommandShipClassCreateT|CommandShipClassMergeT|CommandShipClassRemoveT|CommandShipGroupBreakT|CommandShipGroupDismantleT|CommandShipGroupJoinFleetT|CommandShipGroupLoadT|CommandShipGroupMergeT|CommandShipGroupSendT|CommandShipGroupTransferT|CommandShipGroupUnloadT|CommandShipGroupUpgradeT|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const cmdId = (this.cmdId !== null ? builder.createString(this.cmdId!) : 0);
const payload = builder.createObjectOffset(this.payload);
return CommandItem.createCommandItem(builder,
cmdId,
this.cmdApplied,
this.cmdErrorCode,
this.payloadType,
payload
);
}
}
@@ -0,0 +1,122 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import { CommandFleetMerge, CommandFleetMergeT } from './command-fleet-merge.js';
import { CommandFleetSend, CommandFleetSendT } from './command-fleet-send.js';
import { CommandPlanetProduce, CommandPlanetProduceT } from './command-planet-produce.js';
import { CommandPlanetRename, CommandPlanetRenameT } from './command-planet-rename.js';
import { CommandPlanetRouteRemove, CommandPlanetRouteRemoveT } from './command-planet-route-remove.js';
import { CommandPlanetRouteSet, CommandPlanetRouteSetT } from './command-planet-route-set.js';
import { CommandRaceQuit, CommandRaceQuitT } from './command-race-quit.js';
import { CommandRaceRelation, CommandRaceRelationT } from './command-race-relation.js';
import { CommandRaceVote, CommandRaceVoteT } from './command-race-vote.js';
import { CommandScienceCreate, CommandScienceCreateT } from './command-science-create.js';
import { CommandScienceRemove, CommandScienceRemoveT } from './command-science-remove.js';
import { CommandShipClassCreate, CommandShipClassCreateT } from './command-ship-class-create.js';
import { CommandShipClassMerge, CommandShipClassMergeT } from './command-ship-class-merge.js';
import { CommandShipClassRemove, CommandShipClassRemoveT } from './command-ship-class-remove.js';
import { CommandShipGroupBreak, CommandShipGroupBreakT } from './command-ship-group-break.js';
import { CommandShipGroupDismantle, CommandShipGroupDismantleT } from './command-ship-group-dismantle.js';
import { CommandShipGroupJoinFleet, CommandShipGroupJoinFleetT } from './command-ship-group-join-fleet.js';
import { CommandShipGroupLoad, CommandShipGroupLoadT } from './command-ship-group-load.js';
import { CommandShipGroupMerge, CommandShipGroupMergeT } from './command-ship-group-merge.js';
import { CommandShipGroupSend, CommandShipGroupSendT } from './command-ship-group-send.js';
import { CommandShipGroupTransfer, CommandShipGroupTransferT } from './command-ship-group-transfer.js';
import { CommandShipGroupUnload, CommandShipGroupUnloadT } from './command-ship-group-unload.js';
import { CommandShipGroupUpgrade, CommandShipGroupUpgradeT } from './command-ship-group-upgrade.js';
export enum CommandPayload {
NONE = 0,
CommandRaceQuit = 1,
CommandRaceVote = 2,
CommandRaceRelation = 3,
CommandShipClassCreate = 4,
CommandShipClassMerge = 5,
CommandShipClassRemove = 6,
CommandShipGroupBreak = 7,
CommandShipGroupLoad = 8,
CommandShipGroupUnload = 9,
CommandShipGroupSend = 10,
CommandShipGroupUpgrade = 11,
CommandShipGroupMerge = 12,
CommandShipGroupDismantle = 13,
CommandShipGroupTransfer = 14,
CommandShipGroupJoinFleet = 15,
CommandFleetMerge = 16,
CommandFleetSend = 17,
CommandScienceCreate = 18,
CommandScienceRemove = 19,
CommandPlanetRename = 20,
CommandPlanetProduce = 21,
CommandPlanetRouteSet = 22,
CommandPlanetRouteRemove = 23
}
export function unionToCommandPayload(
type: CommandPayload,
accessor: (obj:CommandFleetMerge|CommandFleetSend|CommandPlanetProduce|CommandPlanetRename|CommandPlanetRouteRemove|CommandPlanetRouteSet|CommandRaceQuit|CommandRaceRelation|CommandRaceVote|CommandScienceCreate|CommandScienceRemove|CommandShipClassCreate|CommandShipClassMerge|CommandShipClassRemove|CommandShipGroupBreak|CommandShipGroupDismantle|CommandShipGroupJoinFleet|CommandShipGroupLoad|CommandShipGroupMerge|CommandShipGroupSend|CommandShipGroupTransfer|CommandShipGroupUnload|CommandShipGroupUpgrade) => CommandFleetMerge|CommandFleetSend|CommandPlanetProduce|CommandPlanetRename|CommandPlanetRouteRemove|CommandPlanetRouteSet|CommandRaceQuit|CommandRaceRelation|CommandRaceVote|CommandScienceCreate|CommandScienceRemove|CommandShipClassCreate|CommandShipClassMerge|CommandShipClassRemove|CommandShipGroupBreak|CommandShipGroupDismantle|CommandShipGroupJoinFleet|CommandShipGroupLoad|CommandShipGroupMerge|CommandShipGroupSend|CommandShipGroupTransfer|CommandShipGroupUnload|CommandShipGroupUpgrade|null
): CommandFleetMerge|CommandFleetSend|CommandPlanetProduce|CommandPlanetRename|CommandPlanetRouteRemove|CommandPlanetRouteSet|CommandRaceQuit|CommandRaceRelation|CommandRaceVote|CommandScienceCreate|CommandScienceRemove|CommandShipClassCreate|CommandShipClassMerge|CommandShipClassRemove|CommandShipGroupBreak|CommandShipGroupDismantle|CommandShipGroupJoinFleet|CommandShipGroupLoad|CommandShipGroupMerge|CommandShipGroupSend|CommandShipGroupTransfer|CommandShipGroupUnload|CommandShipGroupUpgrade|null {
switch(CommandPayload[type]) {
case 'NONE': return null;
case 'CommandRaceQuit': return accessor(new CommandRaceQuit())! as CommandRaceQuit;
case 'CommandRaceVote': return accessor(new CommandRaceVote())! as CommandRaceVote;
case 'CommandRaceRelation': return accessor(new CommandRaceRelation())! as CommandRaceRelation;
case 'CommandShipClassCreate': return accessor(new CommandShipClassCreate())! as CommandShipClassCreate;
case 'CommandShipClassMerge': return accessor(new CommandShipClassMerge())! as CommandShipClassMerge;
case 'CommandShipClassRemove': return accessor(new CommandShipClassRemove())! as CommandShipClassRemove;
case 'CommandShipGroupBreak': return accessor(new CommandShipGroupBreak())! as CommandShipGroupBreak;
case 'CommandShipGroupLoad': return accessor(new CommandShipGroupLoad())! as CommandShipGroupLoad;
case 'CommandShipGroupUnload': return accessor(new CommandShipGroupUnload())! as CommandShipGroupUnload;
case 'CommandShipGroupSend': return accessor(new CommandShipGroupSend())! as CommandShipGroupSend;
case 'CommandShipGroupUpgrade': return accessor(new CommandShipGroupUpgrade())! as CommandShipGroupUpgrade;
case 'CommandShipGroupMerge': return accessor(new CommandShipGroupMerge())! as CommandShipGroupMerge;
case 'CommandShipGroupDismantle': return accessor(new CommandShipGroupDismantle())! as CommandShipGroupDismantle;
case 'CommandShipGroupTransfer': return accessor(new CommandShipGroupTransfer())! as CommandShipGroupTransfer;
case 'CommandShipGroupJoinFleet': return accessor(new CommandShipGroupJoinFleet())! as CommandShipGroupJoinFleet;
case 'CommandFleetMerge': return accessor(new CommandFleetMerge())! as CommandFleetMerge;
case 'CommandFleetSend': return accessor(new CommandFleetSend())! as CommandFleetSend;
case 'CommandScienceCreate': return accessor(new CommandScienceCreate())! as CommandScienceCreate;
case 'CommandScienceRemove': return accessor(new CommandScienceRemove())! as CommandScienceRemove;
case 'CommandPlanetRename': return accessor(new CommandPlanetRename())! as CommandPlanetRename;
case 'CommandPlanetProduce': return accessor(new CommandPlanetProduce())! as CommandPlanetProduce;
case 'CommandPlanetRouteSet': return accessor(new CommandPlanetRouteSet())! as CommandPlanetRouteSet;
case 'CommandPlanetRouteRemove': return accessor(new CommandPlanetRouteRemove())! as CommandPlanetRouteRemove;
default: return null;
}
}
export function unionListToCommandPayload(
type: CommandPayload,
accessor: (index: number, obj:CommandFleetMerge|CommandFleetSend|CommandPlanetProduce|CommandPlanetRename|CommandPlanetRouteRemove|CommandPlanetRouteSet|CommandRaceQuit|CommandRaceRelation|CommandRaceVote|CommandScienceCreate|CommandScienceRemove|CommandShipClassCreate|CommandShipClassMerge|CommandShipClassRemove|CommandShipGroupBreak|CommandShipGroupDismantle|CommandShipGroupJoinFleet|CommandShipGroupLoad|CommandShipGroupMerge|CommandShipGroupSend|CommandShipGroupTransfer|CommandShipGroupUnload|CommandShipGroupUpgrade) => CommandFleetMerge|CommandFleetSend|CommandPlanetProduce|CommandPlanetRename|CommandPlanetRouteRemove|CommandPlanetRouteSet|CommandRaceQuit|CommandRaceRelation|CommandRaceVote|CommandScienceCreate|CommandScienceRemove|CommandShipClassCreate|CommandShipClassMerge|CommandShipClassRemove|CommandShipGroupBreak|CommandShipGroupDismantle|CommandShipGroupJoinFleet|CommandShipGroupLoad|CommandShipGroupMerge|CommandShipGroupSend|CommandShipGroupTransfer|CommandShipGroupUnload|CommandShipGroupUpgrade|null,
index: number
): CommandFleetMerge|CommandFleetSend|CommandPlanetProduce|CommandPlanetRename|CommandPlanetRouteRemove|CommandPlanetRouteSet|CommandRaceQuit|CommandRaceRelation|CommandRaceVote|CommandScienceCreate|CommandScienceRemove|CommandShipClassCreate|CommandShipClassMerge|CommandShipClassRemove|CommandShipGroupBreak|CommandShipGroupDismantle|CommandShipGroupJoinFleet|CommandShipGroupLoad|CommandShipGroupMerge|CommandShipGroupSend|CommandShipGroupTransfer|CommandShipGroupUnload|CommandShipGroupUpgrade|null {
switch(CommandPayload[type]) {
case 'NONE': return null;
case 'CommandRaceQuit': return accessor(index, new CommandRaceQuit())! as CommandRaceQuit;
case 'CommandRaceVote': return accessor(index, new CommandRaceVote())! as CommandRaceVote;
case 'CommandRaceRelation': return accessor(index, new CommandRaceRelation())! as CommandRaceRelation;
case 'CommandShipClassCreate': return accessor(index, new CommandShipClassCreate())! as CommandShipClassCreate;
case 'CommandShipClassMerge': return accessor(index, new CommandShipClassMerge())! as CommandShipClassMerge;
case 'CommandShipClassRemove': return accessor(index, new CommandShipClassRemove())! as CommandShipClassRemove;
case 'CommandShipGroupBreak': return accessor(index, new CommandShipGroupBreak())! as CommandShipGroupBreak;
case 'CommandShipGroupLoad': return accessor(index, new CommandShipGroupLoad())! as CommandShipGroupLoad;
case 'CommandShipGroupUnload': return accessor(index, new CommandShipGroupUnload())! as CommandShipGroupUnload;
case 'CommandShipGroupSend': return accessor(index, new CommandShipGroupSend())! as CommandShipGroupSend;
case 'CommandShipGroupUpgrade': return accessor(index, new CommandShipGroupUpgrade())! as CommandShipGroupUpgrade;
case 'CommandShipGroupMerge': return accessor(index, new CommandShipGroupMerge())! as CommandShipGroupMerge;
case 'CommandShipGroupDismantle': return accessor(index, new CommandShipGroupDismantle())! as CommandShipGroupDismantle;
case 'CommandShipGroupTransfer': return accessor(index, new CommandShipGroupTransfer())! as CommandShipGroupTransfer;
case 'CommandShipGroupJoinFleet': return accessor(index, new CommandShipGroupJoinFleet())! as CommandShipGroupJoinFleet;
case 'CommandFleetMerge': return accessor(index, new CommandFleetMerge())! as CommandFleetMerge;
case 'CommandFleetSend': return accessor(index, new CommandFleetSend())! as CommandFleetSend;
case 'CommandScienceCreate': return accessor(index, new CommandScienceCreate())! as CommandScienceCreate;
case 'CommandScienceRemove': return accessor(index, new CommandScienceRemove())! as CommandScienceRemove;
case 'CommandPlanetRename': return accessor(index, new CommandPlanetRename())! as CommandPlanetRename;
case 'CommandPlanetProduce': return accessor(index, new CommandPlanetProduce())! as CommandPlanetProduce;
case 'CommandPlanetRouteSet': return accessor(index, new CommandPlanetRouteSet())! as CommandPlanetRouteSet;
case 'CommandPlanetRouteRemove': return accessor(index, new CommandPlanetRouteRemove())! as CommandPlanetRouteRemove;
default: return null;
}
}
@@ -0,0 +1,107 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { PlanetProduction } from './planet-production.js';
export class CommandPlanetProduce implements flatbuffers.IUnpackableObject<CommandPlanetProduceT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandPlanetProduce {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandPlanetProduce(bb:flatbuffers.ByteBuffer, obj?:CommandPlanetProduce):CommandPlanetProduce {
return (obj || new CommandPlanetProduce()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandPlanetProduce(bb:flatbuffers.ByteBuffer, obj?:CommandPlanetProduce):CommandPlanetProduce {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandPlanetProduce()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
number():bigint {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
production():PlanetProduction {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt8(this.bb_pos + offset) : PlanetProduction.UNKNOWN;
}
subject():string|null
subject(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
subject(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandPlanetProduce(builder:flatbuffers.Builder) {
builder.startObject(3);
}
static addNumber(builder:flatbuffers.Builder, number:bigint) {
builder.addFieldInt64(0, number, BigInt('0'));
}
static addProduction(builder:flatbuffers.Builder, production:PlanetProduction) {
builder.addFieldInt8(1, production, PlanetProduction.UNKNOWN);
}
static addSubject(builder:flatbuffers.Builder, subjectOffset:flatbuffers.Offset) {
builder.addFieldOffset(2, subjectOffset, 0);
}
static endCommandPlanetProduce(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandPlanetProduce(builder:flatbuffers.Builder, number:bigint, production:PlanetProduction, subjectOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandPlanetProduce.startCommandPlanetProduce(builder);
CommandPlanetProduce.addNumber(builder, number);
CommandPlanetProduce.addProduction(builder, production);
CommandPlanetProduce.addSubject(builder, subjectOffset);
return CommandPlanetProduce.endCommandPlanetProduce(builder);
}
unpack(): CommandPlanetProduceT {
return new CommandPlanetProduceT(
this.number(),
this.production(),
this.subject()
);
}
unpackTo(_o: CommandPlanetProduceT): void {
_o.number = this.number();
_o.production = this.production();
_o.subject = this.subject();
}
}
export class CommandPlanetProduceT implements flatbuffers.IGeneratedObject {
constructor(
public number: bigint = BigInt('0'),
public production: PlanetProduction = PlanetProduction.UNKNOWN,
public subject: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const subject = (this.subject !== null ? builder.createString(this.subject!) : 0);
return CommandPlanetProduce.createCommandPlanetProduce(builder,
this.number,
this.production,
subject
);
}
}
@@ -0,0 +1,92 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandPlanetRename implements flatbuffers.IUnpackableObject<CommandPlanetRenameT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandPlanetRename {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandPlanetRename(bb:flatbuffers.ByteBuffer, obj?:CommandPlanetRename):CommandPlanetRename {
return (obj || new CommandPlanetRename()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandPlanetRename(bb:flatbuffers.ByteBuffer, obj?:CommandPlanetRename):CommandPlanetRename {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandPlanetRename()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
number():bigint {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandPlanetRename(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addNumber(builder:flatbuffers.Builder, number:bigint) {
builder.addFieldInt64(0, number, BigInt('0'));
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(1, nameOffset, 0);
}
static endCommandPlanetRename(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandPlanetRename(builder:flatbuffers.Builder, number:bigint, nameOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandPlanetRename.startCommandPlanetRename(builder);
CommandPlanetRename.addNumber(builder, number);
CommandPlanetRename.addName(builder, nameOffset);
return CommandPlanetRename.endCommandPlanetRename(builder);
}
unpack(): CommandPlanetRenameT {
return new CommandPlanetRenameT(
this.number(),
this.name()
);
}
unpackTo(_o: CommandPlanetRenameT): void {
_o.number = this.number();
_o.name = this.name();
}
}
export class CommandPlanetRenameT implements flatbuffers.IGeneratedObject {
constructor(
public number: bigint = BigInt('0'),
public name: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const name = (this.name !== null ? builder.createString(this.name!) : 0);
return CommandPlanetRename.createCommandPlanetRename(builder,
this.number,
name
);
}
}
@@ -0,0 +1,89 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { PlanetRouteLoadType } from './planet-route-load-type.js';
export class CommandPlanetRouteRemove implements flatbuffers.IUnpackableObject<CommandPlanetRouteRemoveT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandPlanetRouteRemove {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandPlanetRouteRemove(bb:flatbuffers.ByteBuffer, obj?:CommandPlanetRouteRemove):CommandPlanetRouteRemove {
return (obj || new CommandPlanetRouteRemove()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandPlanetRouteRemove(bb:flatbuffers.ByteBuffer, obj?:CommandPlanetRouteRemove):CommandPlanetRouteRemove {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandPlanetRouteRemove()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
origin():bigint {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
loadType():PlanetRouteLoadType {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt8(this.bb_pos + offset) : PlanetRouteLoadType.UNKNOWN;
}
static startCommandPlanetRouteRemove(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addOrigin(builder:flatbuffers.Builder, origin:bigint) {
builder.addFieldInt64(0, origin, BigInt('0'));
}
static addLoadType(builder:flatbuffers.Builder, loadType:PlanetRouteLoadType) {
builder.addFieldInt8(1, loadType, PlanetRouteLoadType.UNKNOWN);
}
static endCommandPlanetRouteRemove(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandPlanetRouteRemove(builder:flatbuffers.Builder, origin:bigint, loadType:PlanetRouteLoadType):flatbuffers.Offset {
CommandPlanetRouteRemove.startCommandPlanetRouteRemove(builder);
CommandPlanetRouteRemove.addOrigin(builder, origin);
CommandPlanetRouteRemove.addLoadType(builder, loadType);
return CommandPlanetRouteRemove.endCommandPlanetRouteRemove(builder);
}
unpack(): CommandPlanetRouteRemoveT {
return new CommandPlanetRouteRemoveT(
this.origin(),
this.loadType()
);
}
unpackTo(_o: CommandPlanetRouteRemoveT): void {
_o.origin = this.origin();
_o.loadType = this.loadType();
}
}
export class CommandPlanetRouteRemoveT implements flatbuffers.IGeneratedObject {
constructor(
public origin: bigint = BigInt('0'),
public loadType: PlanetRouteLoadType = PlanetRouteLoadType.UNKNOWN
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
return CommandPlanetRouteRemove.createCommandPlanetRouteRemove(builder,
this.origin,
this.loadType
);
}
}
@@ -0,0 +1,103 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { PlanetRouteLoadType } from './planet-route-load-type.js';
export class CommandPlanetRouteSet implements flatbuffers.IUnpackableObject<CommandPlanetRouteSetT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandPlanetRouteSet {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandPlanetRouteSet(bb:flatbuffers.ByteBuffer, obj?:CommandPlanetRouteSet):CommandPlanetRouteSet {
return (obj || new CommandPlanetRouteSet()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandPlanetRouteSet(bb:flatbuffers.ByteBuffer, obj?:CommandPlanetRouteSet):CommandPlanetRouteSet {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandPlanetRouteSet()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
origin():bigint {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
destination():bigint {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
loadType():PlanetRouteLoadType {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.readInt8(this.bb_pos + offset) : PlanetRouteLoadType.UNKNOWN;
}
static startCommandPlanetRouteSet(builder:flatbuffers.Builder) {
builder.startObject(3);
}
static addOrigin(builder:flatbuffers.Builder, origin:bigint) {
builder.addFieldInt64(0, origin, BigInt('0'));
}
static addDestination(builder:flatbuffers.Builder, destination:bigint) {
builder.addFieldInt64(1, destination, BigInt('0'));
}
static addLoadType(builder:flatbuffers.Builder, loadType:PlanetRouteLoadType) {
builder.addFieldInt8(2, loadType, PlanetRouteLoadType.UNKNOWN);
}
static endCommandPlanetRouteSet(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandPlanetRouteSet(builder:flatbuffers.Builder, origin:bigint, destination:bigint, loadType:PlanetRouteLoadType):flatbuffers.Offset {
CommandPlanetRouteSet.startCommandPlanetRouteSet(builder);
CommandPlanetRouteSet.addOrigin(builder, origin);
CommandPlanetRouteSet.addDestination(builder, destination);
CommandPlanetRouteSet.addLoadType(builder, loadType);
return CommandPlanetRouteSet.endCommandPlanetRouteSet(builder);
}
unpack(): CommandPlanetRouteSetT {
return new CommandPlanetRouteSetT(
this.origin(),
this.destination(),
this.loadType()
);
}
unpackTo(_o: CommandPlanetRouteSetT): void {
_o.origin = this.origin();
_o.destination = this.destination();
_o.loadType = this.loadType();
}
}
export class CommandPlanetRouteSetT implements flatbuffers.IGeneratedObject {
constructor(
public origin: bigint = BigInt('0'),
public destination: bigint = BigInt('0'),
public loadType: PlanetRouteLoadType = PlanetRouteLoadType.UNKNOWN
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
return CommandPlanetRouteSet.createCommandPlanetRouteSet(builder,
this.origin,
this.destination,
this.loadType
);
}
}
@@ -0,0 +1,56 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandRaceQuit implements flatbuffers.IUnpackableObject<CommandRaceQuitT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandRaceQuit {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandRaceQuit(bb:flatbuffers.ByteBuffer, obj?:CommandRaceQuit):CommandRaceQuit {
return (obj || new CommandRaceQuit()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandRaceQuit(bb:flatbuffers.ByteBuffer, obj?:CommandRaceQuit):CommandRaceQuit {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandRaceQuit()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static startCommandRaceQuit(builder:flatbuffers.Builder) {
builder.startObject(0);
}
static endCommandRaceQuit(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandRaceQuit(builder:flatbuffers.Builder):flatbuffers.Offset {
CommandRaceQuit.startCommandRaceQuit(builder);
return CommandRaceQuit.endCommandRaceQuit(builder);
}
unpack(): CommandRaceQuitT {
return new CommandRaceQuitT();
}
unpackTo(_o: CommandRaceQuitT): void {}
}
export class CommandRaceQuitT implements flatbuffers.IGeneratedObject {
constructor(){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
return CommandRaceQuit.createCommandRaceQuit(builder);
}
}
@@ -0,0 +1,93 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { Relation } from './relation.js';
export class CommandRaceRelation implements flatbuffers.IUnpackableObject<CommandRaceRelationT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandRaceRelation {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandRaceRelation(bb:flatbuffers.ByteBuffer, obj?:CommandRaceRelation):CommandRaceRelation {
return (obj || new CommandRaceRelation()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandRaceRelation(bb:flatbuffers.ByteBuffer, obj?:CommandRaceRelation):CommandRaceRelation {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandRaceRelation()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
acceptor():string|null
acceptor(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
acceptor(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
relation():Relation {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt8(this.bb_pos + offset) : Relation.UNKNOWN;
}
static startCommandRaceRelation(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addAcceptor(builder:flatbuffers.Builder, acceptorOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, acceptorOffset, 0);
}
static addRelation(builder:flatbuffers.Builder, relation:Relation) {
builder.addFieldInt8(1, relation, Relation.UNKNOWN);
}
static endCommandRaceRelation(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandRaceRelation(builder:flatbuffers.Builder, acceptorOffset:flatbuffers.Offset, relation:Relation):flatbuffers.Offset {
CommandRaceRelation.startCommandRaceRelation(builder);
CommandRaceRelation.addAcceptor(builder, acceptorOffset);
CommandRaceRelation.addRelation(builder, relation);
return CommandRaceRelation.endCommandRaceRelation(builder);
}
unpack(): CommandRaceRelationT {
return new CommandRaceRelationT(
this.acceptor(),
this.relation()
);
}
unpackTo(_o: CommandRaceRelationT): void {
_o.acceptor = this.acceptor();
_o.relation = this.relation();
}
}
export class CommandRaceRelationT implements flatbuffers.IGeneratedObject {
constructor(
public acceptor: string|Uint8Array|null = null,
public relation: Relation = Relation.UNKNOWN
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const acceptor = (this.acceptor !== null ? builder.createString(this.acceptor!) : 0);
return CommandRaceRelation.createCommandRaceRelation(builder,
acceptor,
this.relation
);
}
}
@@ -0,0 +1,78 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandRaceVote implements flatbuffers.IUnpackableObject<CommandRaceVoteT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandRaceVote {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandRaceVote(bb:flatbuffers.ByteBuffer, obj?:CommandRaceVote):CommandRaceVote {
return (obj || new CommandRaceVote()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandRaceVote(bb:flatbuffers.ByteBuffer, obj?:CommandRaceVote):CommandRaceVote {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandRaceVote()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
acceptor():string|null
acceptor(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
acceptor(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandRaceVote(builder:flatbuffers.Builder) {
builder.startObject(1);
}
static addAcceptor(builder:flatbuffers.Builder, acceptorOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, acceptorOffset, 0);
}
static endCommandRaceVote(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandRaceVote(builder:flatbuffers.Builder, acceptorOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandRaceVote.startCommandRaceVote(builder);
CommandRaceVote.addAcceptor(builder, acceptorOffset);
return CommandRaceVote.endCommandRaceVote(builder);
}
unpack(): CommandRaceVoteT {
return new CommandRaceVoteT(
this.acceptor()
);
}
unpackTo(_o: CommandRaceVoteT): void {
_o.acceptor = this.acceptor();
}
}
export class CommandRaceVoteT implements flatbuffers.IGeneratedObject {
constructor(
public acceptor: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const acceptor = (this.acceptor !== null ? builder.createString(this.acceptor!) : 0);
return CommandRaceVote.createCommandRaceVote(builder,
acceptor
);
}
}
@@ -0,0 +1,134 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandScienceCreate implements flatbuffers.IUnpackableObject<CommandScienceCreateT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandScienceCreate {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandScienceCreate(bb:flatbuffers.ByteBuffer, obj?:CommandScienceCreate):CommandScienceCreate {
return (obj || new CommandScienceCreate()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandScienceCreate(bb:flatbuffers.ByteBuffer, obj?:CommandScienceCreate):CommandScienceCreate {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandScienceCreate()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
drive():number {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
weapons():number {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
shields():number {
const offset = this.bb!.__offset(this.bb_pos, 10);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
cargo():number {
const offset = this.bb!.__offset(this.bb_pos, 12);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
static startCommandScienceCreate(builder:flatbuffers.Builder) {
builder.startObject(5);
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, nameOffset, 0);
}
static addDrive(builder:flatbuffers.Builder, drive:number) {
builder.addFieldFloat64(1, drive, 0.0);
}
static addWeapons(builder:flatbuffers.Builder, weapons:number) {
builder.addFieldFloat64(2, weapons, 0.0);
}
static addShields(builder:flatbuffers.Builder, shields:number) {
builder.addFieldFloat64(3, shields, 0.0);
}
static addCargo(builder:flatbuffers.Builder, cargo:number) {
builder.addFieldFloat64(4, cargo, 0.0);
}
static endCommandScienceCreate(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandScienceCreate(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset, drive:number, weapons:number, shields:number, cargo:number):flatbuffers.Offset {
CommandScienceCreate.startCommandScienceCreate(builder);
CommandScienceCreate.addName(builder, nameOffset);
CommandScienceCreate.addDrive(builder, drive);
CommandScienceCreate.addWeapons(builder, weapons);
CommandScienceCreate.addShields(builder, shields);
CommandScienceCreate.addCargo(builder, cargo);
return CommandScienceCreate.endCommandScienceCreate(builder);
}
unpack(): CommandScienceCreateT {
return new CommandScienceCreateT(
this.name(),
this.drive(),
this.weapons(),
this.shields(),
this.cargo()
);
}
unpackTo(_o: CommandScienceCreateT): void {
_o.name = this.name();
_o.drive = this.drive();
_o.weapons = this.weapons();
_o.shields = this.shields();
_o.cargo = this.cargo();
}
}
export class CommandScienceCreateT implements flatbuffers.IGeneratedObject {
constructor(
public name: string|Uint8Array|null = null,
public drive: number = 0.0,
public weapons: number = 0.0,
public shields: number = 0.0,
public cargo: number = 0.0
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const name = (this.name !== null ? builder.createString(this.name!) : 0);
return CommandScienceCreate.createCommandScienceCreate(builder,
name,
this.drive,
this.weapons,
this.shields,
this.cargo
);
}
}
@@ -0,0 +1,78 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandScienceRemove implements flatbuffers.IUnpackableObject<CommandScienceRemoveT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandScienceRemove {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandScienceRemove(bb:flatbuffers.ByteBuffer, obj?:CommandScienceRemove):CommandScienceRemove {
return (obj || new CommandScienceRemove()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandScienceRemove(bb:flatbuffers.ByteBuffer, obj?:CommandScienceRemove):CommandScienceRemove {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandScienceRemove()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandScienceRemove(builder:flatbuffers.Builder) {
builder.startObject(1);
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, nameOffset, 0);
}
static endCommandScienceRemove(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandScienceRemove(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandScienceRemove.startCommandScienceRemove(builder);
CommandScienceRemove.addName(builder, nameOffset);
return CommandScienceRemove.endCommandScienceRemove(builder);
}
unpack(): CommandScienceRemoveT {
return new CommandScienceRemoveT(
this.name()
);
}
unpackTo(_o: CommandScienceRemoveT): void {
_o.name = this.name();
}
}
export class CommandScienceRemoveT implements flatbuffers.IGeneratedObject {
constructor(
public name: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const name = (this.name !== null ? builder.createString(this.name!) : 0);
return CommandScienceRemove.createCommandScienceRemove(builder,
name
);
}
}
@@ -0,0 +1,148 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipClassCreate implements flatbuffers.IUnpackableObject<CommandShipClassCreateT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipClassCreate {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipClassCreate(bb:flatbuffers.ByteBuffer, obj?:CommandShipClassCreate):CommandShipClassCreate {
return (obj || new CommandShipClassCreate()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipClassCreate(bb:flatbuffers.ByteBuffer, obj?:CommandShipClassCreate):CommandShipClassCreate {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipClassCreate()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
drive():number {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
armament():bigint {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
weapons():number {
const offset = this.bb!.__offset(this.bb_pos, 10);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
shields():number {
const offset = this.bb!.__offset(this.bb_pos, 12);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
cargo():number {
const offset = this.bb!.__offset(this.bb_pos, 14);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
static startCommandShipClassCreate(builder:flatbuffers.Builder) {
builder.startObject(6);
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, nameOffset, 0);
}
static addDrive(builder:flatbuffers.Builder, drive:number) {
builder.addFieldFloat64(1, drive, 0.0);
}
static addArmament(builder:flatbuffers.Builder, armament:bigint) {
builder.addFieldInt64(2, armament, BigInt('0'));
}
static addWeapons(builder:flatbuffers.Builder, weapons:number) {
builder.addFieldFloat64(3, weapons, 0.0);
}
static addShields(builder:flatbuffers.Builder, shields:number) {
builder.addFieldFloat64(4, shields, 0.0);
}
static addCargo(builder:flatbuffers.Builder, cargo:number) {
builder.addFieldFloat64(5, cargo, 0.0);
}
static endCommandShipClassCreate(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipClassCreate(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset, drive:number, armament:bigint, weapons:number, shields:number, cargo:number):flatbuffers.Offset {
CommandShipClassCreate.startCommandShipClassCreate(builder);
CommandShipClassCreate.addName(builder, nameOffset);
CommandShipClassCreate.addDrive(builder, drive);
CommandShipClassCreate.addArmament(builder, armament);
CommandShipClassCreate.addWeapons(builder, weapons);
CommandShipClassCreate.addShields(builder, shields);
CommandShipClassCreate.addCargo(builder, cargo);
return CommandShipClassCreate.endCommandShipClassCreate(builder);
}
unpack(): CommandShipClassCreateT {
return new CommandShipClassCreateT(
this.name(),
this.drive(),
this.armament(),
this.weapons(),
this.shields(),
this.cargo()
);
}
unpackTo(_o: CommandShipClassCreateT): void {
_o.name = this.name();
_o.drive = this.drive();
_o.armament = this.armament();
_o.weapons = this.weapons();
_o.shields = this.shields();
_o.cargo = this.cargo();
}
}
export class CommandShipClassCreateT implements flatbuffers.IGeneratedObject {
constructor(
public name: string|Uint8Array|null = null,
public drive: number = 0.0,
public armament: bigint = BigInt('0'),
public weapons: number = 0.0,
public shields: number = 0.0,
public cargo: number = 0.0
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const name = (this.name !== null ? builder.createString(this.name!) : 0);
return CommandShipClassCreate.createCommandShipClassCreate(builder,
name,
this.drive,
this.armament,
this.weapons,
this.shields,
this.cargo
);
}
}
@@ -0,0 +1,95 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipClassMerge implements flatbuffers.IUnpackableObject<CommandShipClassMergeT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipClassMerge {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipClassMerge(bb:flatbuffers.ByteBuffer, obj?:CommandShipClassMerge):CommandShipClassMerge {
return (obj || new CommandShipClassMerge()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipClassMerge(bb:flatbuffers.ByteBuffer, obj?:CommandShipClassMerge):CommandShipClassMerge {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipClassMerge()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
target():string|null
target(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
target(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandShipClassMerge(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, nameOffset, 0);
}
static addTarget(builder:flatbuffers.Builder, targetOffset:flatbuffers.Offset) {
builder.addFieldOffset(1, targetOffset, 0);
}
static endCommandShipClassMerge(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipClassMerge(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset, targetOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandShipClassMerge.startCommandShipClassMerge(builder);
CommandShipClassMerge.addName(builder, nameOffset);
CommandShipClassMerge.addTarget(builder, targetOffset);
return CommandShipClassMerge.endCommandShipClassMerge(builder);
}
unpack(): CommandShipClassMergeT {
return new CommandShipClassMergeT(
this.name(),
this.target()
);
}
unpackTo(_o: CommandShipClassMergeT): void {
_o.name = this.name();
_o.target = this.target();
}
}
export class CommandShipClassMergeT implements flatbuffers.IGeneratedObject {
constructor(
public name: string|Uint8Array|null = null,
public target: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const name = (this.name !== null ? builder.createString(this.name!) : 0);
const target = (this.target !== null ? builder.createString(this.target!) : 0);
return CommandShipClassMerge.createCommandShipClassMerge(builder,
name,
target
);
}
}
@@ -0,0 +1,78 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipClassRemove implements flatbuffers.IUnpackableObject<CommandShipClassRemoveT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipClassRemove {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipClassRemove(bb:flatbuffers.ByteBuffer, obj?:CommandShipClassRemove):CommandShipClassRemove {
return (obj || new CommandShipClassRemove()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipClassRemove(bb:flatbuffers.ByteBuffer, obj?:CommandShipClassRemove):CommandShipClassRemove {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipClassRemove()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandShipClassRemove(builder:flatbuffers.Builder) {
builder.startObject(1);
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, nameOffset, 0);
}
static endCommandShipClassRemove(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipClassRemove(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandShipClassRemove.startCommandShipClassRemove(builder);
CommandShipClassRemove.addName(builder, nameOffset);
return CommandShipClassRemove.endCommandShipClassRemove(builder);
}
unpack(): CommandShipClassRemoveT {
return new CommandShipClassRemoveT(
this.name()
);
}
unpackTo(_o: CommandShipClassRemoveT): void {
_o.name = this.name();
}
}
export class CommandShipClassRemoveT implements flatbuffers.IGeneratedObject {
constructor(
public name: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const name = (this.name !== null ? builder.createString(this.name!) : 0);
return CommandShipClassRemove.createCommandShipClassRemove(builder,
name
);
}
}
@@ -0,0 +1,109 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipGroupBreak implements flatbuffers.IUnpackableObject<CommandShipGroupBreakT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupBreak {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupBreak(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupBreak):CommandShipGroupBreak {
return (obj || new CommandShipGroupBreak()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupBreak(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupBreak):CommandShipGroupBreak {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupBreak()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id():string|null
id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
id(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
newId():string|null
newId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
newId(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
quantity():bigint {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
static startCommandShipGroupBreak(builder:flatbuffers.Builder) {
builder.startObject(3);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, idOffset, 0);
}
static addNewId(builder:flatbuffers.Builder, newIdOffset:flatbuffers.Offset) {
builder.addFieldOffset(1, newIdOffset, 0);
}
static addQuantity(builder:flatbuffers.Builder, quantity:bigint) {
builder.addFieldInt64(2, quantity, BigInt('0'));
}
static endCommandShipGroupBreak(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupBreak(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, newIdOffset:flatbuffers.Offset, quantity:bigint):flatbuffers.Offset {
CommandShipGroupBreak.startCommandShipGroupBreak(builder);
CommandShipGroupBreak.addId(builder, idOffset);
CommandShipGroupBreak.addNewId(builder, newIdOffset);
CommandShipGroupBreak.addQuantity(builder, quantity);
return CommandShipGroupBreak.endCommandShipGroupBreak(builder);
}
unpack(): CommandShipGroupBreakT {
return new CommandShipGroupBreakT(
this.id(),
this.newId(),
this.quantity()
);
}
unpackTo(_o: CommandShipGroupBreakT): void {
_o.id = this.id();
_o.newId = this.newId();
_o.quantity = this.quantity();
}
}
export class CommandShipGroupBreakT implements flatbuffers.IGeneratedObject {
constructor(
public id: string|Uint8Array|null = null,
public newId: string|Uint8Array|null = null,
public quantity: bigint = BigInt('0')
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const id = (this.id !== null ? builder.createString(this.id!) : 0);
const newId = (this.newId !== null ? builder.createString(this.newId!) : 0);
return CommandShipGroupBreak.createCommandShipGroupBreak(builder,
id,
newId,
this.quantity
);
}
}
@@ -0,0 +1,78 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipGroupDismantle implements flatbuffers.IUnpackableObject<CommandShipGroupDismantleT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupDismantle {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupDismantle(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupDismantle):CommandShipGroupDismantle {
return (obj || new CommandShipGroupDismantle()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupDismantle(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupDismantle):CommandShipGroupDismantle {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupDismantle()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id():string|null
id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
id(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandShipGroupDismantle(builder:flatbuffers.Builder) {
builder.startObject(1);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, idOffset, 0);
}
static endCommandShipGroupDismantle(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupDismantle(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandShipGroupDismantle.startCommandShipGroupDismantle(builder);
CommandShipGroupDismantle.addId(builder, idOffset);
return CommandShipGroupDismantle.endCommandShipGroupDismantle(builder);
}
unpack(): CommandShipGroupDismantleT {
return new CommandShipGroupDismantleT(
this.id()
);
}
unpackTo(_o: CommandShipGroupDismantleT): void {
_o.id = this.id();
}
}
export class CommandShipGroupDismantleT implements flatbuffers.IGeneratedObject {
constructor(
public id: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const id = (this.id !== null ? builder.createString(this.id!) : 0);
return CommandShipGroupDismantle.createCommandShipGroupDismantle(builder,
id
);
}
}
@@ -0,0 +1,95 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipGroupJoinFleet implements flatbuffers.IUnpackableObject<CommandShipGroupJoinFleetT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupJoinFleet {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupJoinFleet(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupJoinFleet):CommandShipGroupJoinFleet {
return (obj || new CommandShipGroupJoinFleet()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupJoinFleet(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupJoinFleet):CommandShipGroupJoinFleet {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupJoinFleet()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id():string|null
id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
id(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
name():string|null
name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
name(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandShipGroupJoinFleet(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, idOffset, 0);
}
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset) {
builder.addFieldOffset(1, nameOffset, 0);
}
static endCommandShipGroupJoinFleet(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupJoinFleet(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, nameOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandShipGroupJoinFleet.startCommandShipGroupJoinFleet(builder);
CommandShipGroupJoinFleet.addId(builder, idOffset);
CommandShipGroupJoinFleet.addName(builder, nameOffset);
return CommandShipGroupJoinFleet.endCommandShipGroupJoinFleet(builder);
}
unpack(): CommandShipGroupJoinFleetT {
return new CommandShipGroupJoinFleetT(
this.id(),
this.name()
);
}
unpackTo(_o: CommandShipGroupJoinFleetT): void {
_o.id = this.id();
_o.name = this.name();
}
}
export class CommandShipGroupJoinFleetT implements flatbuffers.IGeneratedObject {
constructor(
public id: string|Uint8Array|null = null,
public name: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const id = (this.id !== null ? builder.createString(this.id!) : 0);
const name = (this.name !== null ? builder.createString(this.name!) : 0);
return CommandShipGroupJoinFleet.createCommandShipGroupJoinFleet(builder,
id,
name
);
}
}
@@ -0,0 +1,107 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { ShipGroupCargo } from './ship-group-cargo.js';
export class CommandShipGroupLoad implements flatbuffers.IUnpackableObject<CommandShipGroupLoadT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupLoad {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupLoad(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupLoad):CommandShipGroupLoad {
return (obj || new CommandShipGroupLoad()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupLoad(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupLoad):CommandShipGroupLoad {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupLoad()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id():string|null
id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
id(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
cargo():ShipGroupCargo {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt8(this.bb_pos + offset) : ShipGroupCargo.UNKNOWN;
}
quantity():number {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
static startCommandShipGroupLoad(builder:flatbuffers.Builder) {
builder.startObject(3);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, idOffset, 0);
}
static addCargo(builder:flatbuffers.Builder, cargo:ShipGroupCargo) {
builder.addFieldInt8(1, cargo, ShipGroupCargo.UNKNOWN);
}
static addQuantity(builder:flatbuffers.Builder, quantity:number) {
builder.addFieldFloat64(2, quantity, 0.0);
}
static endCommandShipGroupLoad(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupLoad(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, cargo:ShipGroupCargo, quantity:number):flatbuffers.Offset {
CommandShipGroupLoad.startCommandShipGroupLoad(builder);
CommandShipGroupLoad.addId(builder, idOffset);
CommandShipGroupLoad.addCargo(builder, cargo);
CommandShipGroupLoad.addQuantity(builder, quantity);
return CommandShipGroupLoad.endCommandShipGroupLoad(builder);
}
unpack(): CommandShipGroupLoadT {
return new CommandShipGroupLoadT(
this.id(),
this.cargo(),
this.quantity()
);
}
unpackTo(_o: CommandShipGroupLoadT): void {
_o.id = this.id();
_o.cargo = this.cargo();
_o.quantity = this.quantity();
}
}
export class CommandShipGroupLoadT implements flatbuffers.IGeneratedObject {
constructor(
public id: string|Uint8Array|null = null,
public cargo: ShipGroupCargo = ShipGroupCargo.UNKNOWN,
public quantity: number = 0.0
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const id = (this.id !== null ? builder.createString(this.id!) : 0);
return CommandShipGroupLoad.createCommandShipGroupLoad(builder,
id,
this.cargo,
this.quantity
);
}
}
@@ -0,0 +1,56 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipGroupMerge implements flatbuffers.IUnpackableObject<CommandShipGroupMergeT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupMerge {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupMerge(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupMerge):CommandShipGroupMerge {
return (obj || new CommandShipGroupMerge()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupMerge(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupMerge):CommandShipGroupMerge {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupMerge()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static startCommandShipGroupMerge(builder:flatbuffers.Builder) {
builder.startObject(0);
}
static endCommandShipGroupMerge(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupMerge(builder:flatbuffers.Builder):flatbuffers.Offset {
CommandShipGroupMerge.startCommandShipGroupMerge(builder);
return CommandShipGroupMerge.endCommandShipGroupMerge(builder);
}
unpack(): CommandShipGroupMergeT {
return new CommandShipGroupMergeT();
}
unpackTo(_o: CommandShipGroupMergeT): void {}
}
export class CommandShipGroupMergeT implements flatbuffers.IGeneratedObject {
constructor(){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
return CommandShipGroupMerge.createCommandShipGroupMerge(builder);
}
}
@@ -0,0 +1,92 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipGroupSend implements flatbuffers.IUnpackableObject<CommandShipGroupSendT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupSend {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupSend(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupSend):CommandShipGroupSend {
return (obj || new CommandShipGroupSend()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupSend(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupSend):CommandShipGroupSend {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupSend()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id():string|null
id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
id(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
destination():bigint {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
static startCommandShipGroupSend(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, idOffset, 0);
}
static addDestination(builder:flatbuffers.Builder, destination:bigint) {
builder.addFieldInt64(1, destination, BigInt('0'));
}
static endCommandShipGroupSend(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupSend(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, destination:bigint):flatbuffers.Offset {
CommandShipGroupSend.startCommandShipGroupSend(builder);
CommandShipGroupSend.addId(builder, idOffset);
CommandShipGroupSend.addDestination(builder, destination);
return CommandShipGroupSend.endCommandShipGroupSend(builder);
}
unpack(): CommandShipGroupSendT {
return new CommandShipGroupSendT(
this.id(),
this.destination()
);
}
unpackTo(_o: CommandShipGroupSendT): void {
_o.id = this.id();
_o.destination = this.destination();
}
}
export class CommandShipGroupSendT implements flatbuffers.IGeneratedObject {
constructor(
public id: string|Uint8Array|null = null,
public destination: bigint = BigInt('0')
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const id = (this.id !== null ? builder.createString(this.id!) : 0);
return CommandShipGroupSend.createCommandShipGroupSend(builder,
id,
this.destination
);
}
}
@@ -0,0 +1,95 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipGroupTransfer implements flatbuffers.IUnpackableObject<CommandShipGroupTransferT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupTransfer {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupTransfer(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupTransfer):CommandShipGroupTransfer {
return (obj || new CommandShipGroupTransfer()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupTransfer(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupTransfer):CommandShipGroupTransfer {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupTransfer()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id():string|null
id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
id(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
acceptor():string|null
acceptor(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
acceptor(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startCommandShipGroupTransfer(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, idOffset, 0);
}
static addAcceptor(builder:flatbuffers.Builder, acceptorOffset:flatbuffers.Offset) {
builder.addFieldOffset(1, acceptorOffset, 0);
}
static endCommandShipGroupTransfer(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupTransfer(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, acceptorOffset:flatbuffers.Offset):flatbuffers.Offset {
CommandShipGroupTransfer.startCommandShipGroupTransfer(builder);
CommandShipGroupTransfer.addId(builder, idOffset);
CommandShipGroupTransfer.addAcceptor(builder, acceptorOffset);
return CommandShipGroupTransfer.endCommandShipGroupTransfer(builder);
}
unpack(): CommandShipGroupTransferT {
return new CommandShipGroupTransferT(
this.id(),
this.acceptor()
);
}
unpackTo(_o: CommandShipGroupTransferT): void {
_o.id = this.id();
_o.acceptor = this.acceptor();
}
}
export class CommandShipGroupTransferT implements flatbuffers.IGeneratedObject {
constructor(
public id: string|Uint8Array|null = null,
public acceptor: string|Uint8Array|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const id = (this.id !== null ? builder.createString(this.id!) : 0);
const acceptor = (this.acceptor !== null ? builder.createString(this.acceptor!) : 0);
return CommandShipGroupTransfer.createCommandShipGroupTransfer(builder,
id,
acceptor
);
}
}
@@ -0,0 +1,92 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class CommandShipGroupUnload implements flatbuffers.IUnpackableObject<CommandShipGroupUnloadT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupUnload {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupUnload(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupUnload):CommandShipGroupUnload {
return (obj || new CommandShipGroupUnload()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupUnload(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupUnload):CommandShipGroupUnload {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupUnload()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id():string|null
id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
id(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
quantity():number {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
static startCommandShipGroupUnload(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, idOffset, 0);
}
static addQuantity(builder:flatbuffers.Builder, quantity:number) {
builder.addFieldFloat64(1, quantity, 0.0);
}
static endCommandShipGroupUnload(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupUnload(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, quantity:number):flatbuffers.Offset {
CommandShipGroupUnload.startCommandShipGroupUnload(builder);
CommandShipGroupUnload.addId(builder, idOffset);
CommandShipGroupUnload.addQuantity(builder, quantity);
return CommandShipGroupUnload.endCommandShipGroupUnload(builder);
}
unpack(): CommandShipGroupUnloadT {
return new CommandShipGroupUnloadT(
this.id(),
this.quantity()
);
}
unpackTo(_o: CommandShipGroupUnloadT): void {
_o.id = this.id();
_o.quantity = this.quantity();
}
}
export class CommandShipGroupUnloadT implements flatbuffers.IGeneratedObject {
constructor(
public id: string|Uint8Array|null = null,
public quantity: number = 0.0
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const id = (this.id !== null ? builder.createString(this.id!) : 0);
return CommandShipGroupUnload.createCommandShipGroupUnload(builder,
id,
this.quantity
);
}
}
@@ -0,0 +1,107 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { ShipGroupUpgradeTech } from './ship-group-upgrade-tech.js';
export class CommandShipGroupUpgrade implements flatbuffers.IUnpackableObject<CommandShipGroupUpgradeT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):CommandShipGroupUpgrade {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsCommandShipGroupUpgrade(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupUpgrade):CommandShipGroupUpgrade {
return (obj || new CommandShipGroupUpgrade()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsCommandShipGroupUpgrade(bb:flatbuffers.ByteBuffer, obj?:CommandShipGroupUpgrade):CommandShipGroupUpgrade {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new CommandShipGroupUpgrade()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
id():string|null
id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
id(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
tech():ShipGroupUpgradeTech {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt8(this.bb_pos + offset) : ShipGroupUpgradeTech.UNKNOWN;
}
level():number {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0;
}
static startCommandShipGroupUpgrade(builder:flatbuffers.Builder) {
builder.startObject(3);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, idOffset, 0);
}
static addTech(builder:flatbuffers.Builder, tech:ShipGroupUpgradeTech) {
builder.addFieldInt8(1, tech, ShipGroupUpgradeTech.UNKNOWN);
}
static addLevel(builder:flatbuffers.Builder, level:number) {
builder.addFieldFloat64(2, level, 0.0);
}
static endCommandShipGroupUpgrade(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createCommandShipGroupUpgrade(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, tech:ShipGroupUpgradeTech, level:number):flatbuffers.Offset {
CommandShipGroupUpgrade.startCommandShipGroupUpgrade(builder);
CommandShipGroupUpgrade.addId(builder, idOffset);
CommandShipGroupUpgrade.addTech(builder, tech);
CommandShipGroupUpgrade.addLevel(builder, level);
return CommandShipGroupUpgrade.endCommandShipGroupUpgrade(builder);
}
unpack(): CommandShipGroupUpgradeT {
return new CommandShipGroupUpgradeT(
this.id(),
this.tech(),
this.level()
);
}
unpackTo(_o: CommandShipGroupUpgradeT): void {
_o.id = this.id();
_o.tech = this.tech();
_o.level = this.level();
}
}
export class CommandShipGroupUpgradeT implements flatbuffers.IGeneratedObject {
constructor(
public id: string|Uint8Array|null = null,
public tech: ShipGroupUpgradeTech = ShipGroupUpgradeTech.UNKNOWN,
public level: number = 0.0
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const id = (this.id !== null ? builder.createString(this.id!) : 0);
return CommandShipGroupUpgrade.createCommandShipGroupUpgrade(builder,
id,
this.tech,
this.level
);
}
}
@@ -0,0 +1,15 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
export enum PlanetProduction {
UNKNOWN = 0,
MAT = 1,
CAP = 2,
DRIVE = 3,
WEAPONS = 4,
SHIELDS = 5,
CARGO = 6,
SCIENCE = 7,
SHIP = 8
}
@@ -0,0 +1,11 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
export enum PlanetRouteLoadType {
UNKNOWN = 0,
MAT = 1,
CAP = 2,
COL = 3,
EMP = 4
}
@@ -0,0 +1,9 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
export enum Relation {
UNKNOWN = 0,
WAR = 1,
PEACE = 2
}
@@ -0,0 +1,10 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
export enum ShipGroupCargo {
UNKNOWN = 0,
COL = 1,
MAT = 2,
CAP = 3
}
@@ -0,0 +1,12 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
export enum ShipGroupUpgradeTech {
UNKNOWN = 0,
ALL = 1,
DRIVE = 2,
WEAPONS = 3,
SHIELDS = 4,
CARGO = 5
}
@@ -0,0 +1,56 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class UserGamesCommandResponse implements flatbuffers.IUnpackableObject<UserGamesCommandResponseT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):UserGamesCommandResponse {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsUserGamesCommandResponse(bb:flatbuffers.ByteBuffer, obj?:UserGamesCommandResponse):UserGamesCommandResponse {
return (obj || new UserGamesCommandResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsUserGamesCommandResponse(bb:flatbuffers.ByteBuffer, obj?:UserGamesCommandResponse):UserGamesCommandResponse {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new UserGamesCommandResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static startUserGamesCommandResponse(builder:flatbuffers.Builder) {
builder.startObject(0);
}
static endUserGamesCommandResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createUserGamesCommandResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
UserGamesCommandResponse.startUserGamesCommandResponse(builder);
return UserGamesCommandResponse.endUserGamesCommandResponse(builder);
}
unpack(): UserGamesCommandResponseT {
return new UserGamesCommandResponseT();
}
unpackTo(_o: UserGamesCommandResponseT): void {}
}
export class UserGamesCommandResponseT implements flatbuffers.IGeneratedObject {
constructor(){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
return UserGamesCommandResponse.createUserGamesCommandResponse(builder);
}
}
@@ -0,0 +1,110 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { UUID, UUIDT } from '../common/uuid.js';
import { CommandItem, CommandItemT } from './command-item.js';
export class UserGamesCommand implements flatbuffers.IUnpackableObject<UserGamesCommandT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):UserGamesCommand {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsUserGamesCommand(bb:flatbuffers.ByteBuffer, obj?:UserGamesCommand):UserGamesCommand {
return (obj || new UserGamesCommand()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsUserGamesCommand(bb:flatbuffers.ByteBuffer, obj?:UserGamesCommand):UserGamesCommand {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new UserGamesCommand()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
gameId(obj?:UUID):UUID|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
}
commands(index: number, obj?:CommandItem):CommandItem|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? (obj || new CommandItem()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null;
}
commandsLength():number {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
}
static startUserGamesCommand(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
builder.addFieldStruct(0, gameIdOffset, 0);
}
static addCommands(builder:flatbuffers.Builder, commandsOffset:flatbuffers.Offset) {
builder.addFieldOffset(1, commandsOffset, 0);
}
static createCommandsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset {
builder.startVector(4, data.length, 4);
for (let i = data.length - 1; i >= 0; i--) {
builder.addOffset(data[i]!);
}
return builder.endVector();
}
static startCommandsVector(builder:flatbuffers.Builder, numElems:number) {
builder.startVector(4, numElems, 4);
}
static endUserGamesCommand(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
builder.requiredField(offset, 4) // game_id
return offset;
}
static createUserGamesCommand(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, commandsOffset:flatbuffers.Offset):flatbuffers.Offset {
UserGamesCommand.startUserGamesCommand(builder);
UserGamesCommand.addGameId(builder, gameIdOffset);
UserGamesCommand.addCommands(builder, commandsOffset);
return UserGamesCommand.endUserGamesCommand(builder);
}
unpack(): UserGamesCommandT {
return new UserGamesCommandT(
(this.gameId() !== null ? this.gameId()!.unpack() : null),
this.bb!.createObjList<CommandItem, CommandItemT>(this.commands.bind(this), this.commandsLength())
);
}
unpackTo(_o: UserGamesCommandT): void {
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
_o.commands = this.bb!.createObjList<CommandItem, CommandItemT>(this.commands.bind(this), this.commandsLength());
}
}
export class UserGamesCommandT implements flatbuffers.IGeneratedObject {
constructor(
public gameId: UUIDT|null = null,
public commands: (CommandItemT)[] = []
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const commands = UserGamesCommand.createCommandsVector(builder, builder.createObjectOffsetList(this.commands));
return UserGamesCommand.createUserGamesCommand(builder,
(this.gameId !== null ? this.gameId!.pack(builder) : 0),
commands
);
}
}
@@ -0,0 +1,86 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { UserGamesOrder, UserGamesOrderT } from './user-games-order.js';
export class UserGamesOrderGetResponse implements flatbuffers.IUnpackableObject<UserGamesOrderGetResponseT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):UserGamesOrderGetResponse {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsUserGamesOrderGetResponse(bb:flatbuffers.ByteBuffer, obj?:UserGamesOrderGetResponse):UserGamesOrderGetResponse {
return (obj || new UserGamesOrderGetResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsUserGamesOrderGetResponse(bb:flatbuffers.ByteBuffer, obj?:UserGamesOrderGetResponse):UserGamesOrderGetResponse {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new UserGamesOrderGetResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
found():boolean {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
}
order(obj?:UserGamesOrder):UserGamesOrder|null {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? (obj || new UserGamesOrder()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null;
}
static startUserGamesOrderGetResponse(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addFound(builder:flatbuffers.Builder, found:boolean) {
builder.addFieldInt8(0, +found, +false);
}
static addOrder(builder:flatbuffers.Builder, orderOffset:flatbuffers.Offset) {
builder.addFieldOffset(1, orderOffset, 0);
}
static endUserGamesOrderGetResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
unpack(): UserGamesOrderGetResponseT {
return new UserGamesOrderGetResponseT(
this.found(),
(this.order() !== null ? this.order()!.unpack() : null)
);
}
unpackTo(_o: UserGamesOrderGetResponseT): void {
_o.found = this.found();
_o.order = (this.order() !== null ? this.order()!.unpack() : null);
}
}
export class UserGamesOrderGetResponseT implements flatbuffers.IGeneratedObject {
constructor(
public found: boolean = false,
public order: UserGamesOrderT|null = null
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const order = (this.order !== null ? this.order!.pack(builder) : 0);
UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder);
UserGamesOrderGetResponse.addFound(builder, this.found);
UserGamesOrderGetResponse.addOrder(builder, order);
return UserGamesOrderGetResponse.endUserGamesOrderGetResponse(builder);
}
}
@@ -0,0 +1,90 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { UUID, UUIDT } from '../common/uuid.js';
export class UserGamesOrderGet implements flatbuffers.IUnpackableObject<UserGamesOrderGetT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):UserGamesOrderGet {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsUserGamesOrderGet(bb:flatbuffers.ByteBuffer, obj?:UserGamesOrderGet):UserGamesOrderGet {
return (obj || new UserGamesOrderGet()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsUserGamesOrderGet(bb:flatbuffers.ByteBuffer, obj?:UserGamesOrderGet):UserGamesOrderGet {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new UserGamesOrderGet()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
gameId(obj?:UUID):UUID|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
}
turn():bigint {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
static startUserGamesOrderGet(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
builder.addFieldStruct(0, gameIdOffset, 0);
}
static addTurn(builder:flatbuffers.Builder, turn:bigint) {
builder.addFieldInt64(1, turn, BigInt('0'));
}
static endUserGamesOrderGet(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
builder.requiredField(offset, 4) // game_id
return offset;
}
static createUserGamesOrderGet(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, turn:bigint):flatbuffers.Offset {
UserGamesOrderGet.startUserGamesOrderGet(builder);
UserGamesOrderGet.addGameId(builder, gameIdOffset);
UserGamesOrderGet.addTurn(builder, turn);
return UserGamesOrderGet.endUserGamesOrderGet(builder);
}
unpack(): UserGamesOrderGetT {
return new UserGamesOrderGetT(
(this.gameId() !== null ? this.gameId()!.unpack() : null),
this.turn()
);
}
unpackTo(_o: UserGamesOrderGetT): void {
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
_o.turn = this.turn();
}
}
export class UserGamesOrderGetT implements flatbuffers.IGeneratedObject {
constructor(
public gameId: UUIDT|null = null,
public turn: bigint = BigInt('0')
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
return UserGamesOrderGet.createUserGamesOrderGet(builder,
(this.gameId !== null ? this.gameId!.pack(builder) : 0),
this.turn
);
}
}
@@ -0,0 +1,123 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { UUID, UUIDT } from '../common/uuid.js';
import { CommandItem, CommandItemT } from './command-item.js';
export class UserGamesOrderResponse implements flatbuffers.IUnpackableObject<UserGamesOrderResponseT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):UserGamesOrderResponse {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsUserGamesOrderResponse(bb:flatbuffers.ByteBuffer, obj?:UserGamesOrderResponse):UserGamesOrderResponse {
return (obj || new UserGamesOrderResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsUserGamesOrderResponse(bb:flatbuffers.ByteBuffer, obj?:UserGamesOrderResponse):UserGamesOrderResponse {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new UserGamesOrderResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
gameId(obj?:UUID):UUID|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
}
updatedAt():bigint {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
commands(index: number, obj?:CommandItem):CommandItem|null {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? (obj || new CommandItem()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null;
}
commandsLength():number {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
}
static startUserGamesOrderResponse(builder:flatbuffers.Builder) {
builder.startObject(3);
}
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
builder.addFieldStruct(0, gameIdOffset, 0);
}
static addUpdatedAt(builder:flatbuffers.Builder, updatedAt:bigint) {
builder.addFieldInt64(1, updatedAt, BigInt('0'));
}
static addCommands(builder:flatbuffers.Builder, commandsOffset:flatbuffers.Offset) {
builder.addFieldOffset(2, commandsOffset, 0);
}
static createCommandsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset {
builder.startVector(4, data.length, 4);
for (let i = data.length - 1; i >= 0; i--) {
builder.addOffset(data[i]!);
}
return builder.endVector();
}
static startCommandsVector(builder:flatbuffers.Builder, numElems:number) {
builder.startVector(4, numElems, 4);
}
static endUserGamesOrderResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createUserGamesOrderResponse(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, updatedAt:bigint, commandsOffset:flatbuffers.Offset):flatbuffers.Offset {
UserGamesOrderResponse.startUserGamesOrderResponse(builder);
UserGamesOrderResponse.addGameId(builder, gameIdOffset);
UserGamesOrderResponse.addUpdatedAt(builder, updatedAt);
UserGamesOrderResponse.addCommands(builder, commandsOffset);
return UserGamesOrderResponse.endUserGamesOrderResponse(builder);
}
unpack(): UserGamesOrderResponseT {
return new UserGamesOrderResponseT(
(this.gameId() !== null ? this.gameId()!.unpack() : null),
this.updatedAt(),
this.bb!.createObjList<CommandItem, CommandItemT>(this.commands.bind(this), this.commandsLength())
);
}
unpackTo(_o: UserGamesOrderResponseT): void {
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
_o.updatedAt = this.updatedAt();
_o.commands = this.bb!.createObjList<CommandItem, CommandItemT>(this.commands.bind(this), this.commandsLength());
}
}
export class UserGamesOrderResponseT implements flatbuffers.IGeneratedObject {
constructor(
public gameId: UUIDT|null = null,
public updatedAt: bigint = BigInt('0'),
public commands: (CommandItemT)[] = []
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const commands = UserGamesOrderResponse.createCommandsVector(builder, builder.createObjectOffsetList(this.commands));
return UserGamesOrderResponse.createUserGamesOrderResponse(builder,
(this.gameId !== null ? this.gameId!.pack(builder) : 0),
this.updatedAt,
commands
);
}
}
@@ -0,0 +1,124 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
import { UUID, UUIDT } from '../common/uuid.js';
import { CommandItem, CommandItemT } from './command-item.js';
export class UserGamesOrder implements flatbuffers.IUnpackableObject<UserGamesOrderT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):UserGamesOrder {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsUserGamesOrder(bb:flatbuffers.ByteBuffer, obj?:UserGamesOrder):UserGamesOrder {
return (obj || new UserGamesOrder()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsUserGamesOrder(bb:flatbuffers.ByteBuffer, obj?:UserGamesOrder):UserGamesOrder {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new UserGamesOrder()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
gameId(obj?:UUID):UUID|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
}
updatedAt():bigint {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
commands(index: number, obj?:CommandItem):CommandItem|null {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? (obj || new CommandItem()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null;
}
commandsLength():number {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
}
static startUserGamesOrder(builder:flatbuffers.Builder) {
builder.startObject(3);
}
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
builder.addFieldStruct(0, gameIdOffset, 0);
}
static addUpdatedAt(builder:flatbuffers.Builder, updatedAt:bigint) {
builder.addFieldInt64(1, updatedAt, BigInt('0'));
}
static addCommands(builder:flatbuffers.Builder, commandsOffset:flatbuffers.Offset) {
builder.addFieldOffset(2, commandsOffset, 0);
}
static createCommandsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset {
builder.startVector(4, data.length, 4);
for (let i = data.length - 1; i >= 0; i--) {
builder.addOffset(data[i]!);
}
return builder.endVector();
}
static startCommandsVector(builder:flatbuffers.Builder, numElems:number) {
builder.startVector(4, numElems, 4);
}
static endUserGamesOrder(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
builder.requiredField(offset, 4) // game_id
return offset;
}
static createUserGamesOrder(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, updatedAt:bigint, commandsOffset:flatbuffers.Offset):flatbuffers.Offset {
UserGamesOrder.startUserGamesOrder(builder);
UserGamesOrder.addGameId(builder, gameIdOffset);
UserGamesOrder.addUpdatedAt(builder, updatedAt);
UserGamesOrder.addCommands(builder, commandsOffset);
return UserGamesOrder.endUserGamesOrder(builder);
}
unpack(): UserGamesOrderT {
return new UserGamesOrderT(
(this.gameId() !== null ? this.gameId()!.unpack() : null),
this.updatedAt(),
this.bb!.createObjList<CommandItem, CommandItemT>(this.commands.bind(this), this.commandsLength())
);
}
unpackTo(_o: UserGamesOrderT): void {
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
_o.updatedAt = this.updatedAt();
_o.commands = this.bb!.createObjList<CommandItem, CommandItemT>(this.commands.bind(this), this.commandsLength());
}
}
export class UserGamesOrderT implements flatbuffers.IGeneratedObject {
constructor(
public gameId: UUIDT|null = null,
public updatedAt: bigint = BigInt('0'),
public commands: (CommandItemT)[] = []
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const commands = UserGamesOrder.createCommandsVector(builder, builder.createObjectOffsetList(this.commands));
return UserGamesOrder.createUserGamesOrder(builder,
(this.gameId !== null ? this.gameId!.pack(builder) : 0),
this.updatedAt,
commands
);
}
}
@@ -57,10 +57,18 @@ fresh.
SelectionStore,
SELECTION_CONTEXT_KEY,
} from "$lib/selection.svelte";
import {
createRenderedReportSource,
RENDERED_REPORT_CONTEXT_KEY,
} from "$lib/rendered-report.svelte";
import {
ORDER_DRAFT_CONTEXT_KEY,
OrderDraftStore,
} from "../../../sync/order-draft.svelte";
import {
GALAXY_CLIENT_CONTEXT_KEY,
GalaxyClientHolder,
} from "$lib/galaxy-client-context.svelte";
import { session } from "$lib/session-store.svelte";
import { loadStore } from "../../../platform/store/index";
import { loadCore } from "../../../platform/core/index";
@@ -89,17 +97,23 @@ fresh.
setContext(ORDER_DRAFT_CONTEXT_KEY, orderDraft);
const selection = new SelectionStore();
setContext(SELECTION_CONTEXT_KEY, selection);
const renderedReport = createRenderedReportSource(gameState, orderDraft);
setContext(RENDERED_REPORT_CONTEXT_KEY, renderedReport);
const galaxyClient = new GalaxyClientHolder();
setContext(GALAXY_CLIENT_CONTEXT_KEY, galaxyClient);
// selectedPlanet resolves the current selection against the live
// report so both the desktop sidebar and the mobile sheet display
// the same snapshot. A selection that points at a planet missing
// from the current report (e.g. visibility lost between turns)
// reads as `null` here, which collapses the inspector and the
// sheet without surfacing a stale row.
// sheet without surfacing a stale row. The rendered report layers
// the local order draft on top so the player sees their pending
// renames immediately.
const selectedPlanet = $derived.by(() => {
const sel = selection.selected;
if (sel === null || sel.kind !== "planet") return null;
const report = gameState.report;
const report = renderedReport.report;
if (report === null) return null;
return report.planets.find((p) => p.number === sel.id) ?? null;
});
@@ -149,6 +163,13 @@ fresh.
gameState.init({ client, cache, gameId }),
orderDraft.init({ cache, gameId }),
]);
galaxyClient.set(client);
if (orderDraft.needsServerHydration) {
await orderDraft.hydrateFromServer({
client,
turn: gameState.currentTurn,
});
}
} catch (err) {
gameState.failBootstrap(describeBootstrapError(err));
}
+150 -5
View File
@@ -17,7 +17,10 @@
// any UI.
import type { Cache } from "../platform/store/index";
import type { OrderCommand } from "./order-types";
import type { GalaxyClient } from "../api/galaxy-client";
import { fetchOrder } from "./order-load";
import type { CommandStatus, OrderCommand } from "./order-types";
import { validateEntityName } from "$lib/util/entity-name";
const NAMESPACE = "order-drafts";
const draftKey = (gameId: string): string => `${gameId}/draft`;
@@ -34,9 +37,21 @@ type Status = "idle" | "ready" | "error";
export class OrderDraftStore {
commands: OrderCommand[] = $state([]);
statuses: Record<string, CommandStatus> = $state({});
updatedAt = $state(0);
status: Status = $state("idle");
error: string | null = $state(null);
/**
* needsServerHydration is `true` when the cache row for this game
* was absent at `init` time. The layout reads it after both
* `gameState.init` and `orderDraft.init` resolve and, if `true`,
* calls `hydrateFromServer` once the current turn is known.
* An explicitly empty cache row sets it to `false` (the user has
* an empty draft, not a missing one).
*/
needsServerHydration = $state(false);
private cache: Cache | null = null;
private gameId = "";
private destroyed = false;
@@ -47,6 +62,12 @@ export class OrderDraftStore {
* idempotent on the same store instance — the layout always
* constructs a fresh store per game, so there is no need to support
* mid-life game switching here.
*
* When the cache row is absent, `needsServerHydration` is set to
* `true`; the layout fans out a `hydrateFromServer` call once the
* current turn is known. An explicitly empty cache row is treated
* as "user has an empty draft" and skipped — local intent always
* wins over server snapshot.
*/
async init(opts: { cache: Cache; gameId: string }): Promise<void> {
this.cache = opts.cache;
@@ -57,7 +78,14 @@ export class OrderDraftStore {
draftKey(opts.gameId),
);
if (this.destroyed) return;
this.commands = Array.isArray(stored) ? [...stored] : [];
if (stored === undefined) {
this.commands = [];
this.needsServerHydration = true;
} else {
this.commands = Array.isArray(stored) ? [...stored] : [];
this.needsServerHydration = false;
}
this.recomputeStatuses();
this.status = "ready";
} catch (err) {
if (this.destroyed) return;
@@ -67,13 +95,44 @@ export class OrderDraftStore {
}
/**
* add appends a command to the end of the draft and persists the
* updated list. Mutations made before `init` resolves are ignored —
* the layout always awaits `init` before exposing the store.
* hydrateFromServer fetches the player's stored order from the
* gateway when the cache row was absent at boot. The result is
* merged into `commands` and persisted so subsequent reloads
* prefer the cached version. Failures are non-fatal — the draft
* stays empty and the user can keep composing.
*/
async hydrateFromServer(opts: {
client: GalaxyClient;
turn: number;
}): Promise<void> {
if (this.status !== "ready" || !this.needsServerHydration) return;
this.needsServerHydration = false;
try {
const fetched = await fetchOrder(opts.client, this.gameId, opts.turn);
if (this.destroyed) return;
this.commands = fetched.commands;
this.updatedAt = fetched.updatedAt;
this.recomputeStatuses();
await this.persist();
} catch (err) {
if (this.destroyed) return;
console.warn(
"order-draft: server hydration failed; staying on empty draft",
err,
);
}
}
/**
* add appends a command to the end of the draft, runs local
* validation for the new entry, and persists the updated list.
* Mutations made before `init` resolves are ignored — the layout
* always awaits `init` before exposing the store.
*/
async add(command: OrderCommand): Promise<void> {
if (this.status !== "ready") return;
this.commands = [...this.commands, command];
this.statuses = { ...this.statuses, [command.id]: validateCommand(command) };
await this.persist();
}
@@ -86,6 +145,9 @@ export class OrderDraftStore {
const next = this.commands.filter((cmd) => cmd.id !== id);
if (next.length === this.commands.length) return;
this.commands = next;
const nextStatuses = { ...this.statuses };
delete nextStatuses[id];
this.statuses = nextStatuses;
await this.persist();
}
@@ -109,11 +171,83 @@ export class OrderDraftStore {
await this.persist();
}
/**
* markSubmitting flips the status of every entry in `ids` to
* `submitting` so the order tab can disable per-row controls and
* show a spinner. The state machine runs `valid → submitting →
* applied | rejected` (see ui/docs/order-composer.md).
*/
markSubmitting(ids: string[]): void {
const next = { ...this.statuses };
for (const id of ids) {
next[id] = "submitting";
}
this.statuses = next;
}
/**
* applyResults merges the verdict map returned by `submitOrder`
* into the per-command status map. Entries not present in the
* map keep their current status — useful when only a subset of
* commands round-tripped to the server. The engine-assigned
* `updatedAt` is also stashed for the next submit's stale-order
* detection (kept as plumbing only in Phase 14).
*/
applyResults(opts: {
results: Map<string, CommandStatus>;
updatedAt: number;
}): void {
const next = { ...this.statuses };
for (const [id, status] of opts.results.entries()) {
next[id] = status;
}
this.statuses = next;
this.updatedAt = opts.updatedAt;
}
/**
* markRejected switches every supplied id to `rejected`. Used by
* the order tab when `submitOrder` returns `ok: false` — the
* gateway didn't process any command, so the entire batch is
* treated as rejected.
*/
markRejected(ids: string[]): void {
const next = { ...this.statuses };
for (const id of ids) {
next[id] = "rejected";
}
this.statuses = next;
}
/**
* revertSubmittingToValid resets every entry currently in
* `submitting` back to its pre-submit status (typically `valid`).
* Called when the network layer throws an exception so the
* operator can retry without the rows looking stuck mid-flight.
*/
revertSubmittingToValid(): void {
const next = { ...this.statuses };
for (const cmd of this.commands) {
if (next[cmd.id] === "submitting") {
next[cmd.id] = validateCommand(cmd);
}
}
this.statuses = next;
}
dispose(): void {
this.destroyed = true;
this.cache = null;
}
private recomputeStatuses(): void {
const next: Record<string, CommandStatus> = {};
for (const cmd of this.commands) {
next[cmd.id] = validateCommand(cmd);
}
this.statuses = next;
}
private async persist(): Promise<void> {
if (this.cache === null || this.destroyed) return;
// `commands` is `$state`, so individual entries are proxies.
@@ -123,3 +257,14 @@ export class OrderDraftStore {
await this.cache.put(NAMESPACE, draftKey(this.gameId), snapshot);
}
}
function validateCommand(cmd: OrderCommand): CommandStatus {
switch (cmd.kind) {
case "planetRename":
return validateEntityName(cmd.name).ok ? "valid" : "invalid";
case "placeholder":
// Phase 12 placeholder entries are content-free and never
// transition out of `draft` — they are not submittable.
return "draft";
}
}
+163
View File
@@ -0,0 +1,163 @@
// Reads back the player's stored order for the current turn through
// `user.games.order.get`. Used by `OrderDraftStore` only when the
// local cache row is absent (fresh install, cleared storage, or a
// brand-new device): the local draft is the source of truth, so a
// present-but-empty cache row means "no commands" and is honoured
// over the server snapshot.
import { Builder, ByteBuffer } from "flatbuffers";
import type { GalaxyClient } from "../api/galaxy-client";
import { uuidToHiLo } from "../api/game-state";
import { UUID } from "../proto/galaxy/fbs/common";
import {
CommandPayload,
CommandPlanetRename,
UserGamesOrderGet,
UserGamesOrderGetResponse,
} from "../proto/galaxy/fbs/order";
import type { OrderCommand } from "./order-types";
const MESSAGE_TYPE = "user.games.order.get";
export class OrderLoadError extends Error {
readonly resultCode: string;
readonly code: string;
constructor(resultCode: string, code: string, message: string) {
super(message);
this.name = "OrderLoadError";
this.resultCode = resultCode;
this.code = code;
}
}
export interface FetchedOrder {
commands: OrderCommand[];
updatedAt: number;
}
/**
* fetchOrder issues `user.games.order.get` for the given game and
* turn, decodes the response, and returns the typed draft. A
* `found = false` answer (no order stored on the server) surfaces as
* an empty `commands` array — the caller treats this as a clean
* draft. Unknown command kinds in the response are skipped with a
* console warning so a backend-side schema bump never silently
* corrupts the local draft.
*/
export async function fetchOrder(
client: GalaxyClient,
gameId: string,
turn: number,
): Promise<FetchedOrder> {
if (turn < 0) {
throw new OrderLoadError(
"invalid_request",
"invalid_request",
`turn must be non-negative, got ${turn}`,
);
}
const payload = buildRequest(gameId, turn);
const result = await client.executeCommand(MESSAGE_TYPE, payload);
if (result.resultCode !== "ok") {
const { code, message } = decodeError(result.payloadBytes, result.resultCode);
throw new OrderLoadError(result.resultCode, code, message);
}
return decodeResponse(result.payloadBytes);
}
function buildRequest(gameId: string, turn: number): Uint8Array {
const builder = new Builder(64);
const [hi, lo] = uuidToHiLo(gameId);
const gameIdOffset = UUID.createUUID(builder, hi, lo);
UserGamesOrderGet.startUserGamesOrderGet(builder);
UserGamesOrderGet.addGameId(builder, gameIdOffset);
UserGamesOrderGet.addTurn(builder, BigInt(turn));
const offset = UserGamesOrderGet.endUserGamesOrderGet(builder);
builder.finish(offset);
return builder.asUint8Array();
}
function decodeResponse(payload: Uint8Array): FetchedOrder {
if (payload.length === 0) {
throw new OrderLoadError(
"internal_error",
"internal_error",
"empty user.games.order.get payload",
);
}
const buffer = new ByteBuffer(payload);
const response = UserGamesOrderGetResponse.getRootAsUserGamesOrderGetResponse(buffer);
if (!response.found()) {
return { commands: [], updatedAt: 0 };
}
const order = response.order();
if (order === null) {
throw new OrderLoadError(
"internal_error",
"internal_error",
"order missing while found=true",
);
}
const commands: OrderCommand[] = [];
const length = order.commandsLength();
for (let i = 0; i < length; i++) {
const item = order.commands(i);
if (item === null) continue;
const cmd = decodeCommand(item);
if (cmd === null) continue;
commands.push(cmd);
}
return {
commands,
updatedAt: Number(order.updatedAt()),
};
}
type CommandItemView = NonNullable<
ReturnType<NonNullable<ReturnType<UserGamesOrderGetResponse["order"]>>["commands"]>
>;
function decodeCommand(item: CommandItemView): OrderCommand | null {
if (item === null) return null;
const id = item.cmdId();
if (id === null) return null;
const payloadType = item.payloadType();
switch (payloadType) {
case CommandPayload.CommandPlanetRename: {
const inner = new CommandPlanetRename();
item.payload(inner);
return {
kind: "planetRename",
id,
planetNumber: Number(inner.number()),
name: inner.name() ?? "",
};
}
default:
console.warn(
`fetchOrder: skipping unknown command kind (payloadType=${payloadType})`,
);
return null;
}
}
function decodeError(
payload: Uint8Array,
resultCode: string,
): { code: string; message: string } {
if (payload.length === 0) {
return { code: resultCode, message: resultCode };
}
try {
const text = new TextDecoder().decode(payload);
const parsed = JSON.parse(text) as { code?: string; message?: string };
return {
code: typeof parsed.code === "string" ? parsed.code : resultCode,
message: typeof parsed.message === "string" ? parsed.message : text,
};
} catch {
return { code: resultCode, message: resultCode };
}
}
+17 -2
View File
@@ -25,13 +25,28 @@ export interface PlaceholderCommand {
readonly label: string;
}
/**
* PlanetRenameCommand is the first real command variant — Phase 14
* lands the rename action together with the submit pipeline. The
* `name` is locally validated against `validateEntityName` (the TS
* port of `pkg/util/string.go.ValidateTypeName`) before the entry is
* accepted into the draft; the same rules run server-side, so a
* locally-valid command is always accepted at the wire level.
*/
export interface PlanetRenameCommand {
readonly kind: "planetRename";
readonly id: string;
readonly planetNumber: number;
readonly name: string;
}
/**
* OrderCommand is the discriminated union of every command shape the
* local order draft can hold. The `kind` field is the discriminator;
* narrowing on it enables exhaustive `switch` statements at every
* call site. Phase 14 will widen the union with `planetRename`.
* call site.
*/
export type OrderCommand = PlaceholderCommand;
export type OrderCommand = PlaceholderCommand | PlanetRenameCommand;
/**
* CommandStatus is the lifecycle of a single command from the moment
+230
View File
@@ -0,0 +1,230 @@
// Drives the order submit pipeline: builds a FlatBuffers
// `UserGamesOrder` payload from the local draft, calls
// `client.executeCommand("user.games.order", ...)`, and translates
// the engine response into per-command results the draft store can
// merge with `applyResults`.
//
// The engine populates `cmdApplied` and `cmdErrorCode` on every
// returned command (see `game/openapi.yaml`), so the happy path
// reads real per-command outcomes. An empty response `commands`
// array — the gateway's defensive fallback when no body comes back
// — collapses to a batch-level "all applied" verdict so the player
// is never left with submitted-without-result rows.
//
// Failures fall into two buckets:
// - the gateway answers with a non-`ok` `resultCode` (auth /
// transcoder / engine validation); the result is `ok: false`
// and every submitted entry should flip to `rejected`;
// - the request itself throws (network, signature mismatch, decoder
// panic); the exception bubbles up to the caller, which leaves
// the draft entries in `submitting` for the operator to retry.
import { Builder, ByteBuffer } from "flatbuffers";
import type { GalaxyClient } from "../api/galaxy-client";
import { uuidToHiLo } from "../api/game-state";
import { UUID } from "../proto/galaxy/fbs/common";
import {
CommandItem,
CommandPayload,
CommandPlanetRename,
UserGamesOrder,
UserGamesOrderResponse,
} from "../proto/galaxy/fbs/order";
import type { OrderCommand } from "./order-types";
const MESSAGE_TYPE = "user.games.order";
export class SubmitError extends Error {
readonly resultCode: string;
readonly code: string;
constructor(resultCode: string, code: string, message: string) {
super(message);
this.name = "SubmitError";
this.resultCode = resultCode;
this.code = code;
}
}
export type CommandOutcome = "applied" | "rejected";
export interface SubmitSuccess {
ok: true;
results: Map<string, CommandOutcome>;
errorCodes: Map<string, number | null>;
updatedAt: number;
}
export interface SubmitFailure {
ok: false;
resultCode: string;
code: string;
message: string;
}
export type SubmitResult = SubmitSuccess | SubmitFailure;
export interface SubmitOptions {
updatedAt?: number;
}
/**
* submitOrder posts the `commands` slice through `user.games.order`,
* decodes the FBS response, and returns per-command outcomes the
* caller (the order tab) feeds back into `OrderDraftStore.applyResults`.
*
* @param client GalaxyClient owning the signed-gRPC transport.
* @param gameId Stringified UUID of the game whose order is submitted.
* @param commands Subset of the local draft to send. The caller has
* already filtered out non-`valid` entries.
* @param options.updatedAt Optional engine-assigned timestamp from a
* prior submit — Phase 14 always sends `0` because stale-order
* detection is not yet wired client-side.
*/
export async function submitOrder(
client: GalaxyClient,
gameId: string,
commands: OrderCommand[],
options: SubmitOptions = {},
): Promise<SubmitResult> {
const payload = buildOrderPayload(gameId, commands, options.updatedAt ?? 0);
const result = await client.executeCommand(MESSAGE_TYPE, payload);
if (result.resultCode !== "ok") {
const { code, message } = decodeError(result.payloadBytes, result.resultCode);
return {
ok: false,
resultCode: result.resultCode,
code,
message,
};
}
return decodeOrderResponse(result.payloadBytes, commands);
}
function buildOrderPayload(
gameId: string,
commands: OrderCommand[],
updatedAt: number,
): Uint8Array {
const builder = new Builder(256);
const itemOffsets = commands.map((cmd) => encodeCommandItem(builder, cmd));
const commandsVec = UserGamesOrder.createCommandsVector(builder, itemOffsets);
const [hi, lo] = uuidToHiLo(gameId);
const gameIdOffset = UUID.createUUID(builder, hi, lo);
UserGamesOrder.startUserGamesOrder(builder);
UserGamesOrder.addGameId(builder, gameIdOffset);
UserGamesOrder.addUpdatedAt(builder, BigInt(updatedAt));
UserGamesOrder.addCommands(builder, commandsVec);
const offset = UserGamesOrder.endUserGamesOrder(builder);
builder.finish(offset);
return builder.asUint8Array();
}
function encodeCommandItem(builder: Builder, cmd: OrderCommand): number {
const cmdIdOffset = builder.createString(cmd.id);
const { payloadType, payloadOffset } = encodeCommandPayload(builder, cmd);
CommandItem.startCommandItem(builder);
CommandItem.addCmdId(builder, cmdIdOffset);
CommandItem.addPayloadType(builder, payloadType);
CommandItem.addPayload(builder, payloadOffset);
return CommandItem.endCommandItem(builder);
}
function encodeCommandPayload(
builder: Builder,
cmd: OrderCommand,
): { payloadType: CommandPayload; payloadOffset: number } {
switch (cmd.kind) {
case "planetRename": {
const nameOffset = builder.createString(cmd.name);
const offset = CommandPlanetRename.createCommandPlanetRename(
builder,
BigInt(cmd.planetNumber),
nameOffset,
);
return {
payloadType: CommandPayload.CommandPlanetRename,
payloadOffset: offset,
};
}
case "placeholder":
throw new SubmitError(
"invalid_request",
"invalid_request",
`placeholder commands cannot be submitted (cmd id ${cmd.id})`,
);
}
}
function decodeOrderResponse(
payload: Uint8Array,
commands: OrderCommand[],
): SubmitSuccess {
const results = new Map<string, CommandOutcome>();
const errorCodes = new Map<string, number | null>();
let updatedAt = 0;
if (payload.length === 0) {
// Empty envelope (gateway fallback). Apply batch-level verdict.
for (const cmd of commands) {
results.set(cmd.id, "applied");
errorCodes.set(cmd.id, null);
}
return { ok: true, results, errorCodes, updatedAt };
}
const buffer = new ByteBuffer(payload);
const response = UserGamesOrderResponse.getRootAsUserGamesOrderResponse(buffer);
updatedAt = Number(response.updatedAt());
const length = response.commandsLength();
if (length === 0) {
for (const cmd of commands) {
results.set(cmd.id, "applied");
errorCodes.set(cmd.id, null);
}
return { ok: true, results, errorCodes, updatedAt };
}
for (let i = 0; i < length; i++) {
const item = response.commands(i);
if (item === null) continue;
const cmdId = item.cmdId();
if (cmdId === null) continue;
const applied = item.cmdApplied();
const errorCode = item.cmdErrorCode();
results.set(cmdId, applied === false ? "rejected" : "applied");
errorCodes.set(cmdId, errorCode === null ? null : Number(errorCode));
}
// Defensive: any submitted command not echoed back falls back to
// applied so the draft entry leaves `submitting`.
for (const cmd of commands) {
if (!results.has(cmd.id)) {
results.set(cmd.id, "applied");
errorCodes.set(cmd.id, null);
}
}
return { ok: true, results, errorCodes, updatedAt };
}
function decodeError(
payload: Uint8Array,
resultCode: string,
): { code: string; message: string } {
if (payload.length === 0) {
return { code: resultCode, message: resultCode };
}
try {
const text = new TextDecoder().decode(payload);
const parsed = JSON.parse(text) as { code?: string; message?: string };
return {
code: typeof parsed.code === "string" ? parsed.code : resultCode,
message: typeof parsed.message === "string" ? parsed.message : text,
};
} catch {
return { code: resultCode, message: resultCode };
}
}
+101
View File
@@ -0,0 +1,101 @@
// FlatBuffers payload builders for the Phase 14 Playwright suite.
// Mirrors what `pkg/transcoder/order.go` produces in production for
// the `user.games.order` POST response and the
// `user.games.order.get` GET response.
import { Builder } from "flatbuffers";
import { uuidToHiLo } from "../../../src/api/game-state";
import { UUID } from "../../../src/proto/galaxy/fbs/common";
import {
CommandItem,
CommandPayload,
CommandPlanetRename,
UserGamesOrder,
UserGamesOrderGetResponse,
UserGamesOrderResponse,
} from "../../../src/proto/galaxy/fbs/order";
export interface CommandResultFixture {
cmdId: string;
planetNumber: number;
name: string;
applied: boolean | null;
errorCode: number | null;
}
export function buildOrderResponsePayload(
gameId: string,
commands: CommandResultFixture[],
updatedAt: number,
): Uint8Array {
const builder = new Builder(256);
const itemOffsets = commands.map((c) => encodeItem(builder, c));
const commandsVec = UserGamesOrderResponse.createCommandsVector(
builder,
itemOffsets,
);
const [hi, lo] = uuidToHiLo(gameId);
const gameIdOffset = UUID.createUUID(builder, hi, lo);
UserGamesOrderResponse.startUserGamesOrderResponse(builder);
UserGamesOrderResponse.addGameId(builder, gameIdOffset);
UserGamesOrderResponse.addUpdatedAt(builder, BigInt(updatedAt));
UserGamesOrderResponse.addCommands(builder, commandsVec);
const offset = UserGamesOrderResponse.endUserGamesOrderResponse(builder);
builder.finish(offset);
return builder.asUint8Array();
}
export function buildOrderGetResponsePayload(
gameId: string,
commands: CommandResultFixture[],
updatedAt: number,
found = true,
): Uint8Array {
const builder = new Builder(256);
let orderOffset = 0;
if (found) {
const itemOffsets = commands.map((c) => encodeItem(builder, c));
const commandsVec = UserGamesOrder.createCommandsVector(
builder,
itemOffsets,
);
const [hi, lo] = uuidToHiLo(gameId);
const gameIdOffset = UUID.createUUID(builder, hi, lo);
UserGamesOrder.startUserGamesOrder(builder);
UserGamesOrder.addGameId(builder, gameIdOffset);
UserGamesOrder.addUpdatedAt(builder, BigInt(updatedAt));
UserGamesOrder.addCommands(builder, commandsVec);
orderOffset = UserGamesOrder.endUserGamesOrder(builder);
}
UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder);
UserGamesOrderGetResponse.addFound(builder, found);
if (orderOffset !== 0) {
UserGamesOrderGetResponse.addOrder(builder, orderOffset);
}
const offset =
UserGamesOrderGetResponse.endUserGamesOrderGetResponse(builder);
builder.finish(offset);
return builder.asUint8Array();
}
function encodeItem(builder: Builder, c: CommandResultFixture): number {
const cmdIdOffset = builder.createString(c.cmdId);
const nameOffset = builder.createString(c.name);
const inner = CommandPlanetRename.createCommandPlanetRename(
builder,
BigInt(c.planetNumber),
nameOffset,
);
CommandItem.startCommandItem(builder);
CommandItem.addCmdId(builder, cmdIdOffset);
if (c.applied !== null) CommandItem.addCmdApplied(builder, c.applied);
if (c.errorCode !== null) {
CommandItem.addCmdErrorCode(builder, BigInt(c.errorCode));
}
CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetRename);
CommandItem.addPayload(builder, inner);
return CommandItem.endCommandItem(builder);
}
+315
View File
@@ -0,0 +1,315 @@
// Phase 14 end-to-end coverage for the rename-planet flow. Boots an
// authenticated session, mocks the lobby + report + order routes,
// drives a click into the renderer to select a planet, opens the
// Rename action, types a new name, submits, and verifies the
// optimistic overlay (inspector + map label). A second test covers
// the rejected path: the engine answers `cmdApplied: false` and the
// inspector keeps the original name while the order tab row reads
// `rejected`.
import { fromJson, type JsonValue } from "@bufbuild/protobuf";
import { expect, test, type Page } from "@playwright/test";
import { ByteBuffer } from "flatbuffers";
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
import { UUID } from "../../src/proto/galaxy/fbs/common";
import {
UserGamesOrder,
UserGamesOrderGet,
} from "../../src/proto/galaxy/fbs/order";
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
import {
buildMyGamesListPayload,
type GameFixture,
} from "./fixtures/lobby-fbs";
import { buildReportPayload } from "./fixtures/report-fbs";
import {
buildOrderGetResponsePayload,
buildOrderResponsePayload,
type CommandResultFixture,
} from "./fixtures/order-fbs";
const SESSION_ID = "phase-14-rename-session";
const GAME_ID = "14141414-1414-1414-1414-141414141414";
const WORLD = 4000;
const CENTRE = WORLD / 2;
const TURN = 4;
interface MockOpts {
storedOrder: CommandResultFixture[];
submitOutcome: "applied" | "rejected";
}
interface MockHandle {
get submittedRenameName(): string | null;
}
async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
const game: GameFixture = {
gameId: GAME_ID,
gameName: "Phase 14 Game",
gameType: "private",
status: "running",
ownerUserId: "user-1",
minPlayers: 2,
maxPlayers: 8,
enrollmentEndsAtMs: BigInt(Date.now() + 86_400_000),
createdAtMs: BigInt(Date.now() - 86_400_000),
updatedAtMs: BigInt(Date.now()),
currentTurn: TURN,
};
let storedOrder = opts.storedOrder.slice();
let lastSubmittedName: string | null = null;
let lastReportName = "Earth";
await page.route(
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
async (route) => {
const reqText = route.request().postData();
if (reqText === null) {
await route.fulfill({ status: 400 });
return;
}
const req = fromJson(
ExecuteCommandRequestSchema,
JSON.parse(reqText) as JsonValue,
);
let resultCode = "ok";
let payload: Uint8Array;
switch (req.messageType) {
case "lobby.my.games.list":
payload = buildMyGamesListPayload([game]);
break;
case "user.games.report": {
GameReportRequest.getRootAsGameReportRequest(
new ByteBuffer(req.payloadBytes),
).gameId(new UUID());
payload = buildReportPayload({
turn: TURN,
mapWidth: WORLD,
mapHeight: WORLD,
localPlanets: [
{
number: 17,
name: lastReportName,
x: CENTRE,
y: CENTRE,
size: 1000,
resources: 10,
capital: 0,
material: 0,
population: 850,
colonists: 25,
industry: 700,
production: "drive",
freeIndustry: 175,
},
],
});
break;
}
case "user.games.order": {
const decoded = UserGamesOrder.getRootAsUserGamesOrder(
new ByteBuffer(req.payloadBytes),
);
const length = decoded.commandsLength();
const fixtures: CommandResultFixture[] = [];
for (let i = 0; i < length; i++) {
const item = decoded.commands(i);
if (item === null) continue;
const cmdId = item.cmdId() ?? "";
// Decode the embedded planetRename payload to mirror it back
// in the response.
const inner = new (await import(
"../../src/proto/galaxy/fbs/order"
)).CommandPlanetRename();
item.payload(inner);
const submittedName = inner.name() ?? "";
lastSubmittedName = submittedName;
const applied = opts.submitOutcome === "applied";
fixtures.push({
cmdId,
planetNumber: Number(inner.number()),
name: submittedName,
applied,
errorCode: applied ? null : 1,
});
}
if (opts.submitOutcome === "applied") {
storedOrder = fixtures;
lastReportName = fixtures[0]?.name ?? lastReportName;
}
payload = buildOrderResponsePayload(GAME_ID, fixtures, Date.now());
break;
}
case "user.games.order.get": {
UserGamesOrderGet.getRootAsUserGamesOrderGet(
new ByteBuffer(req.payloadBytes),
);
payload = buildOrderGetResponsePayload(
GAME_ID,
storedOrder,
Date.now(),
storedOrder.length > 0,
);
break;
}
default:
resultCode = "internal_error";
payload = new Uint8Array();
}
const body = await forgeExecuteCommandResponseJson({
requestId: req.requestId,
timestampMs: BigInt(Date.now()),
resultCode,
payloadBytes: payload,
});
await route.fulfill({
status: 200,
contentType: "application/json",
body,
});
},
);
await page.route(
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
async () => {
await new Promise<void>(() => {});
},
);
return {
get submittedRenameName(): string | null {
return lastSubmittedName;
},
};
}
async function bootSession(page: Page): Promise<void> {
await page.goto("/__debug/store");
await expect(page.getByTestId("debug-store-ready")).toBeVisible();
await page.waitForFunction(() => window.__galaxyDebug?.ready === true);
await page.evaluate(() => window.__galaxyDebug!.clearSession());
await page.evaluate(
(id) => window.__galaxyDebug!.setDeviceSessionId(id),
SESSION_ID,
);
await page.evaluate(
(gameId) => window.__galaxyDebug!.clearOrderDraft(gameId),
GAME_ID,
);
}
async function clickPlanetCentre(page: Page): Promise<void> {
const canvas = page.locator("canvas");
const box = await canvas.boundingBox();
expect(box).not.toBeNull();
if (box === null) throw new Error("canvas has no bounding box");
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
}
test("rename a seeded planet, submit, observe overlay + persist after reload", async ({
page,
}, testInfo) => {
test.skip(
testInfo.project.name.startsWith("chromium-mobile"),
"phase 14 spec covers desktop layout; mobile inherits the same store",
);
const handle = await mockGateway(page, {
storedOrder: [],
submitOutcome: "applied",
});
await bootSession(page);
await page.goto(`/games/${GAME_ID}/map`);
await expect(page.getByTestId("active-view-map")).toHaveAttribute(
"data-status",
"ready",
);
await clickPlanetCentre(page);
const sidebar = page.getByTestId("sidebar-tool-inspector");
await expect(sidebar.getByTestId("inspector-planet-name")).toHaveText("Earth");
await sidebar.getByTestId("inspector-planet-rename-action").click();
const input = sidebar.getByTestId("inspector-planet-rename-input");
await input.fill("New-Earth");
await sidebar.getByTestId("inspector-planet-rename-confirm").click();
// Open the order tab and assert the row.
await page.getByTestId("sidebar-tab-order").click();
const orderTool = page.getByTestId("sidebar-tool-order");
await expect(orderTool.getByTestId("order-command-label-0")).toContainText(
"New-Earth",
);
await expect(orderTool.getByTestId("order-command-status-0")).toHaveText(
"valid",
);
await orderTool.getByTestId("order-submit").click();
await expect(orderTool.getByTestId("order-command-status-0")).toHaveText(
"applied",
);
expect(handle.submittedRenameName).toBe("New-Earth");
// Switch back to the inspector — overlay should reflect the new name.
await page.getByTestId("sidebar-tab-inspector").click();
await expect(sidebar.getByTestId("inspector-planet-name")).toHaveText(
"New-Earth",
);
// Reload: the order draft is persisted; on cache-miss boots the
// hydrate-from-server path takes over. Both round-trips re-apply
// the overlay so the player still sees the renamed planet.
await page.reload();
await expect(page.getByTestId("active-view-map")).toHaveAttribute(
"data-status",
"ready",
);
await page.getByTestId("sidebar-tab-order").click();
await expect(orderTool.getByTestId("order-command-label-0")).toContainText(
"New-Earth",
);
});
test("rejected submit keeps the old name and surfaces the failure", async ({
page,
}, testInfo) => {
test.skip(
testInfo.project.name.startsWith("chromium-mobile"),
"phase 14 spec covers desktop layout; mobile inherits the same store",
);
await mockGateway(page, {
storedOrder: [],
submitOutcome: "rejected",
});
await bootSession(page);
await page.goto(`/games/${GAME_ID}/map`);
await expect(page.getByTestId("active-view-map")).toHaveAttribute(
"data-status",
"ready",
);
await clickPlanetCentre(page);
const sidebar = page.getByTestId("sidebar-tool-inspector");
await sidebar.getByTestId("inspector-planet-rename-action").click();
await sidebar.getByTestId("inspector-planet-rename-input").fill("Mars-2");
await sidebar.getByTestId("inspector-planet-rename-confirm").click();
await page.getByTestId("sidebar-tab-order").click();
const orderTool = page.getByTestId("sidebar-tool-order");
await orderTool.getByTestId("order-submit").click();
await expect(orderTool.getByTestId("order-command-status-0")).toHaveText(
"rejected",
);
await page.getByTestId("sidebar-tab-inspector").click();
// Overlay does not apply rejected commands — old name persists.
await expect(sidebar.getByTestId("inspector-planet-name")).toHaveText("Earth");
});
+66
View File
@@ -0,0 +1,66 @@
// Parity tests for the TS port of `pkg/util/string.go.ValidateTypeName`.
// Cases are aligned with `pkg/util/string_test.go.TestValidateString`
// so the client-side and server-side validators reject the same set
// of inputs — a name that's locally valid is always accepted at the
// wire level.
import { describe, expect, test } from "vitest";
import {
validateEntityName,
type EntityNameInvalidReason,
} from "../src/lib/util/entity-name";
describe("validateEntityName", () => {
const valid: { name: string; input: string; expected: string }[] = [
{ name: "latin + digits", input: "Hello_World-123", expected: "Hello_World-123" },
{ name: "cyrillic", input: "Привет_мир-42", expected: "Привет_мир-42" },
{ name: "greek", input: "Αλφα_Βητα-2024", expected: "Αλφα_Βητα-2024" },
{ name: "arabic", input: "مرحبا_العالم-7", expected: "مرحبا_العالم-7" },
{ name: "japanese katakana", input: "テスト_ケース-1", expected: "テスト_ケース-1" },
{ name: "chinese", input: "你好_世界-123", expected: "你好_世界-123" },
{ name: "hindi (combining marks)", input: "नमस्ते_दुनिया-456", expected: "नमस्ते_दुनिया-456" },
{ name: "thai (combining marks)", input: "สวัสดี_โลก-789", expected: "สวัสดี_โลก-789" },
{ name: "korean", input: "안녕하세요_세계-101", expected: "안녕하세요_세계-101" },
{ name: "trim outer whitespace", input: " Earth ", expected: "Earth" },
{ name: "valid consecutive specials", input: "Valid_(special)_Chars", expected: "Valid_(special)_Chars" },
{ name: "all allowed specials", input: "A@#b$%c^*d-_e=+f~(g)[h]{i}j", expected: "A@#b$%c^*d-_e=+f~(g)[h]{i}j" },
];
for (const tc of valid) {
test(`accepts: ${tc.name}`, () => {
const result = validateEntityName(tc.input);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value).toBe(tc.expected);
}
});
}
const invalid: {
name: string;
input: string;
reason: EntityNameInvalidReason;
}[] = [
{ name: "empty after trim", input: " ", reason: "empty" },
{ name: "explicitly empty", input: "", reason: "empty" },
{ name: "too long", input: "ValidatedStringHasTooManyCharacters", reason: "too_long" },
{ name: "internal space", input: "Test 123", reason: "whitespace" },
{ name: "internal tab", input: "Test\tName", reason: "whitespace" },
{ name: "internal newline", input: "Test\nName", reason: "whitespace" },
{ name: "starts with special after trim", input: " -Test123", reason: "starts_with_special" },
{ name: "ends with special after trim", input: "Test123- ", reason: "ends_with_special" },
{ name: "emoji", input: "Test🙂Name", reason: "disallowed_character" },
{ name: "starts with special $", input: "$pecialString", reason: "starts_with_special" },
{ name: "ends with special _", input: "SpecialString_", reason: "ends_with_special" },
{ name: "too many consecutive specials", input: "Too_Many_(special[_]Chars", reason: "consecutive_specials" },
];
for (const tc of invalid) {
test(`rejects: ${tc.name}`, () => {
const result = validateEntityName(tc.input);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.reason).toBe(tc.reason);
}
});
}
});
+14 -1
View File
@@ -23,6 +23,14 @@ import {
SELECTION_CONTEXT_KEY,
SelectionStore,
} from "../src/lib/selection.svelte";
import {
RENDERED_REPORT_CONTEXT_KEY,
createRenderedReportSource,
} from "../src/lib/rendered-report.svelte";
import {
ORDER_DRAFT_CONTEXT_KEY,
OrderDraftStore,
} from "../src/sync/order-draft.svelte";
import type { GameReport, ReportPlanet } from "../src/api/game-state";
const pageMock = vi.hoisted(() => ({
@@ -70,17 +78,22 @@ function makeReport(planets: ReportPlanet[]): GameReport {
function withStores(report: GameReport | null): {
gameState: GameStateStore;
selection: SelectionStore;
orderDraft: OrderDraftStore;
context: Map<unknown, unknown>;
} {
const gameState = new GameStateStore();
gameState.report = report;
gameState.status = report === null ? "idle" : "ready";
const selection = new SelectionStore();
const orderDraft = new OrderDraftStore();
const renderedReport = createRenderedReportSource(gameState, orderDraft);
const context = new Map<unknown, unknown>([
[GAME_STATE_CONTEXT_KEY, gameState],
[SELECTION_CONTEXT_KEY, selection],
[ORDER_DRAFT_CONTEXT_KEY, orderDraft],
[RENDERED_REPORT_CONTEXT_KEY, renderedReport],
]);
return { gameState, selection, context };
return { gameState, selection, orderDraft, context };
}
beforeEach(() => {
+123 -1
View File
@@ -5,12 +5,19 @@
// drive it with synthetic `ReportPlanet` literals — no store.
import "@testing-library/jest-dom/vitest";
import { render } from "@testing-library/svelte";
import "fake-indexeddb/auto";
import { fireEvent, render } from "@testing-library/svelte";
import { beforeEach, describe, expect, test } from "vitest";
import { i18n } from "../src/lib/i18n/index.svelte";
import type { ReportPlanet } from "../src/api/game-state";
import Planet from "../src/lib/inspectors/planet.svelte";
import {
ORDER_DRAFT_CONTEXT_KEY,
OrderDraftStore,
} from "../src/sync/order-draft.svelte";
import { IDBCache } from "../src/platform/store/idb-cache";
import { openGalaxyDB } from "../src/platform/store/idb";
beforeEach(() => {
i18n.resetForTests("en");
@@ -192,6 +199,121 @@ describe("planet inspector", () => {
expect(ui.queryByTestId("inspector-planet-field-natural_resources")).toBeNull();
});
test("Rename action is hidden for non-local planets", () => {
const ui = render(Planet, {
props: {
planet: makePlanet({
number: 9,
name: "Far",
kind: "other",
owner: "Federation",
size: 100,
resources: 5,
}),
},
});
expect(ui.queryByTestId("inspector-planet-rename-action")).toBeNull();
});
test("Rename action opens an inline editor and validates locally", async () => {
const dbName = `galaxy-rename-${crypto.randomUUID()}`;
const db = await openGalaxyDB(dbName);
const cache = new IDBCache(db);
const draft = new OrderDraftStore();
await draft.init({ cache, gameId: "00000000-0000-0000-0000-000000000abc" });
const context = new Map<unknown, unknown>([
[ORDER_DRAFT_CONTEXT_KEY, draft],
]);
const ui = render(Planet, {
props: {
planet: makePlanet({
number: 7,
name: "Earth",
kind: "local",
size: 100,
resources: 5,
population: 100,
colonists: 0,
industry: 0,
industryStockpile: 0,
materialsStockpile: 0,
production: "drive",
freeIndustry: 0,
}),
},
context,
});
const action = ui.getByTestId("inspector-planet-rename-action");
await fireEvent.click(action);
const input = ui.getByTestId("inspector-planet-rename-input") as HTMLInputElement;
expect(input.value).toBe("Earth");
const confirm = ui.getByTestId("inspector-planet-rename-confirm");
expect(confirm).not.toBeDisabled();
await fireEvent.input(input, { target: { value: " " } });
expect(ui.getByTestId("inspector-planet-rename-error")).toBeVisible();
expect(confirm).toBeDisabled();
await fireEvent.input(input, { target: { value: "New Earth!" } });
// Whitespace inside disallowed
expect(ui.getByTestId("inspector-planet-rename-error")).toBeVisible();
expect(confirm).toBeDisabled();
await fireEvent.input(input, { target: { value: "Mars-2" } });
expect(ui.queryByTestId("inspector-planet-rename-error")).toBeNull();
expect(confirm).not.toBeDisabled();
await fireEvent.click(confirm);
expect(draft.commands).toHaveLength(1);
const cmd = draft.commands[0]!;
expect(cmd.kind).toBe("planetRename");
if (cmd.kind !== "planetRename") return;
expect(cmd.planetNumber).toBe(7);
expect(cmd.name).toBe("Mars-2");
draft.dispose();
db.close();
});
test("Cancel closes the editor without adding to the draft", async () => {
const dbName = `galaxy-rename-${crypto.randomUUID()}`;
const db = await openGalaxyDB(dbName);
const cache = new IDBCache(db);
const draft = new OrderDraftStore();
await draft.init({ cache, gameId: "00000000-0000-0000-0000-000000000abc" });
const context = new Map<unknown, unknown>([
[ORDER_DRAFT_CONTEXT_KEY, draft],
]);
const ui = render(Planet, {
props: {
planet: makePlanet({
number: 1,
name: "Earth",
kind: "local",
size: 100,
resources: 5,
population: 1,
colonists: 0,
industry: 0,
industryStockpile: 0,
materialsStockpile: 0,
production: "drive",
freeIndustry: 0,
}),
},
context,
});
await fireEvent.click(ui.getByTestId("inspector-planet-rename-action"));
await fireEvent.click(ui.getByTestId("inspector-planet-rename-cancel"));
expect(ui.queryByTestId("inspector-planet-rename")).toBeNull();
expect(draft.commands).toEqual([]);
draft.dispose();
db.close();
});
test("missing production string falls back to the localised placeholder", () => {
const ui = render(Planet, {
props: {
+154
View File
@@ -175,4 +175,158 @@ describe("OrderDraftStore", () => {
expect(reload.commands.map((c) => c.id)).toEqual(["c1"]);
reload.dispose();
});
test("absent cache row flips needsServerHydration flag", async () => {
const store = new OrderDraftStore();
await store.init({ cache, gameId: GAME_ID });
expect(store.needsServerHydration).toBe(true);
store.dispose();
});
test("explicitly empty cache row honours the user's empty draft", async () => {
const seeded = new OrderDraftStore();
await seeded.init({ cache, gameId: GAME_ID });
await seeded.add({
kind: "planetRename",
id: "00000000-0000-0000-0000-000000000001",
planetNumber: 7,
name: "Earth",
});
await seeded.remove("00000000-0000-0000-0000-000000000001");
seeded.dispose();
const reload = new OrderDraftStore();
await reload.init({ cache, gameId: GAME_ID });
expect(reload.needsServerHydration).toBe(false);
expect(reload.commands).toEqual([]);
reload.dispose();
});
test("planetRename validates locally and statuses reflect valid/invalid", async () => {
const store = new OrderDraftStore();
await store.init({ cache, gameId: GAME_ID });
await store.add({
kind: "planetRename",
id: "id-valid",
planetNumber: 1,
name: "Earth",
});
await store.add({
kind: "planetRename",
id: "id-invalid",
planetNumber: 2,
name: "$bad",
});
expect(store.statuses["id-valid"]).toBe("valid");
expect(store.statuses["id-invalid"]).toBe("invalid");
store.dispose();
});
test("markSubmitting / applyResults flip the status map", async () => {
const store = new OrderDraftStore();
await store.init({ cache, gameId: GAME_ID });
await store.add({
kind: "planetRename",
id: "id-1",
planetNumber: 1,
name: "Earth",
});
store.markSubmitting(["id-1"]);
expect(store.statuses["id-1"]).toBe("submitting");
store.applyResults({
results: new Map([["id-1", "applied"] as const]),
updatedAt: 99,
});
expect(store.statuses["id-1"]).toBe("applied");
expect(store.updatedAt).toBe(99);
store.dispose();
});
test("markRejected switches submitting entries to rejected", async () => {
const store = new OrderDraftStore();
await store.init({ cache, gameId: GAME_ID });
await store.add({
kind: "planetRename",
id: "id-1",
planetNumber: 1,
name: "Earth",
});
store.markSubmitting(["id-1"]);
store.markRejected(["id-1"]);
expect(store.statuses["id-1"]).toBe("rejected");
store.dispose();
});
test("revertSubmittingToValid restores status after a thrown submit", async () => {
const store = new OrderDraftStore();
await store.init({ cache, gameId: GAME_ID });
await store.add({
kind: "planetRename",
id: "id-1",
planetNumber: 1,
name: "Earth",
});
store.markSubmitting(["id-1"]);
store.revertSubmittingToValid();
expect(store.statuses["id-1"]).toBe("valid");
store.dispose();
});
test("hydrateFromServer seeds the draft on a fresh cache", async () => {
const fakeClient = {
executeCommand: async () => {
const { Builder } = await import("flatbuffers");
const { UUID } = await import("../src/proto/galaxy/fbs/common");
const order = await import("../src/proto/galaxy/fbs/order");
const builder = new Builder(128);
const cmdId = builder.createString("hydr-1");
const name = builder.createString("Hydrated");
const inner = order.CommandPlanetRename.createCommandPlanetRename(
builder,
BigInt(7),
name,
);
order.CommandItem.startCommandItem(builder);
order.CommandItem.addCmdId(builder, cmdId);
order.CommandItem.addPayloadType(
builder,
order.CommandPayload.CommandPlanetRename,
);
order.CommandItem.addPayload(builder, inner);
const item = order.CommandItem.endCommandItem(builder);
const cmds = order.UserGamesOrder.createCommandsVector(builder, [item]);
const [hi, lo] = (await import("../src/api/game-state")).uuidToHiLo(
GAME_ID,
);
const gameIdOffset = UUID.createUUID(builder, hi, lo);
order.UserGamesOrder.startUserGamesOrder(builder);
order.UserGamesOrder.addGameId(builder, gameIdOffset);
order.UserGamesOrder.addUpdatedAt(builder, BigInt(7));
order.UserGamesOrder.addCommands(builder, cmds);
const orderOffset = order.UserGamesOrder.endUserGamesOrder(builder);
order.UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder);
order.UserGamesOrderGetResponse.addFound(builder, true);
order.UserGamesOrderGetResponse.addOrder(builder, orderOffset);
const offset =
order.UserGamesOrderGetResponse.endUserGamesOrderGetResponse(builder);
builder.finish(offset);
return {
resultCode: "ok",
payloadBytes: builder.asUint8Array(),
};
},
};
const store = new OrderDraftStore();
await store.init({ cache, gameId: GAME_ID });
expect(store.needsServerHydration).toBe(true);
await store.hydrateFromServer({
client: fakeClient as never,
turn: 5,
});
expect(store.commands).toHaveLength(1);
expect(store.commands[0]!.id).toBe("hydr-1");
expect(store.updatedAt).toBe(7);
expect(store.needsServerHydration).toBe(false);
store.dispose();
});
});
+151
View File
@@ -0,0 +1,151 @@
// Vitest unit coverage for `sync/order-load.ts`. Builds FBS
// `UserGamesOrderGetResponse` payloads by hand and verifies the
// decoder produces the expected `OrderCommand[]`.
import { Builder } from "flatbuffers";
import { describe, expect, test, vi } from "vitest";
import type { GalaxyClient } from "../src/api/galaxy-client";
import { uuidToHiLo } from "../src/api/game-state";
import { UUID } from "../src/proto/galaxy/fbs/common";
import {
CommandItem,
CommandPayload,
CommandPlanetRename,
UserGamesOrder,
UserGamesOrderGet,
UserGamesOrderGetResponse,
} from "../src/proto/galaxy/fbs/order";
import { fetchOrder, OrderLoadError } from "../src/sync/order-load";
const GAME_ID = "11111111-2222-3333-4444-555555555555";
function mockClient(
executeCommand: (
messageType: string,
payload: Uint8Array,
) => Promise<{ resultCode: string; payloadBytes: Uint8Array }>,
): GalaxyClient {
return { executeCommand } as unknown as GalaxyClient;
}
function buildResponse(
commands: { id: string; planetNumber: number; name: string }[],
updatedAt: number,
found = true,
): Uint8Array {
const builder = new Builder(256);
let orderOffset = 0;
if (found) {
const itemOffsets = commands.map((c) => {
const cmdIdOffset = builder.createString(c.id);
const nameOffset = builder.createString(c.name);
const inner = CommandPlanetRename.createCommandPlanetRename(
builder,
BigInt(c.planetNumber),
nameOffset,
);
CommandItem.startCommandItem(builder);
CommandItem.addCmdId(builder, cmdIdOffset);
CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetRename);
CommandItem.addPayload(builder, inner);
return CommandItem.endCommandItem(builder);
});
const commandsVec = UserGamesOrder.createCommandsVector(builder, itemOffsets);
const [hi, lo] = uuidToHiLo(GAME_ID);
const gameIdOffset = UUID.createUUID(builder, hi, lo);
UserGamesOrder.startUserGamesOrder(builder);
UserGamesOrder.addGameId(builder, gameIdOffset);
UserGamesOrder.addUpdatedAt(builder, BigInt(updatedAt));
UserGamesOrder.addCommands(builder, commandsVec);
orderOffset = UserGamesOrder.endUserGamesOrder(builder);
}
UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder);
UserGamesOrderGetResponse.addFound(builder, found);
if (orderOffset !== 0) {
UserGamesOrderGetResponse.addOrder(builder, orderOffset);
}
const offset = UserGamesOrderGetResponse.endUserGamesOrderGetResponse(builder);
builder.finish(offset);
return builder.asUint8Array();
}
describe("fetchOrder", () => {
test("decodes a found response into typed commands", async () => {
const responsePayload = buildResponse(
[{ id: "cmd-1", planetNumber: 7, name: "Earth" }],
42,
);
const exec = vi.fn(async (messageType: string) => {
expect(messageType).toBe("user.games.order.get");
return { resultCode: "ok", payloadBytes: responsePayload };
});
const result = await fetchOrder(mockClient(exec), GAME_ID, 5);
expect(result.commands).toHaveLength(1);
const cmd = result.commands[0]!;
expect(cmd.kind).toBe("planetRename");
if (cmd.kind !== "planetRename") return;
expect(cmd.id).toBe("cmd-1");
expect(cmd.planetNumber).toBe(7);
expect(cmd.name).toBe("Earth");
expect(result.updatedAt).toBe(42);
});
test("found=false surfaces as an empty draft", async () => {
const responsePayload = buildResponse([], 0, false);
const exec = vi.fn(async () => ({
resultCode: "ok",
payloadBytes: responsePayload,
}));
const result = await fetchOrder(mockClient(exec), GAME_ID, 5);
expect(result.commands).toEqual([]);
expect(result.updatedAt).toBe(0);
});
test("rejects negative turn before issuing a request", async () => {
const exec = vi.fn(async () => ({
resultCode: "ok",
payloadBytes: new Uint8Array(),
}));
await expect(fetchOrder(mockClient(exec), GAME_ID, -1)).rejects.toBeInstanceOf(
OrderLoadError,
);
expect(exec).not.toHaveBeenCalled();
});
test("throws OrderLoadError on non-ok resultCode", async () => {
const exec = vi.fn(async () => ({
resultCode: "internal_error",
payloadBytes: new TextEncoder().encode(
JSON.stringify({ code: "boom", message: "down" }),
),
}));
await expect(fetchOrder(mockClient(exec), GAME_ID, 5)).rejects.toMatchObject({
name: "OrderLoadError",
resultCode: "internal_error",
code: "boom",
});
});
test("posts a well-formed UserGamesOrderGet payload", async () => {
let captured: Uint8Array | null = null;
const exec = vi.fn(async (_messageType, payload: Uint8Array) => {
captured = payload;
return {
resultCode: "ok",
payloadBytes: buildResponse([], 0, false),
};
});
await fetchOrder(mockClient(exec), GAME_ID, 9);
expect(captured).not.toBeNull();
const decoded = UserGamesOrderGet.getRootAsUserGamesOrderGet(
new (await import("flatbuffers")).ByteBuffer(captured!),
);
expect(Number(decoded.turn())).toBe(9);
const id = decoded.gameId();
expect(id).not.toBeNull();
});
});
+143
View File
@@ -0,0 +1,143 @@
// Vitest unit coverage for the pure `applyOrderOverlay` projection.
// Phase 14 understands `planetRename` only; future phases (set
// production, route updates) will extend the overlay and need
// equivalent cases here.
import { describe, expect, test } from "vitest";
import {
applyOrderOverlay,
type GameReport,
type ReportPlanet,
} from "../src/api/game-state";
import type { CommandStatus, OrderCommand } from "../src/sync/order-types";
function makePlanet(overrides: Partial<ReportPlanet>): ReportPlanet {
return {
number: 0,
name: "",
x: 0,
y: 0,
kind: "local",
owner: null,
size: null,
resources: null,
industryStockpile: null,
materialsStockpile: null,
industry: null,
population: null,
colonists: null,
production: null,
freeIndustry: null,
...overrides,
};
}
function makeReport(planets: ReportPlanet[]): GameReport {
return {
turn: 4,
mapWidth: 4000,
mapHeight: 4000,
planetCount: planets.length,
planets,
};
}
describe("applyOrderOverlay", () => {
test("returns the same report when no commands match", () => {
const report = makeReport([makePlanet({ number: 1, name: "Earth" })]);
const out = applyOrderOverlay(report, [], {});
expect(out).toBe(report);
});
test("renames a planet on applied commands", () => {
const report = makeReport([
makePlanet({ number: 1, name: "Earth" }),
makePlanet({ number: 2, name: "Mars" }),
]);
const cmd: OrderCommand = {
kind: "planetRename",
id: "cmd-1",
planetNumber: 1,
name: "New Earth",
};
const statuses: Record<string, CommandStatus> = { "cmd-1": "applied" };
const out = applyOrderOverlay(report, [cmd], statuses);
expect(out).not.toBe(report);
expect(out.planets[0]!.name).toBe("New Earth");
expect(out.planets[1]!.name).toBe("Mars");
// raw report stays untouched
expect(report.planets[0]!.name).toBe("Earth");
});
test("renames on submitting too (in-flight optimistic)", () => {
const report = makeReport([makePlanet({ number: 1, name: "Earth" })]);
const cmd: OrderCommand = {
kind: "planetRename",
id: "cmd-1",
planetNumber: 1,
name: "Pending",
};
const out = applyOrderOverlay(report, [cmd], { "cmd-1": "submitting" });
expect(out.planets[0]!.name).toBe("Pending");
});
test("skips unsubmitted statuses (draft/valid/invalid/rejected)", () => {
const report = makeReport([makePlanet({ number: 1, name: "Earth" })]);
const cmd: OrderCommand = {
kind: "planetRename",
id: "cmd-1",
planetNumber: 1,
name: "Tentative",
};
for (const status of ["draft", "valid", "invalid", "rejected"] as const) {
const out = applyOrderOverlay(report, [cmd], { "cmd-1": status });
expect(out.planets[0]!.name).toBe("Earth");
}
});
test("ignores rename for missing planet (visibility lost)", () => {
const report = makeReport([makePlanet({ number: 1, name: "Earth" })]);
const cmd: OrderCommand = {
kind: "planetRename",
id: "cmd-1",
planetNumber: 99,
name: "Phantom",
};
const out = applyOrderOverlay(report, [cmd], { "cmd-1": "applied" });
expect(out).toBe(report);
});
test("placeholder commands pass through", () => {
const report = makeReport([makePlanet({ number: 1, name: "Earth" })]);
const cmd: OrderCommand = {
kind: "placeholder",
id: "cmd-1",
label: "noop",
};
const out = applyOrderOverlay(report, [cmd], { "cmd-1": "applied" });
expect(out).toBe(report);
});
test("multiple renames apply in command order", () => {
const report = makeReport([makePlanet({ number: 1, name: "Old" })]);
const first: OrderCommand = {
kind: "planetRename",
id: "cmd-1",
planetNumber: 1,
name: "Mid",
};
const second: OrderCommand = {
kind: "planetRename",
id: "cmd-2",
planetNumber: 1,
name: "Final",
};
const out = applyOrderOverlay(report, [first, second], {
"cmd-1": "applied",
"cmd-2": "applied",
});
expect(out.planets[0]!.name).toBe("Final");
});
});
+222
View File
@@ -0,0 +1,222 @@
// Component coverage for the Phase 14 order-tab submit flow. Drives
// the tab against an in-memory `OrderDraftStore`, a synthetic
// `GalaxyClient`, and a stubbed `GameStateStore.refresh`. Every
// case asserts both the rendered DOM (status badges, button state)
// and the side effect on the draft store (per-command status flips).
import "@testing-library/jest-dom/vitest";
import "fake-indexeddb/auto";
import { fireEvent, render, waitFor } from "@testing-library/svelte";
import { Builder } from "flatbuffers";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import OrderTab from "../src/lib/sidebar/order-tab.svelte";
import {
ORDER_DRAFT_CONTEXT_KEY,
OrderDraftStore,
} from "../src/sync/order-draft.svelte";
import {
GAME_STATE_CONTEXT_KEY,
GameStateStore,
} from "../src/lib/game-state.svelte";
import {
GALAXY_CLIENT_CONTEXT_KEY,
GalaxyClientHolder,
} from "../src/lib/galaxy-client-context.svelte";
import { i18n } from "../src/lib/i18n/index.svelte";
import { uuidToHiLo } from "../src/api/game-state";
import type { GalaxyClient } from "../src/api/galaxy-client";
import type { OrderCommand } from "../src/sync/order-types";
import { IDBCache } from "../src/platform/store/idb-cache";
import { openGalaxyDB, type GalaxyDB } from "../src/platform/store/idb";
import type { Cache } from "../src/platform/store/index";
import { UUID } from "../src/proto/galaxy/fbs/common";
import {
CommandItem,
CommandPayload,
CommandPlanetRename,
UserGamesOrderResponse,
} from "../src/proto/galaxy/fbs/order";
const GAME_ID = "11111111-2222-3333-4444-555555555555";
let db: Awaited<ReturnType<typeof openGalaxyDB>>;
let dbName: string;
let cache: Cache;
beforeEach(async () => {
dbName = `galaxy-order-tab-${crypto.randomUUID()}`;
db = await openGalaxyDB(dbName);
cache = new IDBCache(db);
i18n.resetForTests("en");
});
afterEach(async () => {
db.close();
await new Promise<void>((resolve) => {
const req = indexedDB.deleteDatabase(dbName);
req.onsuccess = () => resolve();
req.onerror = () => resolve();
req.onblocked = () => resolve();
});
});
interface Setup {
context: Map<unknown, unknown>;
draft: OrderDraftStore;
gameState: GameStateStore;
clientHolder: GalaxyClientHolder;
exec: ReturnType<typeof vi.fn>;
refresh: ReturnType<typeof vi.fn>;
}
function buildResponse(
commands: { id: string; applied: boolean | null; errorCode: number | null }[],
updatedAt: number,
): Uint8Array {
const builder = new Builder(256);
const itemOffsets = commands.map((c) => {
const cmdIdOffset = builder.createString(c.id);
const nameOffset = builder.createString("ignored");
const inner = CommandPlanetRename.createCommandPlanetRename(
builder,
BigInt(0),
nameOffset,
);
CommandItem.startCommandItem(builder);
CommandItem.addCmdId(builder, cmdIdOffset);
if (c.applied !== null) CommandItem.addCmdApplied(builder, c.applied);
if (c.errorCode !== null) {
CommandItem.addCmdErrorCode(builder, BigInt(c.errorCode));
}
CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetRename);
CommandItem.addPayload(builder, inner);
return CommandItem.endCommandItem(builder);
});
const commandsVec = UserGamesOrderResponse.createCommandsVector(
builder,
itemOffsets,
);
const [hi, lo] = uuidToHiLo(GAME_ID);
const gameIdOffset = UUID.createUUID(builder, hi, lo);
UserGamesOrderResponse.startUserGamesOrderResponse(builder);
UserGamesOrderResponse.addGameId(builder, gameIdOffset);
UserGamesOrderResponse.addUpdatedAt(builder, BigInt(updatedAt));
UserGamesOrderResponse.addCommands(builder, commandsVec);
const offset = UserGamesOrderResponse.endUserGamesOrderResponse(builder);
builder.finish(offset);
return builder.asUint8Array();
}
async function makeSetup(commands: OrderCommand[]): Promise<Setup> {
const draft = new OrderDraftStore();
await draft.init({ cache, gameId: GAME_ID });
for (const cmd of commands) {
await draft.add(cmd);
}
const gameState = new GameStateStore();
gameState.gameId = GAME_ID;
gameState.status = "ready";
const refresh = vi.fn(async () => {});
gameState.refresh = refresh as unknown as typeof gameState.refresh;
const clientHolder = new GalaxyClientHolder();
const exec = vi.fn(async (_messageType: string, _payload: Uint8Array) => ({
resultCode: "ok",
payloadBytes: buildResponse(
commands.map((cmd) => ({
id: cmd.id,
applied: true,
errorCode: null,
})),
17,
),
}));
clientHolder.set({ executeCommand: exec } as unknown as GalaxyClient);
const context = new Map<unknown, unknown>([
[ORDER_DRAFT_CONTEXT_KEY, draft],
[GAME_STATE_CONTEXT_KEY, gameState],
[GALAXY_CLIENT_CONTEXT_KEY, clientHolder],
]);
return { context, draft, gameState, clientHolder, exec, refresh };
}
describe("order-tab", () => {
test("renders the empty state when the draft has no commands", async () => {
const { context } = await makeSetup([]);
const ui = render(OrderTab, { context });
expect(ui.getByTestId("order-empty")).toBeVisible();
expect(ui.queryByTestId("order-submit")).toBeNull();
});
test("Submit is disabled when every entry is invalid", async () => {
const { context } = await makeSetup([
{ kind: "planetRename", id: "id-1", planetNumber: 1, name: "" },
]);
const ui = render(OrderTab, { context });
const submit = ui.getByTestId("order-submit");
expect(submit).toBeDisabled();
expect(ui.getByTestId("order-command-status-0")).toHaveTextContent(
"invalid",
);
});
test("Submit posts every valid command and applies returned statuses", async () => {
const { context, draft, exec, refresh } = await makeSetup([
{ kind: "planetRename", id: "id-1", planetNumber: 1, name: "Earth" },
]);
const ui = render(OrderTab, { context });
const submit = ui.getByTestId("order-submit");
expect(submit).not.toBeDisabled();
expect(ui.getByTestId("order-command-status-0")).toHaveTextContent("valid");
await fireEvent.click(submit);
await waitFor(() => {
expect(draft.statuses["id-1"]).toBe("applied");
});
expect(exec).toHaveBeenCalledTimes(1);
expect(refresh).toHaveBeenCalledTimes(1);
expect(ui.getByTestId("order-command-status-0")).toHaveTextContent(
"applied",
);
});
test("Non-ok response marks every submitting entry as rejected", async () => {
const { context, draft, refresh } = await makeSetup([
{ kind: "planetRename", id: "id-1", planetNumber: 1, name: "Earth" },
]);
const exec = vi.fn(async () => ({
resultCode: "invalid_request",
payloadBytes: new TextEncoder().encode(
JSON.stringify({ code: "boom", message: "down" }),
),
}));
const holder = context.get(GALAXY_CLIENT_CONTEXT_KEY) as GalaxyClientHolder;
holder.set({ executeCommand: exec } as unknown as GalaxyClient);
const ui = render(OrderTab, { context });
await fireEvent.click(ui.getByTestId("order-submit"));
await waitFor(() => {
expect(draft.statuses["id-1"]).toBe("rejected");
});
expect(refresh).not.toHaveBeenCalled();
expect(ui.getByTestId("order-submit-error")).toHaveTextContent("down");
});
test("Already-applied entries do not get re-submitted", async () => {
const { context, draft, exec } = await makeSetup([
{ kind: "planetRename", id: "id-1", planetNumber: 1, name: "Earth" },
]);
draft.markSubmitting(["id-1"]);
draft.applyResults({
results: new Map([["id-1", "applied"] as const]),
updatedAt: 1,
});
const ui = render(OrderTab, { context });
const submit = ui.getByTestId("order-submit");
expect(submit).toBeDisabled();
expect(exec).not.toHaveBeenCalled();
});
});
+181
View File
@@ -0,0 +1,181 @@
// Vitest unit coverage for `sync/submit.ts`. Drives the submit
// pipeline against a stub `GalaxyClient` whose `executeCommand`
// hand-builds FBS responses, so the parser is exercised against
// payloads identical to what the real gateway returns.
import { Builder } from "flatbuffers";
import { describe, expect, test, vi } from "vitest";
import type { GalaxyClient } from "../src/api/galaxy-client";
import { uuidToHiLo } from "../src/api/game-state";
import { UUID } from "../src/proto/galaxy/fbs/common";
import {
CommandItem,
CommandPlanetRename,
CommandPayload,
UserGamesOrder,
UserGamesOrderResponse,
} from "../src/proto/galaxy/fbs/order";
import { submitOrder } from "../src/sync/submit";
import type { OrderCommand } from "../src/sync/order-types";
const GAME_ID = "11111111-2222-3333-4444-555555555555";
function mockClient(
executeCommand: (
messageType: string,
payload: Uint8Array,
) => Promise<{ resultCode: string; payloadBytes: Uint8Array }>,
): GalaxyClient {
return { executeCommand } as unknown as GalaxyClient;
}
function buildResponse(
commands: { id: string; applied: boolean | null; errorCode: number | null }[],
updatedAt: number,
): Uint8Array {
const builder = new Builder(256);
const itemOffsets = commands.map((c) => {
const cmdIdOffset = builder.createString(c.id);
const nameOffset = builder.createString("ignored");
const payloadOffset = CommandPlanetRename.createCommandPlanetRename(
builder,
BigInt(0),
nameOffset,
);
CommandItem.startCommandItem(builder);
CommandItem.addCmdId(builder, cmdIdOffset);
if (c.applied !== null) CommandItem.addCmdApplied(builder, c.applied);
if (c.errorCode !== null) {
CommandItem.addCmdErrorCode(builder, BigInt(c.errorCode));
}
CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetRename);
CommandItem.addPayload(builder, payloadOffset);
return CommandItem.endCommandItem(builder);
});
const commandsVec = UserGamesOrderResponse.createCommandsVector(builder, itemOffsets);
const [hi, lo] = uuidToHiLo(GAME_ID);
const gameIdOffset = UUID.createUUID(builder, hi, lo);
UserGamesOrderResponse.startUserGamesOrderResponse(builder);
UserGamesOrderResponse.addGameId(builder, gameIdOffset);
UserGamesOrderResponse.addUpdatedAt(builder, BigInt(updatedAt));
UserGamesOrderResponse.addCommands(builder, commandsVec);
const offset = UserGamesOrderResponse.endUserGamesOrderResponse(builder);
builder.finish(offset);
return builder.asUint8Array();
}
const sampleRename: OrderCommand = {
kind: "planetRename",
id: "00000000-0000-0000-0000-00000000aaaa",
planetNumber: 7,
name: "Earth",
};
describe("submitOrder", () => {
test("decodes per-command results from a populated response", async () => {
const responsePayload = buildResponse(
[{ id: sampleRename.id, applied: true, errorCode: null }],
99,
);
const exec = vi.fn(async () => ({
resultCode: "ok",
payloadBytes: responsePayload,
}));
const result = await submitOrder(mockClient(exec), GAME_ID, [sampleRename]);
expect(exec).toHaveBeenCalledOnce();
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.results.get(sampleRename.id)).toBe("applied");
expect(result.errorCodes.get(sampleRename.id)).toBeNull();
expect(result.updatedAt).toBe(99);
});
test("falls back to batch-level applied when commands array is empty", async () => {
// Hand-craft an envelope without `commands` to mimic the legacy
// gateway behaviour (or a 204 wrapped via the fallback path).
const builder = new Builder(64);
UserGamesOrderResponse.startUserGamesOrderResponse(builder);
const offset = UserGamesOrderResponse.endUserGamesOrderResponse(builder);
builder.finish(offset);
const exec = vi.fn(async () => ({
resultCode: "ok",
payloadBytes: builder.asUint8Array(),
}));
const result = await submitOrder(mockClient(exec), GAME_ID, [sampleRename]);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.results.get(sampleRename.id)).toBe("applied");
expect(result.errorCodes.get(sampleRename.id)).toBeNull();
});
test("surfaces mixed applied / rejected entries by cmd id", async () => {
const second: OrderCommand = {
kind: "planetRename",
id: "00000000-0000-0000-0000-00000000bbbb",
planetNumber: 8,
name: "Mars",
};
const responsePayload = buildResponse(
[
{ id: sampleRename.id, applied: true, errorCode: null },
{ id: second.id, applied: false, errorCode: 42 },
],
120,
);
const exec = vi.fn(async () => ({
resultCode: "ok",
payloadBytes: responsePayload,
}));
const result = await submitOrder(mockClient(exec), GAME_ID, [sampleRename, second]);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.results.get(sampleRename.id)).toBe("applied");
expect(result.errorCodes.get(sampleRename.id)).toBeNull();
expect(result.results.get(second.id)).toBe("rejected");
expect(result.errorCodes.get(second.id)).toBe(42);
});
test("returns SubmitFailure on non-ok resultCode without throwing", async () => {
const exec = vi.fn(async () => ({
resultCode: "invalid_request",
payloadBytes: new TextEncoder().encode(
JSON.stringify({ code: "validation_failed", message: "bad name" }),
),
}));
const result = await submitOrder(mockClient(exec), GAME_ID, [sampleRename]);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.resultCode).toBe("invalid_request");
expect(result.code).toBe("validation_failed");
expect(result.message).toBe("bad name");
});
test("posts a well-formed UserGamesOrder payload", async () => {
let captured: Uint8Array | null = null;
const exec = vi.fn(async (_messageType, payload: Uint8Array) => {
captured = payload;
return { resultCode: "ok", payloadBytes: new Uint8Array() };
});
await submitOrder(mockClient(exec), GAME_ID, [sampleRename]);
expect(captured).not.toBeNull();
const decoded = UserGamesOrder.getRootAsUserGamesOrder(
new (await import("flatbuffers")).ByteBuffer(captured!),
);
expect(decoded.commandsLength()).toBe(1);
const item = decoded.commands(0);
expect(item).not.toBeNull();
expect(item!.cmdId()).toBe(sampleRename.id);
expect(item!.payloadType()).toBe(CommandPayload.CommandPlanetRename);
const inner = new CommandPlanetRename();
item!.payload(inner);
expect(Number(inner.number())).toBe(7);
expect(inner.name()).toBe("Earth");
});
});