From 34251c067322e7e72cfc27d865ff4a8865c73275 Mon Sep 17 00:00:00 2001 From: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Date: Tue, 11 Nov 2025 03:53:25 +0000 Subject: [PATCH] lobby fill time added to stats (#2382) ## Description: Finishes & closes #1969 and closes #1806. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n --------- Co-authored-by: Evan --- src/client/ClientGameRunner.ts | 1 + src/client/LocalServer.ts | 1 + src/client/SinglePlayerModal.ts | 1 + src/core/Schemas.ts | 3 +++ src/core/Util.ts | 12 ++++++++++++ src/core/game/Stats.ts | 3 +++ src/core/game/StatsImpl.ts | 2 ++ src/server/GameServer.ts | 3 +++ 8 files changed, 26 insertions(+) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index d4b8f8a5a..575977f9a 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -245,6 +245,7 @@ export class ClientGameRunner { startTime(), Date.now(), update.winner, + this.lobby.gameStartInfo.lobbyCreatedAt, ); endGame(record); } diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts index e250be225..c21114911 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -84,6 +84,7 @@ export class LocalServer { type: "start", gameStartInfo: this.lobbyConfig.gameStartInfo, turns: [], + lobbyCreatedAt: this.lobbyConfig.gameStartInfo.lobbyCreatedAt, } satisfies ServerStartGameMessage); } diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts index ddf61f365..9c51bf905 100644 --- a/src/client/SinglePlayerModal.ts +++ b/src/client/SinglePlayerModal.ts @@ -597,6 +597,7 @@ export class SinglePlayerModal extends LitElement { disableNPCs: this.disableNPCs, }), }, + lobbyCreatedAt: Date.now(), // ms; server should be authoritative in MP }, } satisfies JoinLobbyEvent, bubbles: true, diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index caac254d8..acedd062a 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -428,6 +428,7 @@ export const PlayerSchema = z.object({ export const GameStartInfoSchema = z.object({ gameID: ID, + lobbyCreatedAt: z.number(), config: GameConfigSchema, players: PlayerSchema.array(), }); @@ -464,6 +465,7 @@ export const ServerStartGameMessageSchema = z.object({ // Turns the client missed if they are late to the game. turns: TurnSchema.array(), gameStartInfo: GameStartInfoSchema, + lobbyCreatedAt: z.number(), }); export const ServerDesyncSchema = z.object({ @@ -560,6 +562,7 @@ export const GameEndInfoSchema = GameStartInfoSchema.extend({ duration: z.number().nonnegative(), num_turns: z.number(), winner: WinnerSchema, + lobbyFillTime: z.number().nonnegative(), }); export type GameEndInfo = z.infer; diff --git a/src/core/Util.ts b/src/core/Util.ts index 159370de5..d060b7c67 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -194,15 +194,27 @@ export function createPartialGameRecord( start: number, end: number, winner: Winner, + // lobby creation time (ms). Defaults to start time for singleplayer. + lobbyCreatedAt?: number, ): PartialGameRecord { const duration = Math.floor((end - start) / 1000); const num_turns = allTurns.length; const turns = allTurns.filter( (t) => t.intents.length !== 0 || t.hash !== undefined, ); + + // Use start time as lobby creation time for singleplayer + const actualLobbyCreatedAt = lobbyCreatedAt ?? start; + const lobbyFillTime = Math.max( + 0, + start - Math.min(actualLobbyCreatedAt, start), + ); + const record: PartialGameRecord = { info: { gameID, + lobbyCreatedAt: actualLobbyCreatedAt, + lobbyFillTime, config, players, start, diff --git a/src/core/game/Stats.ts b/src/core/game/Stats.ts index 06c41af9c..39400087d 100644 --- a/src/core/game/Stats.ts +++ b/src/core/game/Stats.ts @@ -23,6 +23,9 @@ export interface Stats { // Player betrays another player betray(player: Player): void; + // Time between lobby creation and game start (ms) + lobbyFillTime(fillTimeMs: number): void; + // Player sends a trade ship to target boatSendTrade(player: Player, target: Player): void; diff --git a/src/core/game/StatsImpl.ts b/src/core/game/StatsImpl.ts index f6a574848..aa48b1b7f 100644 --- a/src/core/game/StatsImpl.ts +++ b/src/core/game/StatsImpl.ts @@ -264,4 +264,6 @@ export class StatsImpl implements Stats { playerKilled(player: Player, tick: number): void { this._addPlayerKilled(player, tick); } + + lobbyFillTime(fillTimeMs: number): void {} } diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 078c482b4..927b8addc 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -401,6 +401,7 @@ export class GameServer { const result = GameStartInfoSchema.safeParse({ gameID: this.id, + lobbyCreatedAt: this.createdAt, config: this.gameConfig, players: this.activeClients.map((c) => ({ username: c.username, @@ -439,6 +440,7 @@ export class GameServer { type: "start", turns: this.turns.slice(lastTurn), gameStartInfo: this.gameStartInfo, + lobbyCreatedAt: this.createdAt, } satisfies ServerStartGameMessage), ); } catch (error) { @@ -706,6 +708,7 @@ export class GameServer { this._startTime ?? 0, Date.now(), this.winner?.winner, + this.createdAt, ), ), );