diff --git a/client/config/globals.js b/client/config/globals.js index ffdc08e..421a316 100644 --- a/client/config/globals.js +++ b/client/config/globals.js @@ -12,7 +12,9 @@ export const globals = { PAUSE_TIMER: 'pauseTimer', RESUME_TIMER: 'resumeTimer', GET_TIME_REMAINING: 'getTimeRemaining', - KILL_PLAYER: 'killPlayer' + KILL_PLAYER: 'killPlayer', + REVEAL_PLAYER: 'revealPlayer', + TRANSFER_MODERATOR: 'transferModerator' }, STATUS: { LOBBY: "lobby", @@ -26,12 +28,15 @@ export const globals = { PLAYER_JOINED: "playerJoined", SYNC_GAME_STATE: "syncGameState", START_TIMER: "startTimer", - KILL_PLAYER: "killPlayer" + KILL_PLAYER: "killPlayer", + REVEAL_PLAYER: 'revealPlayer' }, USER_TYPES: { MODERATOR: "moderator", PLAYER: "player", - TEMPORARY_MODERATOR: "player / temp mod" + TEMPORARY_MODERATOR: "player / temp mod", + KILLED_PLAYER: "killed", + SPECTATOR: "spectator" }, ENVIRONMENT: { LOCAL: "local", diff --git a/client/modules/GameStateRenderer.js b/client/modules/GameStateRenderer.js index 949b58f..4d7c22f 100644 --- a/client/modules/GameStateRenderer.js +++ b/client/modules/GameStateRenderer.js @@ -1,12 +1,15 @@ import { globals } from "../config/globals.js"; import { toast } from "./Toast.js"; import {templates} from "./Templates.js"; +import {ModalManager} from "./ModalManager.js"; export class GameStateRenderer { constructor(gameState, socket) { this.gameState = gameState; this.socket = socket; this.killPlayerHandlers = {}; + this.revealRoleHandlers = {}; + this.transferModHandlers = {}; this.cardFlipped = false; } @@ -20,12 +23,6 @@ export class GameStateRenderer { lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name,person.userType)) } let playerCount = this.gameState.people.filter((person) => person.userType === globals.USER_TYPES.PLAYER).length; - if (this.gameState.moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { - playerCount += 1; - } - if (this.gameState.client.userType === globals.USER_TYPES.PLAYER) { - playerCount += 1; - } document.querySelector("label[for='lobby-players']").innerText = "People (" + playerCount + "/" + getGameSize(this.gameState.deck) + " Players)"; } @@ -67,19 +64,37 @@ export class GameStateRenderer { let div = document.createElement("div"); div.innerHTML = templates.END_GAME_PROMPT; document.body.appendChild(div); + + let modTransferButton = document.getElementById("mod-transfer-button"); + modTransferButton.addEventListener( + "click", () => { + this.displayAvailableModerators() + ModalManager.displayModal( + "transfer-mod-modal", + "transfer-mod-modal-background", + "close-modal-button" + ) + } + ) this.renderPlayersWithRoleAndAlignmentInfo(); } - renderPlayerView() { + renderPlayerView(isKilled=false) { + if (isKilled) { + let clientUserType = document.getElementById("client-user-type"); + if (clientUserType) { + clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80' + } + } renderPlayerRole(this.gameState); - this.renderPlayersWithNoRoleInformation(); + this.renderPlayersWithNoRoleInformationUnlessRevealed(); } refreshPlayerList(isModerator) { if (isModerator) { this.renderPlayersWithRoleAndAlignmentInfo() } else { - this.renderPlayersWithNoRoleInformation(); + this.renderPlayersWithNoRoleInformationUnlessRevealed(); } } @@ -87,7 +102,12 @@ export class GameStateRenderer { document.querySelectorAll('.game-player').forEach((el) => { let pointer = el.dataset.pointer; if (pointer && this.killPlayerHandlers[pointer]) { - el.removeEventListener('click', this.killPlayerHandlers[pointer]) + el.removeEventListener('click', this.killPlayerHandlers[pointer]); + delete this.killPlayerHandlers[pointer]; + } + if (pointer && this.revealRoleHandlers[pointer]) { + el.removeEventListener('click', this.revealRoleHandlers[pointer]); + delete this.revealRoleHandlers[pointer]; } el.remove(); }); @@ -96,14 +116,14 @@ export class GameStateRenderer { }); let teamGood = this.gameState.people.filter((person) => person.alignment === globals.ALIGNMENT.GOOD); let teamEvil = this.gameState.people.filter((person) => person.alignment === globals.ALIGNMENT.EVIL); - renderGroupOfPlayers(teamEvil, this.killPlayerHandlers, this.gameState.accessCode, globals.ALIGNMENT.EVIL, true, this.socket); - renderGroupOfPlayers(teamGood, this.killPlayerHandlers, this.gameState.accessCode, globals.ALIGNMENT.GOOD, true, this.socket); + renderGroupOfPlayers(teamEvil, this.killPlayerHandlers, this.revealRoleHandlers, this.gameState.accessCode, globals.ALIGNMENT.EVIL, true, this.socket); + renderGroupOfPlayers(teamGood, this.killPlayerHandlers, this.revealRoleHandlers, this.gameState.accessCode, globals.ALIGNMENT.GOOD, true, this.socket); document.getElementById("players-alive-label").innerText = 'Players: ' + this.gameState.people.filter((person) => !person.out).length + ' / ' + this.gameState.people.length + ' Alive'; } - renderPlayersWithNoRoleInformation() { + renderPlayersWithNoRoleInformationUnlessRevealed() { document.querySelectorAll('.game-player').forEach((el) => el.remove()); this.gameState.people.sort((a, b) => { return a.name >= b.name ? 1 : -1; @@ -114,6 +134,44 @@ export class GameStateRenderer { } + updatePlayerCardToKilledState() { + document.querySelector('#role-image').classList.add("killed-card"); + document.getElementById("role-image").setAttribute( + 'src', + '../images/tombstone.png' + ); + } + + displayAvailableModerators() { + document.querySelectorAll('.potential-moderator').forEach((el) => { + let pointer = el.dataset.pointer; + if (pointer && this.transferModHandlers[pointer]) { + el.removeEventListener('click', this.transferModHandlers[pointer]); + delete this.transferModHandlers[pointer]; + } + el.remove(); + }); + let modalContent = document.getElementById("transfer-mod-form-content"); + if (modalContent) { + for (let player of this.gameState.people) { + if (player.out) { + let container = document.createElement("div"); + container.classList.add('potential-moderator'); + container.dataset.pointer = player.id; + container.innerText = player.name; + this.transferModHandlers[player.id] = () => { + if (confirm("Transfer moderator powers to " + player.name + "?")) { + socket.emit(globals.COMMANDS.TRANSFER_MODERATOR, this.gameState.accessCode, player.id); + } + } + + container.addEventListener('click', this.transferModHandlers[player.id]); + modalContent.appendChild(container); + } + } + } + } + } function renderLobbyPerson(name, userType) { @@ -146,7 +204,7 @@ function removeExistingTitle() { } } -function renderGroupOfPlayers(players, handlers, accessCode=null, alignment=null, moderator=false, socket=null) { +function renderGroupOfPlayers(players, killPlayerHandlers, revealRoleHandlers, accessCode=null, alignment=null, moderator=false, socket=null) { for (let player of players) { let container = document.createElement("div"); container.classList.add('game-player'); @@ -159,10 +217,14 @@ function renderGroupOfPlayers(players, handlers, accessCode=null, alignment=null container.querySelector('.game-player-name').innerText = player.name; let roleElement = container.querySelector('.game-player-role') - if (alignment) { + if (moderator) { roleElement.classList.add(alignment); roleElement.innerText = player.gameRole; document.getElementById("player-list-moderator-team-" + alignment).appendChild(container); + } else if (player.revealed) { + roleElement.classList.add(player.alignment); + roleElement.innerText = player.gameRole; + document.getElementById("game-player-list").appendChild(container); } else { roleElement.innerText = "Unknown" document.getElementById("game-player-list").appendChild(container); @@ -175,12 +237,27 @@ function renderGroupOfPlayers(players, handlers, accessCode=null, alignment=null } } else { if (moderator) { - handlers[player.id] = () => { + killPlayerHandlers[player.id] = () => { if (confirm("KILL " + player.name + "?")) { socket.emit(globals.COMMANDS.KILL_PLAYER, accessCode, player.id); } } - container.querySelector('.kill-player-button').addEventListener('click', handlers[player.id]); + container.querySelector('.kill-player-button').addEventListener('click', killPlayerHandlers[player.id]); + } + } + + if (player.revealed) { + if (moderator) { + container.querySelector('.reveal-role-button')?.remove(); + } + } else { + if (moderator) { + revealRoleHandlers[player.id] = () => { + if (confirm("REVEAL " + player.name + "?")) { + socket.emit(globals.COMMANDS.REVEAL_PLAYER, accessCode, player.id); + } + } + container.querySelector('.reveal-role-button').addEventListener('click', revealRoleHandlers[player.id]); } } } @@ -196,19 +273,20 @@ function renderPlayerRole(gameState) { } name.setAttribute("title", gameState.client.gameRole); if (gameState.client.out) { - document.querySelector('#role-description').innerText = "You have been killed."; + document.querySelector('#role-image').classList.add("killed-card"); document.getElementById("role-image").setAttribute( 'src', '../images/tombstone.png' ); } else { - document.querySelector('#role-description').innerText = gameState.client.gameRoleDescription; document.getElementById("role-image").setAttribute( 'src', '../images/roles/' + gameState.client.gameRole.replaceAll(' ', '') + '.png' ); } + document.querySelector('#role-description').innerText = gameState.client.gameRoleDescription; + document.getElementById("game-role-back").addEventListener('click', () => { document.getElementById("game-role").style.display = 'flex'; document.getElementById("game-role-back").style.display = 'none'; diff --git a/client/modules/GameTimerManager.js b/client/modules/GameTimerManager.js index a16cade..b350e51 100644 --- a/client/modules/GameTimerManager.js +++ b/client/modules/GameTimerManager.js @@ -11,21 +11,12 @@ export class GameTimerManager { } } - // startGameTimer (hours, minutes, tickRate, soundManager, timerWorker) { - // if (window.Worker) { - // timerWorker.onmessage = function (e) { - // if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds > 0) { - // document.getElementById('game-timer').innerText = e.data.displayTime; - // } - // }; - // const totalTime = convertFromHoursToMilliseconds(hours) + convertFromMinutesToMilliseconds(minutes); - // timerWorker.postMessage({ totalTime: totalTime, tickInterval: tickRate }); - // } - // } - resumeGameTimer(totalTime, tickRate, soundManager, timerWorker) { if (window.Worker) { - if (this.gameState.client.userType !== globals.USER_TYPES.PLAYER) { + if ( + this.gameState.client.userType === globals.USER_TYPES.MODERATOR + || this.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + ) { this.swapToPauseButton(); } let instance = this; @@ -49,7 +40,10 @@ export class GameTimerManager { pauseGameTimer(timerWorker, timeRemaining) { if (window.Worker) { - if (this.gameState.client.userType !== globals.USER_TYPES.PLAYER) { + if ( + this.gameState.client.userType === globals.USER_TYPES.MODERATOR + || this.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + ) { this.swapToPlayButton(); } @@ -63,7 +57,10 @@ export class GameTimerManager { } displayPausedTime(time) { - if (this.gameState.client.userType !== globals.USER_TYPES.PLAYER) { + if ( + this.gameState.client.userType === globals.USER_TYPES.MODERATOR + || this.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + ) { this.swapToPlayButton(); } @@ -87,18 +84,6 @@ export class GameTimerManager { } attachTimerSocketListeners(socket, timerWorker, gameStateRenderer) { - // if (!socket.hasListeners(globals.EVENTS.START_TIMER)) { - // socket.on(globals.EVENTS.START_TIMER, () => { - // this.startGameTimer( - // gameStateRenderer.gameState.timerParams.hours, - // gameStateRenderer.gameState.timerParams.minutes, - // globals.CLOCK_TICK_INTERVAL_MILLIS, - // null, - // timerWorker - // ) - // }); - // } - if(!socket.hasListeners(globals.COMMANDS.PAUSE_TIMER)) { socket.on(globals.COMMANDS.PAUSE_TIMER, (timeRemaining) => { this.pauseGameTimer(timerWorker, timeRemaining) @@ -152,15 +137,6 @@ export class GameTimerManager { } } - -function convertFromMinutesToMilliseconds(minutes) { - return minutes * 60 * 1000; -} - -function convertFromHoursToMilliseconds(hours) { - return hours * 60 * 60 * 1000; -} - function returnHumanReadableTime(milliseconds, tenthsOfSeconds=false) { let tenths = Math.floor((milliseconds / 100) % 10); diff --git a/client/modules/Templates.js b/client/modules/Templates.js index 4a38c95..42242f2 100644 --- a/client/modules/Templates.js +++ b/client/modules/Templates.js @@ -46,6 +46,15 @@ export const templates = { "
" + "", MODERATOR_GAME_VIEW: + "" + + "" + "