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();