Add exit menu in multiplayer mode

This commit is contained in:
Mike Müller 2026-03-08 21:27:16 +01:00
parent cfeda33d33
commit 9f278a6b72
2 changed files with 66 additions and 13 deletions

View File

@ -290,13 +290,34 @@ function startMatch() {
});
}
function removePlayer(clientId) {
function removePlayer(clientId, voluntaryLeave = false) {
lobby.players = lobby.players.filter((p) => p.id !== clientId);
if (lobby.phase === "game") {
const inRoster = lobby.roster.some((p) => p.id === clientId);
if (inRoster) {
endMatch("A player disconnected. Back to lobby.");
const rosterIndex = lobby.roster.findIndex((p) => p.id === clientId);
if (rosterIndex !== -1) {
const wasHost = clientId === lobby.hostId;
lobby.roster.splice(rosterIndex, 1);
if (wasHost) {
if (voluntaryLeave && lobby.roster.length > 0) {
lobby.hostId = lobby.roster[0].id;
broadcast({ type: "game_player_left", playerId: clientId });
broadcastLobbyState("Host left the round. New host assigned.");
return;
}
endMatch(voluntaryLeave ? "Host left the round. Back to lobby." : "Host disconnected. Back to lobby.");
return;
}
if (!voluntaryLeave) {
endMatch("A player disconnected. Back to lobby.");
return;
}
broadcast({ type: "game_player_left", playerId: clientId });
broadcastLobbyState("A player left the round.");
return;
}
}
@ -359,7 +380,7 @@ function handleMessage(client, msg) {
}
if (msg.type === "lobby_leave") {
removePlayer(client.id);
removePlayer(client.id, true);
return;
}
@ -515,7 +536,7 @@ wss.on("connection", (ws) => {
ws.on("close", () => {
clientsBySocket.delete(ws);
clientsById.delete(client.id);
removePlayer(client.id);
removePlayer(client.id, false);
});
ws.on("error", () => {

View File

@ -408,7 +408,7 @@ function getMenuItems() {
return [
{ id: "nextRound", label: "Start Next Round" },
{ id: "music", label: `Music: ${audio && audio.isMusicEnabled() ? "On" : "Off"}` },
{ id: "leaveMatch", label: "Leave Match" },
{ id: "exitLobby", label: "Exit To Lobby" },
];
}
return [
@ -419,7 +419,7 @@ function getMenuItems() {
}
return [
{ id: "music", label: `Music: ${audio && audio.isMusicEnabled() ? "On" : "Off"}` },
{ id: "leaveMatch", label: "Leave Match" },
{ id: "exitLobby", label: "Exit To Lobby" },
{ id: "close", label: "Close" },
];
}
@ -513,6 +513,12 @@ function activateMenuSelection() {
return;
}
if (item.id === "exitLobby") {
closeMenu();
leaveMultiplayerToLobby();
return;
}
if (item.id === "leaveMatch") {
closeMenu();
leaveMultiplayerToMainMenu();
@ -792,6 +798,27 @@ function handleSocketMessage(raw) {
return;
}
if (msg.type === "game_player_left") {
if (typeof msg.playerId !== "string") {
return;
}
network.activeRoster = network.activeRoster.filter((player) => player.id !== msg.playerId);
network.remoteInputs.delete(msg.playerId);
network.remoteBombPrev.delete(msg.playerId);
if (state.mode === "multiplayer" && state.screen === "game") {
const player = state.players.find((entry) => entry.ownerId === msg.playerId) || null;
if (player && player.alive) {
killPlayer(player, null);
showMatchNotification(player.name + " left the round.");
} else {
showMatchNotification("A player left the round.");
}
}
return;
}
if (msg.type === "player_input") {
if (!network.isHost || state.mode !== "multiplayer" || state.screen !== "game") {
return;
@ -883,6 +910,13 @@ function leaveMultiplayerToMainMenu() {
state.message = "Select mode";
}
function leaveMultiplayerToLobby() {
sendSocketMessage("lobby_leave");
leaveLobbyInternals();
enterLobbyScreen();
state.message = "Returned to lobby.";
}
function leaveSinglePlayerToMainMenu() {
closeMenu();
inputState.bombQueued = false;
@ -1001,7 +1035,6 @@ function serializeGameState(includeFullMap = false) {
})),
nextBombId: state.nextBombId,
outcomePlayed: state.outcomePlayed,
menuOpen: state.menu.open,
};
}
@ -1127,7 +1160,6 @@ function applySnapshot(snapshot) {
}));
state.nextBombId = snapshot.nextBombId;
state.outcomePlayed = snapshot.outcomePlayed;
state.menu.open = snapshot.menuOpen;
network.hasReceivedSnapshot = true;
rebuildFireLookup();
}
@ -2682,14 +2714,14 @@ function onActionDown(action, isRepeat = false) {
}
if (action.type === "menu" && !isRepeat) {
if (state.mode === "multiplayer" && state.status === "running") {
return;
}
if (state.menu.open) {
closeMenu();
} else {
openMenu();
}
if (state.mode === "multiplayer" && !network.isHost) {
sendLocalMultiplayerInput(true);
}
return;
}