import { AllPlayersStats, ClientID, Winner } from "../Schemas"; import { EmojiMessage, GameUpdates, Gold, MessageType, NameViewData, PlayerID, PlayerType, Team, Tick, TrainType, TransportShipState, UnitType, WarshipState, } from "./Game"; import { TileRef } from "./GameMap"; export interface GameUpdateViewData { tick: number; updates: GameUpdates; /** * Packed tile updates as `[tileRef, state]` uint32 pairs. * * `tileRef` is a `TileRef` (fits in uint32), and `state` is the packed per-tile * state (`uint16`) stored in a `uint32` lane. */ packedTileUpdates: Uint32Array; /** * Optional packed motion plan records. * * When present, this buffer is expected to be transferred worker -> main * (similar to `packedTileUpdates`) to avoid structured-clone copies. */ packedMotionPlans?: Uint32Array; playerNameViewData: Record; tickExecutionDuration?: number; pendingTurns?: number; } export interface ErrorUpdate { errMsg: string; stack?: string; } export enum GameUpdateType { // Tile updates are delivered via `packedTileUpdates` on the outer GameUpdateViewData. Tile, Unit, Player, DisplayEvent, DisplayChatEvent, AllianceRequest, AllianceRequestReply, BrokeAlliance, AllianceExpired, AllianceExtension, TargetPlayer, Emoji, Win, Hash, UnitIncoming, BonusEvent, RailroadDestructionEvent, RailroadConstructionEvent, RailroadSnapEvent, ConquestEvent, EmbargoEvent, SpawnPhaseEnd, GamePaused, DonateEvent, } export type GameUpdate = | UnitUpdate | PlayerUpdate | AllianceRequestUpdate | AllianceRequestReplyUpdate | BrokeAllianceUpdate | AllianceExpiredUpdate | DisplayMessageUpdate | DisplayChatMessageUpdate | TargetPlayerUpdate | EmojiUpdate | WinUpdate | HashUpdate | UnitIncomingUpdate | AllianceExtensionUpdate | BonusEventUpdate | RailroadConstructionUpdate | RailroadDestructionUpdate | RailroadSnapUpdate | ConquestUpdate | EmbargoUpdate | SpawnPhaseEndUpdate | GamePausedUpdate | DonateEventUpdate; export interface BonusEventUpdate { type: GameUpdateType.BonusEvent; player: PlayerID; tile: TileRef; gold: number; troops: number; } export interface RailroadConstructionUpdate { type: GameUpdateType.RailroadConstructionEvent; id: number; tiles: TileRef[]; } export interface RailroadDestructionUpdate { type: GameUpdateType.RailroadDestructionEvent; id: number; } export interface RailroadSnapUpdate { type: GameUpdateType.RailroadSnapEvent; originalId: number; newId1: number; newId2: number; tiles1: TileRef[]; tiles2: TileRef[]; } export interface ConquestUpdate { type: GameUpdateType.ConquestEvent; conquerorId: PlayerID; conqueredId: PlayerID; gold: Gold; } export interface DonateEventUpdate { type: GameUpdateType.DonateEvent; donationType: "troops" | "gold"; senderId: PlayerID; recipientId: PlayerID; amount: bigint; } export interface UnitUpdate { type: GameUpdateType.Unit; unitType: UnitType; troops: number; id: number; ownerID: number; lastOwnerID?: number; // TODO: make these tilerefs pos: TileRef; lastPos: TileRef; isActive: boolean; reachedTarget: boolean; warshipState?: WarshipState; transportShipState?: TransportShipState; targetable: boolean; markedForDeletion: number | false; targetUnitId?: number; // Only for trade ships targetTile?: TileRef; // Only for nukes health?: number; underConstruction?: boolean; missileTimerQueue: number[]; level: number; hasTrainStation: boolean; trainType?: TrainType; // Only for trains loaded?: boolean; // Only for trains } export interface AttackUpdate { attackerID: number; targetID: number; troops: number; id: string; retreating: boolean; } /** * Player snapshot delivered worker -> main thread. * * Only `type` and `id` are guaranteed. Every other field is omitted when its * value matches the previous emission for the same player. The first emission * for a player always includes all fields; consumers must handle subsequent * partial updates by merging into local state, not overwriting. * * When adding a field here, also wire it into diffPlayerUpdate() and * applyStateUpdate() in GameUpdateUtils.ts — otherwise it is only ever sent on * the first emission and later changes are silently dropped. */ export interface PlayerUpdate { type: GameUpdateType.Player; id: PlayerID; nameViewData?: NameViewData; clientID?: ClientID | null; name?: string; displayName?: string; team?: Team; smallID?: number; playerType?: PlayerType; isAlive?: boolean; isDisconnected?: boolean; tilesOwned?: number; gold?: Gold; troops?: number; allies?: number[]; embargoes?: Set; isTraitor?: boolean; traitorRemainingTicks?: number; targets?: number[]; outgoingEmojis?: EmojiMessage[]; outgoingAttacks?: AttackUpdate[]; incomingAttacks?: AttackUpdate[]; outgoingAllianceRequests?: PlayerID[]; alliances?: AllianceView[]; hasSpawned?: boolean; spawnTile?: TileRef; betrayals?: number; lastDeleteUnitTick?: Tick; isLobbyCreator?: boolean; } export interface AllianceView { id: number; other: PlayerID; createdAt: Tick; expiresAt: Tick; hasExtensionRequest: boolean; } export interface AllianceRequestUpdate { type: GameUpdateType.AllianceRequest; requestorID: number; recipientID: number; createdAt: Tick; } export interface AllianceRequestReplyUpdate { type: GameUpdateType.AllianceRequestReply; request: AllianceRequestUpdate; accepted: boolean; } export interface BrokeAllianceUpdate { type: GameUpdateType.BrokeAlliance; traitorID: number; betrayedID: number; allianceID: number; } export interface AllianceExpiredUpdate { type: GameUpdateType.AllianceExpired; player1ID: number; player2ID: number; } export interface AllianceExtensionUpdate { type: GameUpdateType.AllianceExtension; playerID: number; allianceID: number; } export interface TargetPlayerUpdate { type: GameUpdateType.TargetPlayer; playerID: number; targetID: number; } export interface EmojiUpdate { type: GameUpdateType.Emoji; emoji: EmojiMessage; } export interface DisplayMessageUpdate { type: GameUpdateType.DisplayEvent; message: string; messageType: MessageType; goldAmount?: bigint; playerID: number | null; params?: Record; unitID?: number; focusPlayerID?: number; } export type DisplayChatMessageUpdate = { type: GameUpdateType.DisplayChatEvent; key: string; category: string; target: string | undefined; playerID: number | null; isFrom: boolean; recipient: string; }; export interface WinUpdate { type: GameUpdateType.Win; allPlayersStats: AllPlayersStats; winner: Winner; } export interface HashUpdate { type: GameUpdateType.Hash; tick: Tick; hash: number; } export interface UnitIncomingUpdate { type: GameUpdateType.UnitIncoming; unitID: number; message: string; messageType: MessageType; playerID: number; } export interface EmbargoUpdate { type: GameUpdateType.EmbargoEvent; event: "start" | "stop"; playerID: number; embargoedID: number; } export interface SpawnPhaseEndUpdate { type: GameUpdateType.SpawnPhaseEnd; startTick: Tick; } export interface GamePausedUpdate { type: GameUpdateType.GamePaused; paused: boolean; }