ui/phase-20: ship-group inspector actions
Eight ship-group operations land on the inspector behind a single inline-form panel: split, send, load, unload, modernize, dismantle, transfer, join fleet. Each action either appends a typed command to the local order draft or surfaces a tooltip explaining the disabled state. Partial-ship operations emit an implicit breakShipGroup command before the targeted action so the engine sees a clean (Break, Action) pair on the wire. `pkg/calc.BlockUpgradeCost` migrates from `game/internal/controller/ship_group_upgrade.go` so the calc bridge can wrap a pure pkg/calc formula; the controller now imports it. The bridge surfaces the function as `core.blockUpgradeCost`, which the inspector calls once per ship block to render the modernize cost preview. `GameReport.otherRaces` is decoded from the report's player block (non-extinct, ≠ self) and feeds the transfer-to-race picker. The planet inspector's stationed-ship rows become clickable for own groups so the actions panel is reachable from the standard click flow (the renderer continues to hide on-planet groups). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,12 @@
|
||||
import type { Cache } from "../platform/store/index";
|
||||
import type { GalaxyClient } from "../api/galaxy-client";
|
||||
import { fetchOrder } from "./order-load";
|
||||
import type { CommandStatus, OrderCommand } from "./order-types";
|
||||
import {
|
||||
isShipGroupCargo,
|
||||
isShipGroupUpgradeTech,
|
||||
type CommandStatus,
|
||||
type OrderCommand,
|
||||
} from "./order-types";
|
||||
import { submitOrder } from "./submit";
|
||||
import { validateEntityName } from "$lib/util/entity-name";
|
||||
import { validateShipClass } from "$lib/util/ship-class-validation";
|
||||
@@ -513,6 +518,68 @@ function validateCommand(cmd: OrderCommand): CommandStatus {
|
||||
// active production / ship groups. Local validation only
|
||||
// guards the name shape.
|
||||
return validateEntityName(cmd.name).ok ? "valid" : "invalid";
|
||||
case "breakShipGroup":
|
||||
// Engine rule (`controller/ship_group.go.breakGroup`):
|
||||
// quantity must be at least 1 and strictly less than the
|
||||
// source group size. We do not know the source size here
|
||||
// (it lives on the report), so the inspector enforces the
|
||||
// upper bound before emitting; locally we only refuse the
|
||||
// degenerate cases — non-positive `quantity`, missing or
|
||||
// equal UUIDs.
|
||||
if (cmd.quantity <= 0) return "invalid";
|
||||
if (!isUuid(cmd.groupId) || !isUuid(cmd.newGroupId)) return "invalid";
|
||||
if (cmd.groupId === cmd.newGroupId) return "invalid";
|
||||
return "valid";
|
||||
case "sendShipGroup":
|
||||
// Reach is enforced by the picker before the command lands
|
||||
// in the draft. Locally we only refuse a degenerate
|
||||
// destination (the engine uses planet number `0` as the
|
||||
// "no planet" sentinel; FBS encodes as `int64`, so any
|
||||
// strictly-positive number is wire-valid).
|
||||
if (cmd.destinationPlanetNumber <= 0) return "invalid";
|
||||
if (!isUuid(cmd.groupId)) return "invalid";
|
||||
return "valid";
|
||||
case "loadShipGroup":
|
||||
// Cargo type and quantity are pre-checked by the inspector
|
||||
// against the planet stock and the group's free capacity;
|
||||
// local validation only guards the wire-valid shape.
|
||||
if (!isShipGroupCargo(cmd.cargo)) return "invalid";
|
||||
if (cmd.quantity <= 0) return "invalid";
|
||||
if (!isUuid(cmd.groupId)) return "invalid";
|
||||
return "valid";
|
||||
case "unloadShipGroup":
|
||||
if (cmd.quantity <= 0) return "invalid";
|
||||
if (!isUuid(cmd.groupId)) return "invalid";
|
||||
return "valid";
|
||||
case "upgradeShipGroup":
|
||||
// Engine rule
|
||||
// (`controller/ship_group_upgrade.go.shipGroupUpgrade:56`):
|
||||
// `tech === "ALL"` requires `level === 0`; per-block tech
|
||||
// requires a strictly positive level. The inspector also
|
||||
// caps the level to the player's race tech, but the
|
||||
// engine re-validates server-side.
|
||||
if (!isShipGroupUpgradeTech(cmd.tech)) return "invalid";
|
||||
if (cmd.tech === "ALL") {
|
||||
if (cmd.level !== 0) return "invalid";
|
||||
} else if (cmd.level <= 0) {
|
||||
return "invalid";
|
||||
}
|
||||
if (!isUuid(cmd.groupId)) return "invalid";
|
||||
return "valid";
|
||||
case "dismantleShipGroup":
|
||||
return isUuid(cmd.groupId) ? "valid" : "invalid";
|
||||
case "transferShipGroup":
|
||||
// `acceptor` is a race name; race names follow the same
|
||||
// entity-name rules as planet/fleet names. The inspector
|
||||
// restricts the picker to `GameReport.otherRaces`, so a
|
||||
// locally-valid name is always a real race.
|
||||
if (!validateEntityName(cmd.acceptor).ok) return "invalid";
|
||||
if (!isUuid(cmd.groupId)) return "invalid";
|
||||
return "valid";
|
||||
case "joinFleetShipGroup":
|
||||
if (!validateEntityName(cmd.name).ok) return "invalid";
|
||||
if (!isUuid(cmd.groupId)) return "invalid";
|
||||
return "valid";
|
||||
case "placeholder":
|
||||
// Phase 12 placeholder entries are content-free and never
|
||||
// transition out of `draft` — they are not submittable.
|
||||
|
||||
@@ -18,8 +18,18 @@ import {
|
||||
CommandPlanetRouteSet,
|
||||
CommandShipClassCreate,
|
||||
CommandShipClassRemove,
|
||||
CommandShipGroupBreak,
|
||||
CommandShipGroupDismantle,
|
||||
CommandShipGroupJoinFleet,
|
||||
CommandShipGroupLoad,
|
||||
CommandShipGroupSend,
|
||||
CommandShipGroupTransfer,
|
||||
CommandShipGroupUnload,
|
||||
CommandShipGroupUpgrade,
|
||||
PlanetProduction,
|
||||
PlanetRouteLoadType,
|
||||
ShipGroupCargo,
|
||||
ShipGroupUpgradeTech,
|
||||
UserGamesOrderGet,
|
||||
UserGamesOrderGetResponse,
|
||||
} from "../proto/galaxy/fbs/order";
|
||||
@@ -27,6 +37,8 @@ import type {
|
||||
CargoLoadType,
|
||||
OrderCommand,
|
||||
ProductionType,
|
||||
ShipGroupCargo as ShipGroupCargoLiteral,
|
||||
ShipGroupUpgradeTech as ShipGroupUpgradeTechLiteral,
|
||||
} from "./order-types";
|
||||
|
||||
const MESSAGE_TYPE = "user.games.order.get";
|
||||
@@ -222,6 +234,102 @@ function decodeCommand(item: CommandItemView): OrderCommand | null {
|
||||
name: inner.name() ?? "",
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandShipGroupBreak: {
|
||||
const inner = new CommandShipGroupBreak();
|
||||
item.payload(inner);
|
||||
return {
|
||||
kind: "breakShipGroup",
|
||||
id,
|
||||
groupId: inner.id() ?? "",
|
||||
newGroupId: inner.newId() ?? "",
|
||||
quantity: Number(inner.quantity()),
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandShipGroupSend: {
|
||||
const inner = new CommandShipGroupSend();
|
||||
item.payload(inner);
|
||||
return {
|
||||
kind: "sendShipGroup",
|
||||
id,
|
||||
groupId: inner.id() ?? "",
|
||||
destinationPlanetNumber: Number(inner.destination()),
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandShipGroupLoad: {
|
||||
const inner = new CommandShipGroupLoad();
|
||||
item.payload(inner);
|
||||
const cargo = shipGroupCargoFromFBS(inner.cargo());
|
||||
if (cargo === null) {
|
||||
console.warn(
|
||||
`fetchOrder: skipping CommandShipGroupLoad with unknown cargo enum (${inner.cargo()})`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
kind: "loadShipGroup",
|
||||
id,
|
||||
groupId: inner.id() ?? "",
|
||||
cargo,
|
||||
quantity: inner.quantity(),
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandShipGroupUnload: {
|
||||
const inner = new CommandShipGroupUnload();
|
||||
item.payload(inner);
|
||||
return {
|
||||
kind: "unloadShipGroup",
|
||||
id,
|
||||
groupId: inner.id() ?? "",
|
||||
quantity: inner.quantity(),
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandShipGroupUpgrade: {
|
||||
const inner = new CommandShipGroupUpgrade();
|
||||
item.payload(inner);
|
||||
const tech = shipGroupUpgradeTechFromFBS(inner.tech());
|
||||
if (tech === null) {
|
||||
console.warn(
|
||||
`fetchOrder: skipping CommandShipGroupUpgrade with unknown tech enum (${inner.tech()})`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
kind: "upgradeShipGroup",
|
||||
id,
|
||||
groupId: inner.id() ?? "",
|
||||
tech,
|
||||
level: inner.level(),
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandShipGroupDismantle: {
|
||||
const inner = new CommandShipGroupDismantle();
|
||||
item.payload(inner);
|
||||
return {
|
||||
kind: "dismantleShipGroup",
|
||||
id,
|
||||
groupId: inner.id() ?? "",
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandShipGroupTransfer: {
|
||||
const inner = new CommandShipGroupTransfer();
|
||||
item.payload(inner);
|
||||
return {
|
||||
kind: "transferShipGroup",
|
||||
id,
|
||||
groupId: inner.id() ?? "",
|
||||
acceptor: inner.acceptor() ?? "",
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandShipGroupJoinFleet: {
|
||||
const inner = new CommandShipGroupJoinFleet();
|
||||
item.payload(inner);
|
||||
return {
|
||||
kind: "joinFleetShipGroup",
|
||||
id,
|
||||
groupId: inner.id() ?? "",
|
||||
name: inner.name() ?? "",
|
||||
};
|
||||
}
|
||||
default:
|
||||
console.warn(
|
||||
`fetchOrder: skipping unknown command kind (payloadType=${payloadType})`,
|
||||
@@ -288,6 +396,55 @@ export function cargoLoadTypeFromFBS(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shipGroupCargoFromFBS reverses `shipGroupCargoToFBS` from
|
||||
* `submit.ts`. `ShipGroupCargo.UNKNOWN` and any out-of-band value
|
||||
* yield `null` so the caller drops the entry rather than
|
||||
* fabricating a synthetic cargo type.
|
||||
*/
|
||||
export function shipGroupCargoFromFBS(
|
||||
value: ShipGroupCargo,
|
||||
): ShipGroupCargoLiteral | null {
|
||||
switch (value) {
|
||||
case ShipGroupCargo.COL:
|
||||
return "COL";
|
||||
case ShipGroupCargo.CAP:
|
||||
return "CAP";
|
||||
case ShipGroupCargo.MAT:
|
||||
return "MAT";
|
||||
case ShipGroupCargo.UNKNOWN:
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shipGroupUpgradeTechFromFBS reverses `shipGroupUpgradeTechToFBS`
|
||||
* from `submit.ts`. `ShipGroupUpgradeTech.UNKNOWN` and any
|
||||
* out-of-band value yield `null`.
|
||||
*/
|
||||
export function shipGroupUpgradeTechFromFBS(
|
||||
value: ShipGroupUpgradeTech,
|
||||
): ShipGroupUpgradeTechLiteral | null {
|
||||
switch (value) {
|
||||
case ShipGroupUpgradeTech.ALL:
|
||||
return "ALL";
|
||||
case ShipGroupUpgradeTech.DRIVE:
|
||||
return "DRIVE";
|
||||
case ShipGroupUpgradeTech.WEAPONS:
|
||||
return "WEAPONS";
|
||||
case ShipGroupUpgradeTech.SHIELDS:
|
||||
return "SHIELDS";
|
||||
case ShipGroupUpgradeTech.CARGO:
|
||||
return "CARGO";
|
||||
case ShipGroupUpgradeTech.UNKNOWN:
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeError(
|
||||
payload: Uint8Array,
|
||||
resultCode: string,
|
||||
|
||||
@@ -166,6 +166,209 @@ export interface RemoveShipClassCommand {
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ShipGroupCargo mirrors the engine `ShipGroupCargo` enum
|
||||
* (`pkg/schema/fbs/order.fbs`). Three values: colonists, capital
|
||||
* (industry crates), and materials. Empty (`EMP`) is a route-level
|
||||
* concept (`PlanetRouteLoadType`) and is not a valid cargo type for a
|
||||
* ship-group load command — the FBS enum deliberately omits it.
|
||||
*/
|
||||
export type ShipGroupCargo = "COL" | "CAP" | "MAT";
|
||||
|
||||
/**
|
||||
* SHIP_GROUP_CARGO_VALUES is the canonical tuple of `ShipGroupCargo`
|
||||
* literals. Used by validators and the FBS converters in
|
||||
* `submit.ts` and `order-load.ts` to narrow incoming strings.
|
||||
*/
|
||||
export const SHIP_GROUP_CARGO_VALUES = [
|
||||
"COL",
|
||||
"CAP",
|
||||
"MAT",
|
||||
] as const satisfies readonly ShipGroupCargo[];
|
||||
|
||||
/**
|
||||
* isShipGroupCargo narrows an arbitrary string to the
|
||||
* `ShipGroupCargo` union.
|
||||
*/
|
||||
export function isShipGroupCargo(value: string): value is ShipGroupCargo {
|
||||
return (SHIP_GROUP_CARGO_VALUES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* ShipGroupUpgradeTech mirrors the engine `ShipGroupUpgradeTech`
|
||||
* enum (`pkg/schema/fbs/order.fbs`): `ALL` upgrades every applicable
|
||||
* block to the player's current race tech (level argument must be 0
|
||||
* — see `controller/ship_group_upgrade.go:56`); the four per-block
|
||||
* values upgrade exactly that block to the requested level.
|
||||
*/
|
||||
export type ShipGroupUpgradeTech =
|
||||
| "ALL"
|
||||
| "DRIVE"
|
||||
| "WEAPONS"
|
||||
| "SHIELDS"
|
||||
| "CARGO";
|
||||
|
||||
/**
|
||||
* SHIP_GROUP_UPGRADE_TECH_VALUES is the canonical tuple of
|
||||
* `ShipGroupUpgradeTech` literals. The order matches the FBS enum.
|
||||
*/
|
||||
export const SHIP_GROUP_UPGRADE_TECH_VALUES = [
|
||||
"ALL",
|
||||
"DRIVE",
|
||||
"WEAPONS",
|
||||
"SHIELDS",
|
||||
"CARGO",
|
||||
] as const satisfies readonly ShipGroupUpgradeTech[];
|
||||
|
||||
/**
|
||||
* isShipGroupUpgradeTech narrows an arbitrary string to the
|
||||
* `ShipGroupUpgradeTech` union.
|
||||
*/
|
||||
export function isShipGroupUpgradeTech(
|
||||
value: string,
|
||||
): value is ShipGroupUpgradeTech {
|
||||
return (SHIP_GROUP_UPGRADE_TECH_VALUES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* BreakShipGroupCommand splits a player-owned ship group into two:
|
||||
* the original keeps `originalCount - quantity` ships and a new group
|
||||
* with `newGroupId` carries `quantity`. Used both as a stand-alone
|
||||
* action and as the implicit prelude to Send / Load / Unload /
|
||||
* Modernize / Dismantle / Transfer when the player picks fewer than
|
||||
* all ships. Engine rules (`controller/ship_group.go.breakGroup`):
|
||||
* source group must be `StateInOrbit`, `quantity` must be in `[1,
|
||||
* originalCount - 1]` for a real split. The new group carries a
|
||||
* proportional slice of the cargo and starts unattached to any fleet.
|
||||
*/
|
||||
export interface BreakShipGroupCommand {
|
||||
readonly kind: "breakShipGroup";
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
readonly newGroupId: string;
|
||||
readonly quantity: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* SendShipGroupCommand launches a player-owned ship group toward a
|
||||
* destination planet. Engine rules
|
||||
* (`controller/ship_group_send.go.shipGroupSend`): group must be
|
||||
* `StateInOrbit`; ship class must have a non-zero drive block; the
|
||||
* destination must be within the player's current
|
||||
* `FlightDistance() = localPlayerDrive * 40` (torus-aware).
|
||||
* The picker filters the planet list before emitting, so a draft
|
||||
* entry that survives validation is always reachable at submit time.
|
||||
*/
|
||||
export interface SendShipGroupCommand {
|
||||
readonly kind: "sendShipGroup";
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
readonly destinationPlanetNumber: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* LoadShipGroupCommand loads cargo of one of the three ship-group
|
||||
* cargo types onto a player-owned group. Engine rules
|
||||
* (`controller/ship_group.go.shipGroupLoad`): group must be
|
||||
* `StateInOrbit`; planet must be owned by the player or unowned;
|
||||
* ship class must have a non-zero cargo block; the existing cargo
|
||||
* type (if any) must equal `cargo`; `quantity` is bounded by the
|
||||
* planet's stock and the group's free capacity. The inspector
|
||||
* pre-checks each of these so a draft entry is always wire-valid.
|
||||
*/
|
||||
export interface LoadShipGroupCommand {
|
||||
readonly kind: "loadShipGroup";
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
readonly cargo: ShipGroupCargo;
|
||||
readonly quantity: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* UnloadShipGroupCommand drops cargo from a player-owned group at
|
||||
* its current orbit. Engine rules
|
||||
* (`controller/ship_group.go.shipGroupUnload`): group must be
|
||||
* `StateInOrbit`; ship class must have a non-zero cargo block; group
|
||||
* must currently carry cargo. Colonists (`COL`) cannot be unloaded
|
||||
* over a foreign planet — the inspector disables the action with a
|
||||
* tooltip in that case. The cargo type is implicit (whatever the
|
||||
* group is carrying); only `quantity` is sent on the wire.
|
||||
*/
|
||||
export interface UnloadShipGroupCommand {
|
||||
readonly kind: "unloadShipGroup";
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
readonly quantity: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpgradeShipGroupCommand schedules a tech upgrade for a player-
|
||||
* owned group at its current orbit. Engine rules
|
||||
* (`controller/ship_group_upgrade.go.shipGroupUpgrade`): group must
|
||||
* be `StateInOrbit`; the planet must be owned by the player or
|
||||
* unowned; for per-block techs the requested `level` must be in
|
||||
* `(group.tech, race.tech]`; for `tech === "ALL"` the `level` must
|
||||
* be 0 (the engine fans the upgrade out to every block whose mass is
|
||||
* non-zero). The inspector renders a live cost preview through
|
||||
* `core.blockUpgradeCost` to make the production cost visible before
|
||||
* the player commits.
|
||||
*/
|
||||
export interface UpgradeShipGroupCommand {
|
||||
readonly kind: "upgradeShipGroup";
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
readonly tech: ShipGroupUpgradeTech;
|
||||
readonly level: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* DismantleShipGroupCommand deconstructs a player-owned group at its
|
||||
* current orbit, returning the empty mass to the planet's materials
|
||||
* stockpile. Engine rules (`controller/ship_group.go.shipGroupDismantle`):
|
||||
* group must be `StateInOrbit`; over a foreign planet, colonists
|
||||
* (`COL`) on board are *lost* — the inspector surfaces an explicit
|
||||
* two-step confirmation in that case before adding the command to
|
||||
* the draft.
|
||||
*/
|
||||
export interface DismantleShipGroupCommand {
|
||||
readonly kind: "dismantleShipGroup";
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TransferShipGroupCommand hands a player-owned group to another
|
||||
* race. Engine rules (`controller/ship_group.go.shipGroupTransfer`):
|
||||
* acceptor must be a different, non-extinct race; group must not
|
||||
* already be in `StateTransfer`. The inspector restricts the
|
||||
* acceptor picker to `GameReport.otherRaces` (non-extinct ≠ self),
|
||||
* so a draft entry always names a real race.
|
||||
*/
|
||||
export interface TransferShipGroupCommand {
|
||||
readonly kind: "transferShipGroup";
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
readonly acceptor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* JoinFleetShipGroupCommand attaches a player-owned group to a fleet
|
||||
* (creating it on the fly if no fleet by that name exists). Engine
|
||||
* rules (`controller/fleet.go.ShipGroupJoinFleet`): group must be
|
||||
* `StateInOrbit`; the target fleet, when it already exists, must
|
||||
* sit in the same orbit as the group; `name` must pass
|
||||
* `validateEntityName`. Because the engine handles the whole-group
|
||||
* attach atomically (no per-ship counter), this command does not
|
||||
* support implicit-split — the inspector exposes Split as a
|
||||
* separate explicit action when partial detachment is desired.
|
||||
*/
|
||||
export interface JoinFleetShipGroupCommand {
|
||||
readonly kind: "joinFleetShipGroup";
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* OrderCommand is the discriminated union of every command shape the
|
||||
* local order draft can hold. The `kind` field is the discriminator;
|
||||
@@ -179,7 +382,15 @@ export type OrderCommand =
|
||||
| SetCargoRouteCommand
|
||||
| RemoveCargoRouteCommand
|
||||
| CreateShipClassCommand
|
||||
| RemoveShipClassCommand;
|
||||
| RemoveShipClassCommand
|
||||
| BreakShipGroupCommand
|
||||
| SendShipGroupCommand
|
||||
| LoadShipGroupCommand
|
||||
| UnloadShipGroupCommand
|
||||
| UpgradeShipGroupCommand
|
||||
| DismantleShipGroupCommand
|
||||
| TransferShipGroupCommand
|
||||
| JoinFleetShipGroupCommand;
|
||||
|
||||
/**
|
||||
* PRODUCTION_TYPE_VALUES is the canonical tuple of `ProductionType`
|
||||
|
||||
@@ -33,8 +33,18 @@ import {
|
||||
CommandPlanetRouteSet,
|
||||
CommandShipClassCreate,
|
||||
CommandShipClassRemove,
|
||||
CommandShipGroupBreak,
|
||||
CommandShipGroupDismantle,
|
||||
CommandShipGroupJoinFleet,
|
||||
CommandShipGroupLoad,
|
||||
CommandShipGroupSend,
|
||||
CommandShipGroupTransfer,
|
||||
CommandShipGroupUnload,
|
||||
CommandShipGroupUpgrade,
|
||||
PlanetProduction,
|
||||
PlanetRouteLoadType,
|
||||
ShipGroupCargo,
|
||||
ShipGroupUpgradeTech,
|
||||
UserGamesOrder,
|
||||
UserGamesOrderResponse,
|
||||
} from "../proto/galaxy/fbs/order";
|
||||
@@ -42,6 +52,8 @@ import type {
|
||||
CargoLoadType,
|
||||
OrderCommand,
|
||||
ProductionType,
|
||||
ShipGroupCargo as ShipGroupCargoLiteral,
|
||||
ShipGroupUpgradeTech as ShipGroupUpgradeTechLiteral,
|
||||
} from "./order-types";
|
||||
|
||||
const MESSAGE_TYPE = "user.games.order";
|
||||
@@ -222,6 +234,109 @@ function encodeCommandPayload(
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "breakShipGroup": {
|
||||
const idOffset = builder.createString(cmd.groupId);
|
||||
const newIdOffset = builder.createString(cmd.newGroupId);
|
||||
CommandShipGroupBreak.startCommandShipGroupBreak(builder);
|
||||
CommandShipGroupBreak.addId(builder, idOffset);
|
||||
CommandShipGroupBreak.addNewId(builder, newIdOffset);
|
||||
CommandShipGroupBreak.addQuantity(builder, BigInt(cmd.quantity));
|
||||
const offset = CommandShipGroupBreak.endCommandShipGroupBreak(builder);
|
||||
return {
|
||||
payloadType: CommandPayload.CommandShipGroupBreak,
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "sendShipGroup": {
|
||||
const idOffset = builder.createString(cmd.groupId);
|
||||
const offset = CommandShipGroupSend.createCommandShipGroupSend(
|
||||
builder,
|
||||
idOffset,
|
||||
BigInt(cmd.destinationPlanetNumber),
|
||||
);
|
||||
return {
|
||||
payloadType: CommandPayload.CommandShipGroupSend,
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "loadShipGroup": {
|
||||
const idOffset = builder.createString(cmd.groupId);
|
||||
CommandShipGroupLoad.startCommandShipGroupLoad(builder);
|
||||
CommandShipGroupLoad.addId(builder, idOffset);
|
||||
CommandShipGroupLoad.addCargo(builder, shipGroupCargoToFBS(cmd.cargo));
|
||||
CommandShipGroupLoad.addQuantity(builder, cmd.quantity);
|
||||
const offset = CommandShipGroupLoad.endCommandShipGroupLoad(builder);
|
||||
return {
|
||||
payloadType: CommandPayload.CommandShipGroupLoad,
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "unloadShipGroup": {
|
||||
const idOffset = builder.createString(cmd.groupId);
|
||||
const offset = CommandShipGroupUnload.createCommandShipGroupUnload(
|
||||
builder,
|
||||
idOffset,
|
||||
cmd.quantity,
|
||||
);
|
||||
return {
|
||||
payloadType: CommandPayload.CommandShipGroupUnload,
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "upgradeShipGroup": {
|
||||
const idOffset = builder.createString(cmd.groupId);
|
||||
CommandShipGroupUpgrade.startCommandShipGroupUpgrade(builder);
|
||||
CommandShipGroupUpgrade.addId(builder, idOffset);
|
||||
CommandShipGroupUpgrade.addTech(
|
||||
builder,
|
||||
shipGroupUpgradeTechToFBS(cmd.tech),
|
||||
);
|
||||
CommandShipGroupUpgrade.addLevel(builder, cmd.level);
|
||||
const offset = CommandShipGroupUpgrade.endCommandShipGroupUpgrade(builder);
|
||||
return {
|
||||
payloadType: CommandPayload.CommandShipGroupUpgrade,
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "dismantleShipGroup": {
|
||||
const idOffset = builder.createString(cmd.groupId);
|
||||
const offset =
|
||||
CommandShipGroupDismantle.createCommandShipGroupDismantle(
|
||||
builder,
|
||||
idOffset,
|
||||
);
|
||||
return {
|
||||
payloadType: CommandPayload.CommandShipGroupDismantle,
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "transferShipGroup": {
|
||||
const idOffset = builder.createString(cmd.groupId);
|
||||
const acceptorOffset = builder.createString(cmd.acceptor);
|
||||
const offset = CommandShipGroupTransfer.createCommandShipGroupTransfer(
|
||||
builder,
|
||||
idOffset,
|
||||
acceptorOffset,
|
||||
);
|
||||
return {
|
||||
payloadType: CommandPayload.CommandShipGroupTransfer,
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "joinFleetShipGroup": {
|
||||
const idOffset = builder.createString(cmd.groupId);
|
||||
const nameOffset = builder.createString(cmd.name);
|
||||
const offset =
|
||||
CommandShipGroupJoinFleet.createCommandShipGroupJoinFleet(
|
||||
builder,
|
||||
idOffset,
|
||||
nameOffset,
|
||||
);
|
||||
return {
|
||||
payloadType: CommandPayload.CommandShipGroupJoinFleet,
|
||||
payloadOffset: offset,
|
||||
};
|
||||
}
|
||||
case "placeholder":
|
||||
throw new SubmitError(
|
||||
"invalid_request",
|
||||
@@ -277,6 +392,49 @@ export function cargoLoadTypeToFBS(value: CargoLoadType): PlanetRouteLoadType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shipGroupCargoToFBS converts the wire-stable `ShipGroupCargo`
|
||||
* literal to the FlatBuffers enum value. Mirrors the engine
|
||||
* `ShipGroupCargo` enum (`pkg/schema/fbs/order.fbs`). The FBS enum
|
||||
* carries an `UNKNOWN` zero value as the default; the encoder always
|
||||
* emits one of the three real values.
|
||||
*/
|
||||
export function shipGroupCargoToFBS(
|
||||
value: ShipGroupCargoLiteral,
|
||||
): ShipGroupCargo {
|
||||
switch (value) {
|
||||
case "COL":
|
||||
return ShipGroupCargo.COL;
|
||||
case "CAP":
|
||||
return ShipGroupCargo.CAP;
|
||||
case "MAT":
|
||||
return ShipGroupCargo.MAT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shipGroupUpgradeTechToFBS converts the wire-stable
|
||||
* `ShipGroupUpgradeTech` literal to the FlatBuffers enum value.
|
||||
* Mirrors the engine `ShipGroupUpgradeTech` enum
|
||||
* (`pkg/schema/fbs/order.fbs`).
|
||||
*/
|
||||
export function shipGroupUpgradeTechToFBS(
|
||||
value: ShipGroupUpgradeTechLiteral,
|
||||
): ShipGroupUpgradeTech {
|
||||
switch (value) {
|
||||
case "ALL":
|
||||
return ShipGroupUpgradeTech.ALL;
|
||||
case "DRIVE":
|
||||
return ShipGroupUpgradeTech.DRIVE;
|
||||
case "WEAPONS":
|
||||
return ShipGroupUpgradeTech.WEAPONS;
|
||||
case "SHIELDS":
|
||||
return ShipGroupUpgradeTech.SHIELDS;
|
||||
case "CARGO":
|
||||
return ShipGroupUpgradeTech.CARGO;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeOrderResponse(
|
||||
payload: Uint8Array,
|
||||
commands: OrderCommand[],
|
||||
|
||||
Reference in New Issue
Block a user