diff --git a/TableturfBattleClient/src/GameVariables.ts b/TableturfBattleClient/src/GameVariables.ts index 065434c..defd90f 100644 --- a/TableturfBattleClient/src/GameVariables.ts +++ b/TableturfBattleClient/src/GameVariables.ts @@ -3,6 +3,7 @@ let clientToken = window.localStorage.getItem('clientToken') || ''; /** The data of the current game, or null if not in a game. */ let currentGame: { id: string, + state: GameState, /** The list of players in the current game. */ players: Player[], /** The maximum number of players in the game. */ diff --git a/TableturfBattleClient/src/Pages/DeckEditPage.ts b/TableturfBattleClient/src/Pages/DeckEditPage.ts index 765f283..4e0d9a4 100644 --- a/TableturfBattleClient/src/Pages/DeckEditPage.ts +++ b/TableturfBattleClient/src/Pages/DeckEditPage.ts @@ -183,7 +183,7 @@ function stopEditingDeck() { function onBeforeUnload_deckEditor(e: BeforeUnloadEvent) { e.preventDefault(); - return 'You have unsaved changes to your deck that will be lost. Are you sure you want to continue?'; + return 'You have unsaved changes to your deck that will be lost.'; } function addTestStage(stage: Stage) { diff --git a/TableturfBattleClient/src/Pages/GamePage.ts b/TableturfBattleClient/src/Pages/GamePage.ts index f249a31..af3252a 100644 --- a/TableturfBattleClient/src/Pages/GamePage.ts +++ b/TableturfBattleClient/src/Pages/GamePage.ts @@ -130,7 +130,7 @@ function initTest(stage: Stage) { clear(); testMode = true; gamePage.classList.add('deckTest'); - currentGame = { id: 'test', maxPlayers: 2, players: [ ], webSocket: null, turnNumber: 1, turnTimeLimit: null, turnTimeLeft: null, me: { playerIndex: 0, move: null, deck: null, hand: null, cardsUsed: [ ] } }; + currentGame = { id: 'test', state: GameState.Ongoing, maxPlayers: 2, players: [ ], webSocket: null, turnNumber: 1, turnTimeLimit: null, turnTimeLeft: null, me: { playerIndex: 0, move: null, deck: null, hand: null, cardsUsed: [ ] } }; board.resize(stage.copyGrid()); const startSpaces = stage.getStartSpaces(2); board.startSpaces = startSpaces; diff --git a/TableturfBattleClient/src/Pages/PreGamePage.ts b/TableturfBattleClient/src/Pages/PreGamePage.ts index d384dbb..b79cb33 100644 --- a/TableturfBattleClient/src/Pages/PreGamePage.ts +++ b/TableturfBattleClient/src/Pages/PreGamePage.ts @@ -312,6 +312,7 @@ function loadReplay(base64: string) { currentGame = { id: 'replay', + state: GameState.Redraw, me: null, players: players, maxPlayers: numPlayers, diff --git a/TableturfBattleClient/src/app.ts b/TableturfBattleClient/src/app.ts index 4b7c991..16f52a1 100644 --- a/TableturfBattleClient/src/app.ts +++ b/TableturfBattleClient/src/app.ts @@ -14,6 +14,7 @@ const decks: Deck[] = [ new Deck('Starter Deck', [ 6, 34, 159, 13, 45, 137, 22, let selectedDeck: Deck | null = null; let editingDeck = false; let deckModified = false; +let shouldConfirmLeavingGame = false; function delay(ms: number, abortSignal?: AbortSignal) { return new Promise((resolve, reject) => { @@ -92,6 +93,8 @@ function onGameStateChange(game: any, playerData: PlayerData | null) { if (currentGame == null) throw new Error('currentGame is null'); clearPlayContainers(); + currentGame.state = game.state; + if (game.board) { board.flip = playerData != null && playerData.playerIndex % 2 != 0; if (board.flip) gamePage.classList.add('boardFlipped'); @@ -109,11 +112,13 @@ function onGameStateChange(game: any, playerData: PlayerData | null) { switch (game.state) { case GameState.WaitingForPlayers: showPage('lobby'); + clearConfirmLeavingGame(); lobbySelectedStageSection.hidden = true; lobbyStageSection.hidden = !playerData || game.players[playerData.playerIndex]?.isReady; break; case GameState.Preparing: showPage('lobby'); + if (currentGame.me) setConfirmLeavingGame(); if (selectedStageButton) lobbySelectedStageSection.removeChild(selectedStageButton.element); selectedStageButton = new StageButton(stageDatabase.stages?.find(s => s.name == game.stage)!); @@ -142,6 +147,7 @@ function onGameStateChange(game: any, playerData: PlayerData | null) { switch (game.state) { case GameState.Redraw: + if (currentGame.me) setConfirmLeavingGame(); redrawModal.hidden = currentGame.me == null || currentGame.players[currentGame.me.playerIndex].isReady; turnNumberLabel.setTurnNumber(null); canPlay = false; @@ -149,6 +155,7 @@ function onGameStateChange(game: any, playerData: PlayerData | null) { timeLabel.paused = false; break; case GameState.Ongoing: + if (currentGame.me) setConfirmLeavingGame(); turnNumberLabel.setTurnNumber(game.turnNumber); board.autoHighlight = true; canPlay = currentGame.me != null && !currentGame.players[currentGame.me.playerIndex].isReady; @@ -157,6 +164,7 @@ function onGameStateChange(game: any, playerData: PlayerData | null) { setupControlsForPlay(); break; case GameState.Ended: + clearConfirmLeavingGame(); gamePage.classList.add('gameEnded'); turnNumberLabel.setTurnNumber(null); timeLabel.hide(); @@ -170,6 +178,7 @@ function onGameStateChange(game: any, playerData: PlayerData | null) { let errorDialogCallback: ((e: Event) => void) | null = null; function communicationError(message?: string, showButton?: boolean, callback?: (e: Event) => void) { + clearConfirmLeavingGame(); preGameLoadingSection.hidden = true; errorMessage.innerText = message ?? 'A communication error has occurred.\nPlease reload the page to rejoin.'; errorDialogCallback = callback ?? null; @@ -218,6 +227,7 @@ function setupWebSocket(gameID: string, myPlayerIndex: number | null) { } else { currentGame = { id: gameID, + state: payload.data.state, me: payload.playerData, players: payload.data.players, maxPlayers: payload.data.maxPlayers, @@ -355,6 +365,8 @@ function setupWebSocket(gameID: string, myPlayerIndex: number | null) { turnNumberLabel.setTurnNumber(payload.data.game.turnNumber); clearPlayContainers(); if (payload.event == 'gameEnd') { + currentGame.state = GameState.Ended; + clearConfirmLeavingGame(); gameButtonsContainer.hidden = true; passButton.enabled = false; specialButton.enabled = false; @@ -382,17 +394,37 @@ function setupWebSocket(gameID: string, myPlayerIndex: number | null) { return myPlayerIndex; } +function setConfirmLeavingGame() { + if (!shouldConfirmLeavingGame) { + shouldConfirmLeavingGame = true; + window.addEventListener('beforeunload', onBeforeUnload_game); + } +} + +function clearConfirmLeavingGame() { + if (shouldConfirmLeavingGame) { + shouldConfirmLeavingGame = false; + window.removeEventListener('beforeunload', onBeforeUnload_game); + } +} + function processUrl() { if (deckModified) { if (!confirm('You have unsaved changes to your deck. Are you sure you want to leave?')) { setUrl('deckeditor'); return false; } + } else if (shouldConfirmLeavingGame) { + if (!confirm('This will disconnect you from a game in progress. Are you sure you want to leave?')) { + setUrl(`game/${currentGame!.id}`); + return false; + } } stopEditingDeck(); errorDialog.close(); currentGame = null; currentReplay = null; + clearConfirmLeavingGame(); if (location.pathname.endsWith('/deckeditor') || location.hash == '#deckeditor') onInitialise(showDeckList); else { @@ -417,6 +449,11 @@ function processUrl() { return true; } +function onBeforeUnload_game(e: BeforeUnloadEvent) { + e.preventDefault(); + return 'This will disconnect you from a game in progress.'; +} + function parseGameID(s: string) { const m = /(?:^|[#/])([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i.exec(s); return m ? m[1] : null;