JoinLobbyModal for public and private lobbies (#3097)

## Description:

Replaced the src/client/JoinPrivateLobbyModal.ts with a new
src/client/JoinLobbyModal.ts which handles both public + private
lobbies.

<img width="771" height="714" alt="image"
src="https://github.com/user-attachments/assets/7ac55d91-3f0c-4f99-b960-cea9e617538d"
/>

also made a "connecting" to the lobby 
<img width="772" height="708" alt="image"
src="https://github.com/user-attachments/assets/a2812462-c5f4-459a-b63a-49d93bb2a6a2"
/>


It also needed to be updated to address the issue with the modal using
both polling + websockets

## 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
This commit is contained in:
Ryan
2026-02-03 05:02:20 +00:00
committed by GitHub
parent d04f90ec4a
commit 2baaebfef3
14 changed files with 1201 additions and 839 deletions
+52 -3
View File
@@ -17,6 +17,7 @@ import {
PlayerRecord,
ServerDesyncSchema,
ServerErrorMessage,
ServerLobbyInfoMessage,
ServerPrestartMessageSchema,
ServerStartGameMessage,
ServerTurnMessage,
@@ -78,6 +79,8 @@ export class GameServer {
public desyncCount = 0;
private lobbyInfoIntervalId: ReturnType<typeof setInterval> | null = null;
constructor(
public readonly id: string,
readonly log_: Logger,
@@ -232,6 +235,7 @@ export class GameServer {
this.markClientDisconnected(client.clientID, false);
this.allClients.set(client.clientID, client);
this.addListeners(client);
this.startLobbyInfoBroadcast();
// In case a client joined the game late and missed the start message.
if (this._hasStarted) {
@@ -280,6 +284,7 @@ export class GameServer {
client.ws = ws;
this.addListeners(client);
this.startLobbyInfoBroadcast();
if (this._hasStarted) {
this.sendStartGameMsg(client.ws, msg.lastTurn);
@@ -542,6 +547,47 @@ export class GameServer {
});
}
private startLobbyInfoBroadcast() {
if (this._hasStarted || this._hasEnded) {
return;
}
if (this.lobbyInfoIntervalId !== null) {
return;
}
this.broadcastLobbyInfo();
this.lobbyInfoIntervalId = setInterval(() => {
if (
this._hasStarted ||
this._hasEnded ||
this.activeClients.length === 0
) {
this.stopLobbyInfoBroadcast();
return;
}
this.broadcastLobbyInfo();
}, 1000);
}
private stopLobbyInfoBroadcast() {
if (this.lobbyInfoIntervalId === null) {
return;
}
clearInterval(this.lobbyInfoIntervalId);
this.lobbyInfoIntervalId = null;
}
private broadcastLobbyInfo() {
const msg = JSON.stringify({
type: "lobby_info",
lobby: this.gameInfo(),
} satisfies ServerLobbyInfoMessage);
this.activeClients.forEach((c) => {
if (c.ws.readyState === WebSocket.OPEN) {
c.ws.send(msg);
}
});
}
public start() {
if (this._hasStarted || this._hasEnded) {
return;
@@ -771,12 +817,15 @@ export class GameServer {
clientID: c.clientID,
})),
gameConfig: this.gameConfig,
msUntilStart: this.isPublic()
? this.createdAt + this.config.gameCreationRate()
: undefined,
msUntilStart: this.isPublic() ? this.getMsUntilStart() : undefined,
};
}
private getMsUntilStart(): number {
const startTime = this.createdAt + this.config.gameCreationRate();
return Math.max(0, startTime - Date.now());
}
public isPublic(): boolean {
return this.gameConfig.gameType === GameType.Public;
}
+1 -1
View File
@@ -267,7 +267,7 @@ async function fetchLobbies(): Promise<number> {
gameID: gi.gameID,
numClients: gi?.clients?.length ?? 0,
gameConfig: gi.gameConfig,
msUntilStart: (gi.msUntilStart ?? Date.now()) - Date.now(),
msUntilStart: gi.msUntilStart,
} as GameInfo;
});