Skip to content

Commit 0dc5224

Browse files
authored
Close private lobby when host leaves 🚪 (#3503)
## Description: When the host of a private lobby disconnects before the game starts, the lobby is now closed: - **Server:** Detects host disconnection via `creatorPersistentID` match, kicks all remaining clients with a `host_left` reason, and marks the game as ended so it's cleaned up by the game manager and can no longer be joined. - **Client:** Participants receive an `alert()` saying "The host has left the lobby." and their JoinLobbyModal is closed automatically via the `leave-lobby` event. - **Phase logic:** `phase()` now returns `Finished` for ended private lobbies that haven't started, preventing the game from lingering in `Lobby` state. ## 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: FloPinguin
1 parent cf0cf14 commit 0dc5224

3 files changed

Lines changed: 28 additions & 1 deletion

File tree

‎resources/lang/en.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,8 @@
863863
},
864864
"kick_reason": {
865865
"duplicate_session": "Kicked from game (you may have been playing on another tab)",
866-
"lobby_creator": "Kicked by lobby creator"
866+
"lobby_creator": "Kicked by lobby creator",
867+
"host_left": "The host has left the lobby."
867868
},
868869
"send_troops_modal": {
869870
"title_with_name": "Send Troops to {name}",

‎src/client/ClientGameRunner.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ export function joinLobby(
172172
composed: true,
173173
}),
174174
);
175+
} else if (message.error === "kick_reason.host_left") {
176+
alert(translateText("kick_reason.host_left"));
177+
document.dispatchEvent(
178+
new CustomEvent("leave-lobby", {
179+
detail: { lobby: lobbyConfig.gameID, cause: "host-left" },
180+
bubbles: true,
181+
composed: true,
182+
}),
183+
);
175184
} else {
176185
showErrorModal(
177186
message.error,

‎src/server/GameServer.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export enum GamePhase {
3535

3636
const KICK_REASON_DUPLICATE_SESSION = "kick_reason.duplicate_session";
3737
const KICK_REASON_LOBBY_CREATOR = "kick_reason.lobby_creator";
38+
const KICK_REASON_HOST_LEFT = "kick_reason.host_left";
3839
const KICK_REASON_TOO_MUCH_DATA = "kick_reason.too_much_data";
3940
const KICK_REASON_INVALID_MESSAGE = "kick_reason.invalid_message";
4041

@@ -542,6 +543,20 @@ export class GameServer {
542543
this.activeClients = this.activeClients.filter(
543544
(c) => c.clientID !== client.clientID,
544545
);
546+
// Close lobby when host leaves before game starts
547+
if (
548+
!this._hasStarted &&
549+
!this.isPublic() &&
550+
client.persistentID === this.creatorPersistentID
551+
) {
552+
this.log.info("Host left, closing lobby", {
553+
gameID: this.id,
554+
});
555+
for (const c of [...this.activeClients]) {
556+
this.kickClient(c.clientID, KICK_REASON_HOST_LEFT);
557+
}
558+
this._hasEnded = true;
559+
}
545560
});
546561
client.ws.on("error", (error: Error) => {
547562
if ((error as any).code === "WS_ERR_UNEXPECTED_RSV_1") {
@@ -847,6 +862,8 @@ export class GameServer {
847862
} else {
848863
return GamePhase.Active;
849864
}
865+
} else if (this._hasEnded) {
866+
return GamePhase.Finished;
850867
} else {
851868
return GamePhase.Lobby;
852869
}

0 commit comments

Comments
 (0)