diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index f1b233987..b9530fbc1 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -187,7 +187,9 @@ export class ClientGameRunner { } private saveGame(update: WinUpdate) { - if (this.myPlayer === null) throw new Error("Not initialized"); + if (this.myPlayer === null) { + return; + } const players: PlayerRecord[] = [ { playerID: this.myPlayer.id(), diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts index 8a99b6b38..c287530a8 100644 --- a/src/client/graphics/layers/WinModal.ts +++ b/src/client/graphics/layers/WinModal.ts @@ -207,10 +207,13 @@ export class WinModal extends LitElement implements Layer { new SendWinnerEvent(winnerClient, wu.allPlayersStats, "player"), ); } - if (winner === this.game.myPlayer()) { + if ( + winnerClient !== null && + winnerClient === this.game.myPlayer()?.clientID() + ) { this._title = translateText("win_modal.you_won"); } else { - this._title = translateText("win_modal.you_won", { + this._title = translateText("win_modal.other_won", { player: winner.name(), }); } diff --git a/src/core/game/StatsImpl.ts b/src/core/game/StatsImpl.ts index e81f1d74c..2546fa2cb 100644 --- a/src/core/game/StatsImpl.ts +++ b/src/core/game/StatsImpl.ts @@ -56,7 +56,7 @@ export class StatsImpl implements Stats { const p = this._makePlayerStats(player); if (p === undefined) return; if (p.attacks === undefined) p.attacks = [0]; - while (p.attacks.length < index) p.attacks.push(0); + while (p.attacks.length <= index) p.attacks.push(0); p.attacks[index] += value; } @@ -80,7 +80,7 @@ export class StatsImpl implements Stats { if (p === undefined) return; if (p.boats === undefined) p.boats = { [type]: [0] }; if (p.boats[type] === undefined) p.boats[type] = [0]; - while (p.boats[type].length < index) p.boats[type].push(0); + while (p.boats[type].length <= index) p.boats[type].push(0); p.boats[type][index] += value; } @@ -95,7 +95,7 @@ export class StatsImpl implements Stats { if (p === undefined) return; if (p.bombs === undefined) p.bombs = { [type]: [0] }; if (p.bombs[type] === undefined) p.bombs[type] = [0]; - while (p.bombs[type].length < index) p.bombs[type].push(0); + while (p.bombs[type].length <= index) p.bombs[type].push(0); p.bombs[type][index] += value; } @@ -103,7 +103,7 @@ export class StatsImpl implements Stats { const p = this._makePlayerStats(player); if (p === undefined) return; if (p.gold === undefined) p.gold = [0]; - while (p.gold.length < index) p.gold.push(0); + while (p.gold.length <= index) p.gold.push(0); p.gold[index] += value; } diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index d10a6e050..949bd2b68 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -56,6 +56,7 @@ export class GameServer { private _hasPrestarted = false; private kickedClients: Set = new Set(); + private outOfSyncClients: Set = new Set(); constructor( public readonly id: string, @@ -200,7 +201,15 @@ export class GameServer { client.hashes.set(clientMsg.turnNumber, clientMsg.hash); } if (clientMsg.type === "winner") { + if ( + this.outOfSyncClients.has(client.clientID) || + this.kickedClients.has(client.clientID) || + this.winner !== null + ) { + return; + } this.winner = clientMsg; + this.archiveGame(); } } catch (error) { this.log.info( @@ -382,40 +391,16 @@ export class GameServer { } this.log.info(`ending game with ${this.turns.length} turns`); try { - if (this.allClients.size > 0) { - const playerRecords: PlayerRecord[] = Array.from( - this.allClients.values(), - ).map((client) => { - const stats = this.winner?.allPlayersStats[client.clientID]; - if (stats === undefined) { - this.log.warn( - `Unable to find stats for clientID ${client.clientID}`, - ); - } - return { - playerID: client.playerID, - clientID: client.clientID, - username: client.username, - persistentID: client.persistentID, - stats, - } satisfies PlayerRecord; - }); - archive( - createGameRecord( - this.id, - this.gameStartInfo.config, - playerRecords, - this.turns, - this._startTime ?? 0, - Date.now(), - this.winner?.winner ?? null, - this.winner?.winnerType ?? null, - ), - ); - } else { + if (this.allClients.size === 0) { this.log.info("no clients joined, not archiving game", { gameID: this.id, }); + } else if (this.winner !== null) { + this.log.info("game already archived", { + gameID: this.id, + }); + } else { + this.archiveGame(); } } catch (error) { let errorDetails; @@ -549,6 +534,40 @@ export class GameServer { } } + private archiveGame() { + this.log.info("archiving game", { + gameID: this.id, + winner: this.winner?.winner, + }); + const playerRecords: PlayerRecord[] = Array.from( + this.allClients.values(), + ).map((client) => { + const stats = this.winner?.allPlayersStats[client.clientID]; + if (stats === undefined) { + this.log.warn(`Unable to find stats for clientID ${client.clientID}`); + } + return { + playerID: client.playerID, + clientID: client.clientID, + username: client.username, + persistentID: client.persistentID, + stats, + } satisfies PlayerRecord; + }); + archive( + createGameRecord( + this.id, + this.gameStartInfo.config, + playerRecords, + this.turns, + this._startTime ?? 0, + Date.now(), + this.winner?.winner ?? null, + this.winner?.winnerType ?? null, + ), + ); + } + private handleSynchronization() { if (this.activeClients.length <= 1) { return; @@ -586,6 +605,7 @@ export class GameServer { const desyncMsg = JSON.stringify(serverDesync.data); for (const c of outOfSyncClients) { + this.outOfSyncClients.add(c.clientID); if (this.sentDesyncMessageClients.has(c.clientID)) { continue; }