From a66bc7b413569fc21ec057aff1c0701b7ec87dad Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 8 Dec 2021 00:13:13 -0500 Subject: [PATCH] flesh out killing players, display 0 on timerand remove play/pause button --- client/config/globals.js | 3 +- client/images/eye.svg | 3 + client/images/tombstone.png | Bin 0 -> 29778 bytes client/modules/GameStateRenderer.js | 108 +++++++++++++++------------- client/modules/GameTimerManager.js | 24 ++++++- client/modules/Templates.js | 3 +- client/modules/Timer.js | 4 ++ client/scripts/game.js | 16 +++++ client/styles/GLOBAL.css | 4 +- client/styles/game.css | 30 ++++++-- client/views/404.html | 18 ++--- client/views/game.html | 8 +-- server/modules/GameManager.js | 8 ++- server/modules/GameStateCurator.js | 10 +-- 14 files changed, 157 insertions(+), 82 deletions(-) create mode 100644 client/images/eye.svg create mode 100644 client/images/tombstone.png diff --git a/client/config/globals.js b/client/config/globals.js index 8eec0a7..ffdc08e 100644 --- a/client/config/globals.js +++ b/client/config/globals.js @@ -25,7 +25,8 @@ export const globals = { EVENTS: { PLAYER_JOINED: "playerJoined", SYNC_GAME_STATE: "syncGameState", - START_TIMER: "startTimer" + START_TIMER: "startTimer", + KILL_PLAYER: "killPlayer" }, USER_TYPES: { MODERATOR: "moderator", diff --git a/client/images/eye.svg b/client/images/eye.svg new file mode 100644 index 0000000..5add930 --- /dev/null +++ b/client/images/eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/tombstone.png b/client/images/tombstone.png new file mode 100644 index 0000000000000000000000000000000000000000..49a91b6422748a71d20ce2b271ea711caf194dcd GIT binary patch literal 29778 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 I_a43cZ!$wV!2kdN literal 0 HcmV?d00001 diff --git a/client/modules/GameStateRenderer.js b/client/modules/GameStateRenderer.js index 46fb670..949b58f 100644 --- a/client/modules/GameStateRenderer.js +++ b/client/modules/GameStateRenderer.js @@ -27,7 +27,7 @@ export class GameStateRenderer { playerCount += 1; } document.querySelector("label[for='lobby-players']").innerText = - "Other People (" + playerCount + "/" + getGameSize(this.gameState.deck) + " Players)"; + "People (" + playerCount + "/" + getGameSize(this.gameState.deck) + " Players)"; } renderLobbyHeader() { @@ -67,22 +67,53 @@ export class GameStateRenderer { let div = document.createElement("div"); div.innerHTML = templates.END_GAME_PROMPT; document.body.appendChild(div); - renderPlayersWithRoleAndAlignmentInfo(this.gameState.people, this.socket, this.gameState.accessCode, this.killPlayerHandlers); + this.renderPlayersWithRoleAndAlignmentInfo(); } renderPlayerView() { renderPlayerRole(this.gameState); - renderPlayersWithNoRoleInformation(this.gameState.people, this.killPlayerHandlers); + this.renderPlayersWithNoRoleInformation(); } refreshPlayerList(isModerator) { if (isModerator) { - renderPlayersWithRoleAndAlignmentInfo(this.gameState.people, this.socket, this.gameState.accessCode, this.killPlayerHandlers) + this.renderPlayersWithRoleAndAlignmentInfo() } else { - renderPlayersWithNoRoleInformation(this.gameState.people, this.killPlayerHandlers); + this.renderPlayersWithNoRoleInformation(); } } + renderPlayersWithRoleAndAlignmentInfo() { + document.querySelectorAll('.game-player').forEach((el) => { + let pointer = el.dataset.pointer; + if (pointer && this.killPlayerHandlers[pointer]) { + el.removeEventListener('click', this.killPlayerHandlers[pointer]) + } + el.remove(); + }); + this.gameState.people.sort((a, b) => { + return a.name >= b.name ? 1 : -1; + }); + 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); + document.getElementById("players-alive-label").innerText = + 'Players: ' + this.gameState.people.filter((person) => !person.out).length + ' / ' + this.gameState.people.length + ' Alive'; + + } + + renderPlayersWithNoRoleInformation() { + document.querySelectorAll('.game-player').forEach((el) => el.remove()); + this.gameState.people.sort((a, b) => { + return a.name >= b.name ? 1 : -1; + }); + renderGroupOfPlayers(this.gameState.people, this.killPlayerHandlers); + document.getElementById("players-alive-label").innerText = + 'Players: ' + this.gameState.people.filter((person) => !person.out).length + ' / ' + this.gameState.people.length + ' Alive'; + + } + } function renderLobbyPerson(name, userType) { @@ -115,37 +146,6 @@ function removeExistingTitle() { } } -function renderPlayersWithRoleAndAlignmentInfo(people, socket, accessCode, handlers) { - document.querySelectorAll('.game-player').forEach((el) => { - let pointer = el.dataset.pointer; - if (pointer && handlers[pointer]) { - el.removeEventListener('click', handlers[pointer]) - } - el.remove(); - }); - people.sort((a, b) => { - return a.name >= b.name ? 1 : -1; - }); - let teamGood = people.filter((person) => person.alignment === globals.ALIGNMENT.GOOD); - let teamEvil = people.filter((person) => person.alignment === globals.ALIGNMENT.EVIL); - renderGroupOfPlayers(teamEvil, handlers, accessCode, globals.ALIGNMENT.EVIL, true, socket); - renderGroupOfPlayers(teamGood, handlers, accessCode, globals.ALIGNMENT.GOOD, true, socket); - document.getElementById("players-alive-label").innerText = - 'Players: ' + people.filter((person) => !person.out).length + ' / ' + people.length + ' Alive'; - -} - -function renderPlayersWithNoRoleInformation(people, handlers) { - document.querySelectorAll('.game-player').forEach((el) => el.remove()); - people.sort((a, b) => { - return a.name >= b.name ? 1 : -1; - }); - renderGroupOfPlayers(people, handlers); - document.getElementById("players-alive-label").innerText = - 'Players: ' + people.filter((person) => !person.out).length + ' / ' + people.length + ' Alive'; - -} - function renderGroupOfPlayers(players, handlers, accessCode=null, alignment=null, moderator=false, socket=null) { for (let player of players) { let container = document.createElement("div"); @@ -168,14 +168,18 @@ function renderGroupOfPlayers(players, handlers, accessCode=null, alignment=null document.getElementById("game-player-list").appendChild(container); } - if (moderator) { - handlers[player.id] = () => { - socket.emit(globals.COMMANDS.KILL_PLAYER, accessCode, player.id); + if (player.out) { + container.classList.add('killed'); + if (moderator) { + container.querySelector('.kill-player-button')?.remove(); } - if (player.out) { - container.classList.add('killed'); - container.querySelector('.kill-player-button').remove(); - } else { + } else { + if (moderator) { + handlers[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]); } } @@ -191,11 +195,19 @@ function renderPlayerRole(gameState) { name.classList.add('evil'); } name.setAttribute("title", gameState.client.gameRole); - document.querySelector('#role-description').innerText = gameState.client.gameRoleDescription; - document.getElementById("role-image").setAttribute( - 'src', - '../images/roles/' + gameState.client.gameRole.replaceAll(' ', '') + '.png' - ); + if (gameState.client.out) { + document.querySelector('#role-description').innerText = "You have been killed."; + 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.getElementById("game-role-back").addEventListener('click', () => { document.getElementById("game-role").style.display = 'flex'; diff --git a/client/modules/GameTimerManager.js b/client/modules/GameTimerManager.js index 3c1dcc9..a16cade 100644 --- a/client/modules/GameTimerManager.js +++ b/client/modules/GameTimerManager.js @@ -28,15 +28,19 @@ export class GameTimerManager { if (this.gameState.client.userType !== globals.USER_TYPES.PLAYER) { this.swapToPauseButton(); } - + let instance = this; let timer = document.getElementById('game-timer'); timer.classList.remove('paused'); timer.innerText = totalTime < 60000 ? returnHumanReadableTime(totalTime, true) : returnHumanReadableTime(totalTime); timerWorker.onmessage = function (e) { - if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds > 0) { - timer.innerText = e.data.displayTime; + if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds >= 0) { + if (e.data.timeRemainingInMilliseconds === 0) { + instance.displayExpiredTime(); + } else { + timer.innerText = e.data.displayTime; + } } }; timerWorker.postMessage({ totalTime: totalTime, tickInterval: tickRate }); @@ -70,6 +74,18 @@ export class GameTimerManager { timer.classList.add('paused'); } + displayExpiredTime() { + let currentBtn = document.querySelector('#play-pause img'); + if (currentBtn) { + currentBtn.removeEventListener('click', this.pauseListener); + currentBtn.removeEventListener('click', this.playListener); + currentBtn.remove(); + } + + let timer = document.getElementById('game-timer'); + timer.innerText = returnHumanReadableTime(0, true); + } + attachTimerSocketListeners(socket, timerWorker, gameStateRenderer) { // if (!socket.hasListeners(globals.EVENTS.START_TIMER)) { // socket.on(globals.EVENTS.START_TIMER, () => { @@ -100,6 +116,8 @@ export class GameTimerManager { console.log('received time remaining from server'); if (paused) { this.displayPausedTime(timeRemaining); + } else if (timeRemaining === 0) { + this.displayExpiredTime(); } else { this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker); } diff --git a/client/modules/Templates.js b/client/modules/Templates.js index 8c1e41f..4a38c95 100644 --- a/client/modules/Templates.js +++ b/client/modules/Templates.js @@ -75,7 +75,8 @@ export const templates = { "
" + "" + "
" + - "" + + "" + + "" + "
", GAME_PLAYER: "
" + diff --git a/client/modules/Timer.js b/client/modules/Timer.js index 8d581b7..abc81cf 100644 --- a/client/modules/Timer.js +++ b/client/modules/Timer.js @@ -30,6 +30,10 @@ onmessage = function (e) { function stepFn (expected, interval, start, totalTime) { const now = Date.now(); if (now - start >= totalTime) { + postMessage({ + timeRemainingInMilliseconds: 0, + displayTime: returnHumanReadableTime(0, true) + }); return; } const delta = now - expected; diff --git a/client/scripts/game.js b/client/scripts/game.js index 288e5ff..b04735f 100644 --- a/client/scripts/game.js +++ b/client/scripts/game.js @@ -132,6 +132,22 @@ function setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTim if (timerWorker && gameTimerManager) { gameTimerManager.attachTimerSocketListeners(socket, timerWorker, gameStateRenderer); } + + if (!socket.hasListeners(globals.EVENTS.KILL_PLAYER)) { + socket.on(globals.EVENTS.KILL_PLAYER, (id) => { + let killedPerson = gameStateRenderer.gameState.people.find((person) => person.id === id); + if (killedPerson) { + killedPerson.out = true; + if (gameStateRenderer.gameState.client.userType === globals.USER_TYPES.MODERATOR) { + toast(killedPerson.name + ' killed.', 'success', true, true, 6); + gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo() + } else { + toast(killedPerson.name + ' was killed!', 'warning', false, true, 6); + gameStateRenderer.renderPlayersWithNoRoleInformation(); + } + } + }); + } } function displayStartGamePromptForModerators(gameStateRenderer, socket) { diff --git a/client/styles/GLOBAL.css b/client/styles/GLOBAL.css index f43b683..6a51085 100644 --- a/client/styles/GLOBAL.css +++ b/client/styles/GLOBAL.css @@ -214,11 +214,11 @@ input { } .good, .compact-card.good .card-role { - color: #4b6bfa !important; + color: #4b6bfa; } .evil, .compact-card.evil .card-role { - color: #e73333 !important + color: #e73333; } @keyframes placeholder { diff --git a/client/styles/game.css b/client/styles/game.css index fbb53e8..e2bf9ee 100644 --- a/client/styles/game.css +++ b/client/styles/game.css @@ -346,7 +346,7 @@ label[for='moderator'] { text-overflow: ellipsis; } -.kill-player-button, .make-mod-button { +.kill-player-button, .reveal-role-button { font-family: 'signika-negative', sans-serif !important; padding: 5px; border-radius: 3px; @@ -358,6 +358,18 @@ label[for='moderator'] { text-shadow: 0 3px 4px rgb(0 0 0 / 55%); margin: 5px 0 5px 25px; min-width: 6em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.reveal-role-button { + background-color: #3f5256; +} + +.reveal-role-button img { + width: 18px; + margin-left: 5px; } .killed::after { @@ -367,12 +379,13 @@ label[for='moderator'] { font-size: 24px; } -.killed { - filter: grayscale(1); - opacity: 0.6; - pointer-events: none; +.killed, .killed .game-player-role { + color: gray !important; } +.reveal-role-button { + display: flex; + align-items: center; } .make-mod-button { @@ -390,7 +403,12 @@ label[for='moderator'] { } .kill-player-button { - background-color: #3f5256; + background-color: #9f4747; +} + +.game-player > div:nth-child(2) { + display: flex; + flex-wrap: wrap; } #game-player-list > div { diff --git a/client/views/404.html b/client/views/404.html index 210c111..a543e48 100644 --- a/client/views/404.html +++ b/client/views/404.html @@ -13,20 +13,16 @@ - - - - - - - - - - + + + + + + diff --git a/client/views/game.html b/client/views/game.html index 9cfbb4e..3a12de1 100644 --- a/client/views/game.html +++ b/client/views/game.html @@ -13,13 +13,13 @@ - - - + + +
diff --git a/server/modules/GameManager.js b/server/modules/GameManager.js index 8909eea..3a50d63 100644 --- a/server/modules/GameManager.js +++ b/server/modules/GameManager.js @@ -88,6 +88,10 @@ class GameManager { socketId: socket.id, logLevel: this.logger.logLevel }); + } else { + if (game.timerParams && game.timerParams.timeRemaining === 0) { + this.namespace.to(socket.id).emit(globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, game.timerParams.timeRemaining, game.timerParams.paused); + } } } }); @@ -96,10 +100,10 @@ class GameManager { let game = this.activeGameRunner.activeGames[accessCode]; if (game) { let person = game.people.find((person) => person.id === personId) - if (person) { + if (person && !person.out) { this.logger.debug('game ' + accessCode + ': killing player ' + person.name); person.out = true; - namespace.in(accessCode).emit(globals.CLIENT_COMMANDS.KILL_PLAYER, ) + namespace.in(accessCode).emit(globals.CLIENT_COMMANDS.KILL_PLAYER, person.id) } } }) diff --git a/server/modules/GameStateCurator.js b/server/modules/GameStateCurator.js index e707ba1..946cb9a 100644 --- a/server/modules/GameStateCurator.js +++ b/server/modules/GameStateCurator.js @@ -11,11 +11,13 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) { ? { name: person.name, cookie: person.cookie, userType: person.userType } : { name: person.name, + id: person.id, cookie: person.cookie, userType: person.userType, gameRole: person.gameRole, gameRoleDescription: person.gameRoleDescription, - alignment: person.alignment + alignment: person.alignment, + out: person.out } switch (person.userType) { case globals.USER_TYPES.PLAYER: @@ -27,10 +29,10 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) { deck: game.deck, people: game.people .filter((person) => { - return person.assigned === true && person.cookie !== client.cookie + return person.assigned === true && (person.userType !== globals.USER_TYPES.MODERATOR && person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR) }) - .map((filteredPerson) => ({ name: filteredPerson.name, userType: filteredPerson.userType })), + .map((filteredPerson) => ({ name: filteredPerson.name, id: filteredPerson.id, userType: filteredPerson.userType, out: filteredPerson.out })), timerParams: game.timerParams, isFull: game.isFull, } @@ -91,7 +93,7 @@ function mapPeopleForTempModerator(people, client) { } function mapPerson(person) { - return { name: person.name, userType: person.userType, out: person.out }; + return { name: person.name, id: person.id, userType: person.userType, out: person.out }; } module.exports = GameStateCurator;