reveal role functionality, beginnings of mod transfer

This commit is contained in:
Alec
2021-12-14 23:21:54 -05:00
parent a66bc7b413
commit 342ae5b80b
11 changed files with 250 additions and 76 deletions

View File

@@ -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",

View File

@@ -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';

View File

@@ -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);

View File

@@ -46,6 +46,15 @@ export const templates = {
"<div id='game-player-list'></div>" +
"</div>",
MODERATOR_GAME_VIEW:
"<div id='transfer-mod-modal-background' class='modal-background' style='display: none'></div>" +
"<div id='transfer-mod-modal' class='modal' style='display: none'>" +
"<form id='transfer-mod-form'>" +
"<div id='transfer-mod-form-content'></div>" +
"<div id='modal-button-container'>" +
"<button id='close-modal-button'>Cancel</button>" +
"</div>" +
"</form>" +
"</div>" +
"<div id='game-header'>" +
"<div class='timer-container-moderator'>" +
"<div>" +
@@ -54,7 +63,7 @@ export const templates = {
"</div>" +
"<div id='play-pause'>" + "</div>" +
"</div>" +
"<button class='moderator-player-button make-mod-button'>Transfer Mod Powers \uD83D\uDD00</button>" +
"<button id='mod-transfer-button' class='moderator-player-button make-mod-button'>Transfer Mod Powers \uD83D\uDD00</button>" +
"</div>" +
"<div>" +
"<label id='players-alive-label'></label>" +

View File

@@ -74,6 +74,10 @@ function processGameState (gameState, userId, socket, gameStateRenderer) {
document.getElementById("game-state-container").innerHTML = templates.PLAYER_GAME_VIEW;
gameStateRenderer.renderPlayerView();
break;
case globals.USER_TYPES.KILLED_PLAYER:
document.getElementById("game-state-container").innerHTML = templates.PLAYER_GAME_VIEW;
gameStateRenderer.renderPlayerView(true);
break;
case globals.USER_TYPES.MODERATOR:
document.querySelector("#start-game-prompt")?.remove();
document.getElementById("game-state-container").innerHTML = templates.MODERATOR_GAME_VIEW;
@@ -142,8 +146,39 @@ function setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTim
toast(killedPerson.name + ' killed.', 'success', true, true, 6);
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo()
} else {
toast(killedPerson.name + ' was killed!', 'warning', false, true, 6);
gameStateRenderer.renderPlayersWithNoRoleInformation();
if (killedPerson.id === gameStateRenderer.gameState.client.id) {
let clientUserType = document.getElementById("client-user-type");
if (clientUserType) {
clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80'
}
gameStateRenderer.updatePlayerCardToKilledState();
toast('You have been killed!', 'warning', false, true, 6);
} else {
toast(killedPerson.name + ' was killed!', 'warning', false, true, 6);
}
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed();
}
}
});
}
if (!socket.hasListeners(globals.EVENTS.REVEAL_PLAYER)) {
socket.on(globals.EVENTS.REVEAL_PLAYER, (revealData) => {
let revealedPerson = gameStateRenderer.gameState.people.find((person) => person.id === revealData.id);
if (revealedPerson) {
revealedPerson.revealed = true;
revealedPerson.gameRole = revealData.gameRole;
revealedPerson.alignment = revealData.alignment;
if (gameStateRenderer.gameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(revealedPerson.name + ' revealed.', 'success', true, true, 6);
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo()
} else {
if (revealedPerson.id === gameStateRenderer.gameState.client.id) {
toast('Your role has been revealed!', 'warning', false, true, 6);
} else {
toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', false, true, 6);
}
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed();
}
}
});

View File

@@ -41,10 +41,33 @@ h1 {
margin: 0.5em auto;
}
#game-state-container > div {
#game-state-container > div:not(#transfer-mod-modal-background):not(#transfer-mod-modal){
margin: 1em;
}
.potential-moderator {
display: flex;
color: #d7d7d7;
background-color: black;
border: 2px solid transparent;
align-items: center;
padding: 10px;
border-radius: 3px;
justify-content: space-between;
margin: 0.5em 0;
position: relative;
}
.potential-moderator:hover {
border: 2px solid #d7d7d7;
cursor: pointer;
}
.potential-moderator:active {
border: 2px solid #21ba45;
transition: border 0.2s ease-out;
}
#game-link {
user-select: none;
-ms-user-select: none;
@@ -234,7 +257,7 @@ label[for='moderator'] {
align-items: center;
justify-content: center;
position: fixed;
z-index: 1000;
z-index: 3;
border-radius: 3px;
font-family: 'signika-negative', sans-serif;
font-weight: 100;
@@ -326,7 +349,7 @@ label[for='moderator'] {
}
.game-player {
border-left: 2px solid gray;
border-left: 3px solid #21ba45;
display: flex;
color: #d7d7d7;
background-color: black;
@@ -380,7 +403,11 @@ label[for='moderator'] {
}
.killed, .killed .game-player-role {
color: gray !important;
/*color: gray !important;*/
}
.game-player.killed {
border-left: 3px solid #444444;
}
.reveal-role-button {
@@ -406,6 +433,10 @@ label[for='moderator'] {
background-color: #9f4747;
}
.killed-card {
width: 55% !important;
}
.game-player > div:nth-child(2) {
display: flex;
flex-wrap: wrap;

View File

@@ -3,7 +3,6 @@
text-align: center;
position: fixed;
width: 100%;
height: 100%;
z-index: 100;
top: 50%;
left: 50%;
@@ -11,10 +10,10 @@
background-color: #23282b;
align-items: center;
justify-content: center;
max-width: 17em;
max-height: 24em;
max-width: 19em;
max-height: 80%;
height: fit-content;
font-family: sans-serif;
font-size: 22px;
flex-direction: column;
padding: 1em;
}
@@ -25,7 +24,7 @@
left: 0;
width: 100%;
height: calc(100% + 100px);
background-color: rgba(0, 0, 0, 0.55);
background-color: rgba(0, 0, 0, 0.75);
z-index: 50;
cursor: pointer;
}

View File

@@ -9,7 +9,8 @@ const globals = {
PAUSE_TIMER: 'pauseTimer',
RESUME_TIMER: 'resumeTimer',
GET_TIME_REMAINING: 'getTimeRemaining',
KILL_PLAYER: 'killPlayer'
KILL_PLAYER: 'killPlayer',
REVEAL_PLAYER: 'revealPlayer'
},
STATUS: {
LOBBY: "lobby",
@@ -20,7 +21,9 @@ const globals = {
USER_TYPES: {
MODERATOR: "moderator",
PLAYER: "player",
TEMPORARY_MODERATOR: "player / temp mod"
TEMPORARY_MODERATOR: "player / temp mod",
KILLED_PLAYER: "killed",
SPECTATOR: "spectator"
},
ERROR_MESSAGE: {
GAME_IS_FULL: "This game is full"

View File

@@ -11,6 +11,7 @@ class Person {
this.alignment = alignment;
this.assigned = assigned;
this.out = false;
this.revealed = false;
}
}

View File

@@ -102,10 +102,29 @@ class GameManager {
let person = game.people.find((person) => person.id === personId)
if (person && !person.out) {
this.logger.debug('game ' + accessCode + ': killing player ' + person.name);
person.userType = globals.USER_TYPES.KILLED_PLAYER;
person.out = true;
namespace.in(accessCode).emit(globals.CLIENT_COMMANDS.KILL_PLAYER, person.id)
}
}
});
socket.on(globals.CLIENT_COMMANDS.REVEAL_PLAYER, (accessCode, personId) => {
let game = this.activeGameRunner.activeGames[accessCode];
if (game) {
let person = game.people.find((person) => person.id === personId)
if (person && !person.revealed) {
this.logger.debug('game ' + accessCode + ': revealing player ' + person.name);
person.revealed = true;
namespace.in(accessCode).emit(
globals.CLIENT_COMMANDS.REVEAL_PLAYER,
{
id: person.id,
gameRole: person.gameRole,
alignment: person.alignment
})
}
}
})
}

View File

@@ -1,5 +1,8 @@
const globals = require("../config/globals")
/* The purpose of this component is to only return the game state information that is necessary. For example, we only want to return player role information
to moderators. This avoids any possibility of a player having access to information that they shouldn't.
*/
const GameStateCurator = {
getGameStateFromPerspectiveOfPerson: (game, person, gameRunner, socket, logger) => {
return getGameStateBasedOnPermissions(game, person, gameRunner);
@@ -21,6 +24,7 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
}
switch (person.userType) {
case globals.USER_TYPES.PLAYER:
case globals.USER_TYPES.KILLED_PLAYER:
return {
accessCode: game.accessCode,
status: game.status,
@@ -32,7 +36,7 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
return person.assigned === true
&& (person.userType !== globals.USER_TYPES.MODERATOR && person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR)
})
.map((filteredPerson) => ({ name: filteredPerson.name, id: filteredPerson.id, userType: filteredPerson.userType, out: filteredPerson.out })),
.map((filteredPerson) => mapPerson(filteredPerson)),
timerParams: game.timerParams,
isFull: game.isFull,
}
@@ -75,7 +79,8 @@ function mapPeopleForModerator(people, client) {
gameRole: person.gameRole,
gameRoleDescription: person.gameRoleDescription,
alignment: person.alignment,
out: person.out
out: person.out,
revealed: person.revealed
}));
}
@@ -88,12 +93,25 @@ function mapPeopleForTempModerator(people, client) {
name: person.name,
id: person.id,
userType: person.userType,
out: person.out
out: person.out,
revealed: person.revealed
}));
}
function mapPerson(person) {
return { name: person.name, id: person.id, userType: person.userType, out: person.out };
if (person.revealed) {
return {
name: person.name,
id: person.id,
userType: person.userType,
out: person.out,
revealed: person.revealed,
gameRole: person.gameRole,
alignment: person.alignment
};
} else {
return { name: person.name, id: person.id, userType: person.userType, out: person.out, revealed: person.revealed };
}
}
module.exports = GameStateCurator;