diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 5cc88c3eb..09271afda 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -194,7 +194,7 @@ export function joinLobby( async function createClientGame( lobbyConfig: LobbyConfig, - clientID: ClientID, + clientID: ClientID | undefined, eventBus: EventBus, transport: Transport, userSettings: UserSettings, @@ -267,7 +267,7 @@ export class ClientGameRunner { constructor( private lobby: LobbyConfig, - private clientID: ClientID, + private clientID: ClientID | undefined, private eventBus: EventBus, private renderer: GameRenderer, private input: InputHandler, @@ -294,7 +294,7 @@ export class ClientGameRunner { } private async saveGame(update: WinUpdate) { - if (this.myPlayer === null) { + if (!this.clientID) { return; } const players: PlayerRecord[] = [ @@ -544,6 +544,7 @@ export class ClientGameRunner { return; } if (this.myPlayer === null) { + if (!this.clientID) return; const myPlayer = this.gameView.playerByClientID(this.clientID); if (myPlayer === null) return; this.myPlayer = myPlayer; @@ -578,6 +579,7 @@ export class ClientGameRunner { const tile = this.gameView.ref(cell.x, cell.y); if (this.myPlayer === null) { + if (!this.clientID) return; const myPlayer = this.gameView.playerByClientID(this.clientID); if (myPlayer === null) return; this.myPlayer = myPlayer; @@ -639,6 +641,7 @@ export class ClientGameRunner { } if (this.myPlayer === null) { + if (!this.clientID) return; const myPlayer = this.gameView.playerByClientID(this.clientID); if (myPlayer === null) return; this.myPlayer = myPlayer; @@ -664,6 +667,7 @@ export class ClientGameRunner { } if (this.myPlayer === null) { + if (!this.clientID) return; const myPlayer = this.gameView.playerByClientID(this.clientID); if (myPlayer === null) return; this.myPlayer = myPlayer; diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts index 90712b868..58c43306c 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -113,7 +113,8 @@ export class LocalServer { gameStartInfo: this.lobbyConfig.gameStartInfo, turns: [], lobbyCreatedAt: this.lobbyConfig.gameStartInfo.lobbyCreatedAt, - myClientID: this.clientID, + // Don't send myClientID for replays — viewer has no player identity. + myClientID: this.lobbyConfig.gameRecord ? undefined : this.clientID, } satisfies ServerStartGameMessage); } @@ -127,7 +128,7 @@ export class LocalServer { gameStartInfo: this.lobbyConfig.gameStartInfo!, turns: this.turns, lobbyCreatedAt: this.lobbyConfig.gameStartInfo!.lobbyCreatedAt, - myClientID: this.clientID, + myClientID: this.lobbyConfig.gameRecord ? undefined : this.clientID, } satisfies ServerStartGameMessage); } if (clientMsg.type === "intent") { diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index 9019a78e5..427a32781 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -33,7 +33,7 @@ import { simpleHash } from "./Util"; export async function createGameRunner( gameStart: GameStartInfo, - clientID: ClientID, + clientID: ClientID | undefined, mapLoader: GameMapLoader, callBack: (gu: GameUpdateViewData | ErrorUpdate) => void, ): Promise { diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index 067e83069..a42738774 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -552,8 +552,9 @@ export const ServerStartGameMessageSchema = z.object({ turns: TurnSchema.array(), gameStartInfo: GameStartInfoSchema, lobbyCreatedAt: z.number(), - // The clientID assigned to this connection by the server - myClientID: ID, + // The clientID assigned to this connection by the server. + // Absent for replays where the viewer has no player identity. + myClientID: ID.optional(), }); export const ServerDesyncSchema = z.object({ diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts index d50aaadb7..9b61629d9 100644 --- a/src/core/execution/ExecutionManager.ts +++ b/src/core/execution/ExecutionManager.ts @@ -36,7 +36,7 @@ export class Executor { constructor( private mg: Game, private gameID: GameID, - private clientID: ClientID, + private clientID: ClientID | undefined, ) { // Add one to avoid id collisions with bots. this.random = new PseudoRandom(simpleHash(gameID) + 1); diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index bdd9f2092..d3e3ad87e 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -657,7 +657,7 @@ export class GameView implements GameMap { public worker: WorkerClient, private _config: Config, private _mapData: TerrainMapData, - private _myClientID: ClientID, + private _myClientID: ClientID | undefined, private _myUsername: string, private _gameID: GameID, private humans: Player[], @@ -785,7 +785,9 @@ export class GameView implements GameMap { } }); - this._myPlayer ??= this.playerByClientID(this._myClientID); + if (this._myClientID) { + this._myPlayer ??= this.playerByClientID(this._myClientID); + } for (const unit of this._units.values()) { unit._wasUpdated = false; @@ -1103,7 +1105,7 @@ export class GameView implements GameMap { ); } - myClientID(): ClientID { + myClientID(): ClientID | undefined { return this._myClientID; } diff --git a/src/core/worker/WorkerClient.ts b/src/core/worker/WorkerClient.ts index 7655f9050..372e4af96 100644 --- a/src/core/worker/WorkerClient.ts +++ b/src/core/worker/WorkerClient.ts @@ -23,7 +23,7 @@ export class WorkerClient { constructor( private gameStartInfo: GameStartInfo, - private clientID: ClientID, + private clientID: ClientID | undefined, ) { this.worker = new Worker(new URL("./Worker.worker.ts", import.meta.url), { type: "module", diff --git a/src/core/worker/WorkerMessages.ts b/src/core/worker/WorkerMessages.ts index b8b04740d..7d75f1882 100644 --- a/src/core/worker/WorkerMessages.ts +++ b/src/core/worker/WorkerMessages.ts @@ -39,7 +39,7 @@ interface BaseWorkerMessage { export interface InitMessage extends BaseWorkerMessage { type: "init"; gameStartInfo: GameStartInfo; - clientID: ClientID; + clientID: ClientID | undefined; } export interface TurnMessage extends BaseWorkerMessage {