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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user