diff --git a/server.js b/server.js index fa8d04a..093d606 100644 --- a/server.js +++ b/server.js @@ -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", () => { diff --git a/src/game.js b/src/game.js index 7d60940..72ca91c 100644 --- a/src/game.js +++ b/src/game.js @@ -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; }