Extend friend grouping to the lobby team preview

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.
This commit is contained in:
evanpelle
2026-05-23 20:52:13 +01:00
parent db501c68d2
commit 8f982ce123
4 changed files with 32 additions and 18 deletions
+1
View File
@@ -340,6 +340,7 @@ export class LobbyTeamView extends LitElement {
c.clientID,
false,
c.clanTag,
c.friends ?? [],
),
);
const assignment = assignTeamsLobbyPreview(
+2
View File
@@ -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",
+28 -17
View File
@@ -731,29 +731,21 @@ export class GameServer {
// if no client connects/pings.
this.lastPingUpdate = Date.now();
const publicIdToClientID = new Map<string, ClientID>();
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<string, ClientID>();
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;
}
+1 -1
View File
@@ -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();