From 8f982ce1232bb559989f518ba23ca07250f9be08 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 23 May 2026 20:52:13 +0100 Subject: [PATCH] Extend friend grouping to the lobby team preview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The preview was calling assignTeams without friend data, so the team layout shown in the lobby could differ from the layout the game actually started with. Wire friends through ClientInfo so the preview matches. Extract the publicId→clientID translation used by both start() and gameInfo() into buildFriendsLookup() to remove the duplicate. --- src/client/components/LobbyPlayerView.ts | 1 + src/core/Schemas.ts | 2 ++ src/server/GameServer.ts | 45 +++++++++++++++--------- src/server/ServerEnv.ts | 2 +- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/client/components/LobbyPlayerView.ts b/src/client/components/LobbyPlayerView.ts index 5dcd3eef0..231fef396 100644 --- a/src/client/components/LobbyPlayerView.ts +++ b/src/client/components/LobbyPlayerView.ts @@ -340,6 +340,7 @@ export class LobbyTeamView extends LitElement { c.clientID, false, c.clanTag, + c.friends ?? [], ), ); const assignment = assignTeamsLobbyPreview( diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index 46610373c..f14a39c76 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -156,6 +156,7 @@ const ClientInfoSchema = z.object({ clientID: z.string(), username: UsernameSchema, clanTag: ClanTagSchema, + friends: z.array(z.string()).optional(), }); export const GameInfoSchema = z.object({ @@ -192,6 +193,7 @@ export interface ClientInfo { clientID: ClientID; username: string; clanTag: string | null; + friends?: ClientID[]; } export enum LogSeverity { Debug = "DEBUG", diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 59673e2fd..3ae18efab 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -731,29 +731,21 @@ export class GameServer { // if no client connects/pings. this.lastPingUpdate = Date.now(); - const publicIdToClientID = new Map(); - for (const c of this.activeClients) { - if (c.publicId) publicIdToClientID.set(c.publicId, c.clientID); - } + const friendsFor = this.buildFriendsLookup(); const result = GameStartInfoSchema.safeParse({ gameID: this.id, lobbyCreatedAt: this.createdAt, visibleAt: this.visibleAt, config: this.gameConfig, - players: this.activeClients.map((c) => { - const friendClientIDs = c.friends - .map((pid) => publicIdToClientID.get(pid)) - .filter((id): id is ClientID => id !== undefined); - return { - username: c.username, - clanTag: c.clanTag ?? null, - clientID: c.clientID, - cosmetics: c.cosmetics, - isLobbyCreator: this.lobbyCreatorID === c.clientID, - friends: friendClientIDs.length > 0 ? friendClientIDs : undefined, - }; - }), + players: this.activeClients.map((c) => ({ + username: c.username, + clanTag: c.clanTag ?? null, + clientID: c.clientID, + cosmetics: c.cosmetics, + isLobbyCreator: this.lobbyCreatorID === c.clientID, + friends: friendsFor(c), + })), }); if (!result.success) { const error = z.prettifyError(result.error); @@ -966,12 +958,14 @@ export class GameServer { } public gameInfo(): GameInfo { + const friendsFor = this.buildFriendsLookup(); return { gameID: this.id, clients: this.activeClients.map((c) => ({ username: c.username, clanTag: c.clanTag ?? null, clientID: c.clientID, + friends: friendsFor(c), })), lobbyCreatorClientID: this.lobbyCreatorID, gameConfig: this.gameConfig, @@ -981,6 +975,23 @@ export class GameServer { }; } + // Maps each active client's publicId-based friends list to in-game + // clientIDs, dropping friends not present in this game. Returns undefined + // when no friends are present so the field can be omitted from the wire + // payload. + private buildFriendsLookup(): (client: Client) => ClientID[] | undefined { + const publicIdToClientID = new Map(); + for (const c of this.activeClients) { + if (c.publicId) publicIdToClientID.set(c.publicId, c.clientID); + } + return (client: Client) => { + const friendClientIDs = client.friends + .map((pid) => publicIdToClientID.get(pid)) + .filter((id): id is ClientID => id !== undefined); + return friendClientIDs.length > 0 ? friendClientIDs : undefined; + }; + } + public isPublic(): boolean { return this.gameConfig.gameType === GameType.Public; } diff --git a/src/server/ServerEnv.ts b/src/server/ServerEnv.ts index 012d360af..365f9a325 100644 --- a/src/server/ServerEnv.ts +++ b/src/server/ServerEnv.ts @@ -111,7 +111,7 @@ export class ServerEnv { return 100; } static gameCreationRate(): number { - return ServerEnv.gameEnv === GameEnv.Dev ? 5 * 1000 : 2 * 60 * 1000; + return ServerEnv.gameEnv === GameEnv.Dev ? 50 * 1000 : 2 * 60 * 1000; } static workerIndex(gameID: GameID): number { return simpleHash(gameID) % ServerEnv.numWorkers();