progress on new lobby

This commit is contained in:
Alec
2021-11-16 00:22:35 -05:00
parent 909546c84e
commit d9ae7db7b9
26 changed files with 2120 additions and 87 deletions

View File

@@ -4,6 +4,26 @@ export const globals = {
PLAYER_ID_COOKIE_KEY: 'play-werewolf-anon-id',
ACCESS_CODE_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789',
COMMANDS: {
FETCH_GAME_STATE: 'fetchGameState'
FETCH_GAME_STATE: 'fetchGameState',
GET_ENVIRONMENT: 'getEnvironment'
},
GAME_STATE: {
LOBBY: 'lobby'
},
ALIGNMENT: {
GOOD: "good",
EVIL: "evil"
},
EVENTS: {
PLAYER_JOINED: "playerJoined"
},
USER_TYPES: {
MODERATOR: "moderator",
PLAYER: "player",
TEMPORARY_MODERATOR: "temporary moderator"
},
ENVIRONMENT: {
LOCAL: "local",
PRODUCTION: "production"
}
};

14
client/images/clock.svg Normal file
View File

@@ -0,0 +1,14 @@
<svg width="158" height="155" xmlns="http://www.w3.org/2000/svg">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="157" width="160" y="-1" x="-1"/>
<g display="none" id="canvasGrid">
<rect fill="none" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
</g>
</g>
<g>
<title>Layer 1</title>
<path stroke="none" id="svg_1" d="m78.750021,0.749998c-43.078721,0 -78.000016,34.473574 -78.000016,76.999988c0,42.526414 34.921295,76.999988 78.000016,76.999988c43.078721,0 78.000016,-34.473574 78.000016,-76.999988c0,-42.526414 -34.921295,-76.999988 -78.000016,-76.999988zm0,138.100913c-34.137753,0 -61.899834,-27.411445 -61.899834,-61.100926c0,-33.694779 27.762081,-61.106224 61.899834,-61.106224c34.137753,0 61.894467,27.406147 61.894467,61.106224c0,33.700077 -27.767448,61.100926 -61.894467,61.100926zm-0.005367,-118.71582c-2.9678,0 -5.361361,2.373469 -5.361361,5.297921l0,48.852132l-32.447234,14.007704c-2.715564,1.176139 -3.949911,4.301912 -2.758498,6.98266c0.880143,1.98672 2.844366,3.173455 4.910556,3.173455c0.719141,0 1.454383,-0.143044 2.152058,-0.450323l35.559936,-15.353376c0.026834,-0.010596 0.048301,-0.021192 0.069767,-0.031788l0.021467,-0.010596c0.080501,-0.031788 0.123435,-0.105958 0.198569,-0.132448c0.55814,-0.275492 1.078712,-0.598665 1.497317,-1.033095c0.182469,-0.180129 0.284437,-0.413238 0.423971,-0.619857c0.257603,-0.339067 0.542039,-0.672836 0.697675,-1.080776c0.128801,-0.317875 0.139535,-0.66224 0.203936,-1.001307c0.069767,-0.344365 0.203936,-0.646346 0.203936,-0.990711l0,-52.316972c0,-2.919155 -2.404294,-5.292623 -5.372094,-5.292623z" stroke-width="1.5" fill="#86cb92"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

210
client/images/copy.svg Normal file
View File

@@ -0,0 +1,210 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="121.00714mm"
height="144.81964mm"
viewBox="0 0 121.00714 144.81964"
version="1.1"
id="svg8"
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)"
sodipodi:docname="copy.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="powerclip"
id="path-effect951"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Use fill-rule evenodd on &lt;b&gt;fill and stroke&lt;/b&gt; dialog if no flatten result after convert clip to paths." />
<inkscape:path-effect
effect="powerclip"
id="path-effect937"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Use fill-rule evenodd on &lt;b&gt;fill and stroke&lt;/b&gt; dialog if no flatten result after convert clip to paths." />
<inkscape:path-effect
effect="powerclip"
id="path-effect923"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Use fill-rule evenodd on &lt;b&gt;fill and stroke&lt;/b&gt; dialog if no flatten result after convert clip to paths." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath913">
<g
id="g921"
style="display:none">
<path
id="path915"
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 109.61309,67.279762 h 52.91667 c 2.51278,0 4.53571,2.022929 4.53571,4.535714 0,2.512786 -2.02293,4.535715 -4.53571,4.535715 h -52.91667 c -2.51278,0 -4.53571,-2.022929 -4.53571,-4.535715 0,-2.512785 2.02293,-4.535714 4.53571,-4.535714 z" />
<path
id="path917"
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 109.12738,83.424988 h 52.91667 c 2.51278,0 4.53571,2.022928 4.53571,4.535714 0,2.512786 -2.02293,4.535714 -4.53571,4.535714 h -52.91667 c -2.51278,0 -4.53571,-2.022928 -4.53571,-4.535714 0,-2.512786 2.02293,-4.535714 4.53571,-4.535714 z" />
<path
id="path919"
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 109.12738,99.375595 h 52.91667 c 2.51278,0 4.53571,2.022925 4.53571,4.535715 0,2.51278 -2.02293,4.53571 -4.53571,4.53571 h -52.91667 c -2.51278,0 -4.53571,-2.02293 -4.53571,-4.53571 0,-2.51279 2.02293,-4.535715 4.53571,-4.535715 z" />
</g>
<path
id="lpe_path-effect923"
class="powerclip"
d="M 55.205951,21.944047 H 156.46071 V 151.16905 H 55.205951 Z m 54.407139,45.335715 c -2.51278,0 -4.53571,2.022929 -4.53571,4.535714 0,2.512786 2.02293,4.535715 4.53571,4.535715 h 52.91667 c 2.51278,0 4.53571,-2.022929 4.53571,-4.535715 0,-2.512785 -2.02293,-4.535714 -4.53571,-4.535714 z m -0.48571,16.145226 c -2.51278,0 -4.53571,2.022928 -4.53571,4.535714 0,2.512786 2.02293,4.535714 4.53571,4.535714 h 52.91667 c 2.51278,0 4.53571,-2.022928 4.53571,-4.535714 0,-2.512786 -2.02293,-4.535714 -4.53571,-4.535714 z m 0,15.950607 c -2.51278,0 -4.53571,2.022925 -4.53571,4.535715 0,2.51278 2.02293,4.53571 4.53571,4.53571 h 52.91667 c 2.51278,0 4.53571,-2.02293 4.53571,-4.53571 0,-2.51279 -2.02293,-4.535715 -4.53571,-4.535715 z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath927">
<g
id="g935"
transform="translate(0.24285867,0.02228802)"
style="display:none">
<path
id="path929"
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 109.61309,67.279762 h 52.91667 c 2.51278,0 4.53571,2.022929 4.53571,4.535714 0,2.512786 -2.02293,4.535715 -4.53571,4.535715 h -52.91667 c -2.51278,0 -4.53571,-2.022929 -4.53571,-4.535715 0,-2.512785 2.02293,-4.535714 4.53571,-4.535714 z" />
<path
id="path931"
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 109.12738,83.424988 h 52.91667 c 2.51278,0 4.53571,2.022928 4.53571,4.535714 0,2.512786 -2.02293,4.535714 -4.53571,4.535714 h -52.91667 c -2.51278,0 -4.53571,-2.022928 -4.53571,-4.535714 0,-2.512786 2.02293,-4.535714 4.53571,-4.535714 z" />
<path
id="path933"
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 109.12738,99.375595 h 52.91667 c 2.51278,0 4.53571,2.022925 4.53571,4.535715 0,2.51278 -2.02293,4.53571 -4.53571,4.53571 h -52.91667 c -2.51278,0 -4.53571,-2.02293 -4.53571,-4.53571 0,-2.51279 2.02293,-4.535715 4.53571,-4.535715 z" />
</g>
<path
id="lpe_path-effect937"
class="powerclip"
d="M 84.958331,47.538688 H 186.21309 V 176.76369 H 84.958331 Z m 24.654759,19.741074 c -2.51278,0 -4.53571,2.022929 -4.53571,4.535714 0,2.512786 2.02293,4.535715 4.53571,4.535715 h 52.91667 c 2.51278,0 4.53571,-2.022929 4.53571,-4.535715 0,-2.512785 -2.02293,-4.535714 -4.53571,-4.535714 z m -0.48571,16.145226 c -2.51278,0 -4.53571,2.022928 -4.53571,4.535714 0,2.512786 2.02293,4.535714 4.53571,4.535714 h 52.91667 c 2.51278,0 4.53571,-2.022928 4.53571,-4.535714 0,-2.512786 -2.02293,-4.535714 -4.53571,-4.535714 z m 0,15.950607 c -2.51278,0 -4.53571,2.022925 -4.53571,4.535715 0,2.51278 2.02293,4.53571 4.53571,4.53571 h 52.91667 c 2.51278,0 4.53571,-2.02293 4.53571,-4.53571 0,-2.51279 -2.02293,-4.535715 -4.53571,-4.535715 z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath941">
<g
id="g949"
style="display:none">
<rect
style="opacity:0.996;fill:whitesmoke;stroke:#a5ccd1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
id="rect943"
width="39.498516"
height="11.483695"
x="116.32217"
y="66.357109"
d="m 116.32217,66.357109 h 39.49851 v 11.483695 h -39.49851 z" />
<rect
style="opacity:0.996;fill:whitesmoke;stroke:#a5ccd1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
id="rect945"
width="39.498516"
height="11.483695"
x="115.93681"
y="82.326607"
d="m 115.93681,82.326607 h 39.49852 v 11.483695 h -39.49852 z" />
<rect
style="opacity:0.996;fill:whitesmoke;stroke:#a5ccd1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
id="rect947"
width="39.498516"
height="11.483695"
x="115.91958"
y="98.25666"
d="m 115.91958,98.25666 h 39.49851 v 11.4837 h -39.49851 z" />
</g>
<path
id="lpe_path-effect951"
class="powerclip"
d="M 55.205951,21.944047 H 156.46071 V 151.16905 H 55.205951 Z m 61.116219,44.413062 v 11.483695 h 39.49851 V 66.357109 Z m -0.38536,15.969498 v 11.483695 h 39.49852 V 82.326607 Z m -0.0172,15.930053 v 11.4837 h 39.49851 v -11.4837 z" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
inkscape:cx="229.70018"
inkscape:cy="291.6465"
inkscape:document-units="mm"
inkscape:current-layer="g862"
inkscape:document-rotation="0"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-global="true" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-60.205951,-26.944047)">
<g
id="g862">
<g
id="g870">
<path
id="rect833"
style="opacity:0.996;fill:whitesmoke;fill-opacity:0;stroke:whitesmoke;stroke-width:8.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 77.863093,30.994047 h 55.940477 c 7.53836,0 13.60714,6.068786 13.60714,13.607143 v 83.91071 c 0,7.53836 -6.06878,13.60715 -13.60714,13.60715 H 77.863093 c -7.538357,0 -13.607142,-6.06879 -13.607142,-13.60715 V 44.60119 c 0,-7.538357 6.068785,-13.607143 13.607142,-13.607143 z"
clip-path="url(#clipPath941)"
inkscape:original-d="m 77.863093,30.994047 h 55.940477 c 7.53836,0 13.60714,6.068786 13.60714,13.607143 v 83.91071 c 0,7.53836 -6.06878,13.60715 -13.60714,13.60715 H 77.863093 c -7.538357,0 -13.607142,-6.06879 -13.607142,-13.60715 V 44.60119 c 0,-7.538357 6.068785,-13.607143 13.607142,-13.607143 z"
inkscape:path-effect="#path-effect923;#path-effect951" />
<path
id="rect833-1"
style="opacity:0.996;fill:whitesmoke;fill-opacity:1;stroke:whitesmoke;stroke-width:8.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 107.61547,56.588688 h 55.94048 c 7.53836,0 13.60714,6.068785 13.60714,13.607142 v 83.91072 c 0,7.53835 -6.06878,13.60714 -13.60714,13.60714 h -55.94048 c -7.53835,0 -13.607139,-6.06879 -13.607139,-13.60714 V 70.19583 c 0,-7.538357 6.068789,-13.607142 13.607139,-13.607142 z"
clip-path="url(#clipPath927)"
inkscape:original-d="m 107.61547,56.588688 h 55.94048 c 7.53836,0 13.60714,6.068785 13.60714,13.607142 v 83.91072 c 0,7.53835 -6.06878,13.60714 -13.60714,13.60714 h -55.94048 c -7.53835,0 -13.607139,-6.06879 -13.607139,-13.60714 V 70.19583 c 0,-7.538357 6.068789,-13.607142 13.607139,-13.607142 z"
inkscape:path-effect="#path-effect937" />
<path
style="opacity:0.996;fill:#cfced2;fill-opacity:0;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 181.02138,183.03773 c -5.53501,-2.80025 -7.14286,-5.82668 -7.14286,-13.44493 0,-16.70234 2.35705,-17.05861 112.85714,-17.05861 110.5001,0 112.85715,0.35627 112.85715,17.05861 0,16.70235 -2.35705,17.05862 -112.85715,17.05862 -73.8022,0 -100.3663,-0.90805 -105.71428,-3.61369 z"
id="path953"
transform="matrix(0.26458333,0,0,0.26458333,60.205951,26.944047)" />
<path
style="opacity:0.996;fill:#cfced2;fill-opacity:0;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 174.32134,241.51069 c -5.9592,-6.58484 -6.06152,-15.89102 -0.23874,-21.7138 3.92103,-3.92103 17.98077,-4.4898 110.98575,-4.4898 99.543,0 106.80467,0.34113 111.22449,5.22497 6.28388,6.94361 5.96667,13.53689 -0.98575,20.48931 -5.5075,5.5075 -9.52381,5.71429 -110.98575,5.71429 -98.35673,0 -105.58205,-0.3432 -110,-5.22497 z"
id="path955"
transform="matrix(0.26458333,0,0,0.26458333,60.205951,26.944047)" />
<path
style="opacity:0.996;fill:#cfced2;fill-opacity:0;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 174.32134,301.51069 c -6.21551,-6.86807 -5.99825,-15.59745 0.53536,-21.51029 4.74851,-4.29734 15.71793,-4.69038 112.03415,-4.01425 l 106.77025,0.74951 4.22446,7.52791 c 3.7896,6.753 3.72048,8.29708 -0.67144,15 l -4.8959,7.47209 H 285.68405 c -99.6769,0 -106.94268,-0.3409 -111.36271,-5.22497 z"
id="path957"
transform="matrix(0.26458333,0,0,0.26458333,60.205951,26.944047)" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -3,6 +3,7 @@ import { cancelCurrentToast, toast } from "./Toast.js";
import { customCards } from "../config/customCards.js";
import { ModalManager } from "./ModalManager.js";
import {XHRUtility} from "./XHRUtility.js";
import {globals} from "../config/globals.js";
export class GameCreationStepManager {
constructor(deckManager) {
@@ -401,8 +402,9 @@ function loadCustomRoles(deckManager) {
function constructCompactDeckBuilderElement(card, deckManager) {
const cardContainer = document.createElement("div");
let alignmentClass = card.team === globals.ALIGNMENT.GOOD ? globals.ALIGNMENT.GOOD : globals.ALIGNMENT.EVIL
cardContainer.setAttribute("class", "compact-card");
cardContainer.setAttribute("class", "compact-card " + alignmentClass);
cardContainer.setAttribute("id", "card-" + card.role.replaceAll(' ', '-'));
@@ -419,6 +421,7 @@ function constructCompactDeckBuilderElement(card, deckManager) {
"</div>";
cardContainer.querySelector('.card-role').innerText = card.role;
cardContainer.title = card.role;
cardContainer.querySelector('.card-quantity').innerText = card.quantity;
if (card.quantity > 0) {
@@ -477,6 +480,8 @@ function updateCustomRoleOptionsList(deckManager, selectEl) {
function addOptionsToList(options, selectEl) {
for (let i = 0; i < options.length; i ++) {
let optionEl = document.createElement("option");
let alignmentClass = customCards[i].team === globals.ALIGNMENT.GOOD ? globals.ALIGNMENT.GOOD : globals.ALIGNMENT.EVIL
optionEl.classList.add(alignmentClass);
optionEl.setAttribute("value", customCards[i].role);
optionEl.innerText = customCards[i].role;
selectEl.appendChild(optionEl);

View File

@@ -0,0 +1,75 @@
import {globals} from "../config/globals.js";
export class GameStateRenderer {
constructor(gameState) {
this.gameState = gameState;
}
renderLobbyPlayers() {
document.querySelectorAll('.lobby-player').forEach((el) => el.remove())
let lobbyPlayersContainer = document.getElementById("lobby-players");
if (this.gameState.userType !== globals.USER_TYPES.MODERATOR) {
renderClient(this.gameState.client, lobbyPlayersContainer);
}
for (let person of this.gameState.people) {
let personEl = document.createElement("div");
personEl.innerText = person.name;
personEl.classList.add('lobby-player');
lobbyPlayersContainer.appendChild(personEl);
}
let playerCount;
if (this.gameState.userType === globals.USER_TYPES.MODERATOR) {
playerCount = this.gameState.people.length;
} else {
playerCount = 1 + this.gameState.people.length;
}
document.querySelector("label[for='lobby-players']").innerText =
"Players ( " + playerCount + " / " + getGameSize(this.gameState.deck) + " )";
}
renderLobbyHeader() {
let title = document.createElement("h1");
title.innerText = "Lobby";
document.body.prepend(title);
let gameLinkContainer = document.getElementById("game-link");
gameLinkContainer.innerText = window.location;
let copyImg = document.createElement("img");
copyImg.setAttribute("src", "../images/copy.svg");
gameLinkContainer.appendChild(copyImg);
let moderatorContainer = document.getElementById("moderator");
let text, modClass;
if (this.gameState.userType === globals.USER_TYPES.MODERATOR || this.gameState.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
moderatorContainer.innerText = this.gameState.moderator.name + " (you)";
moderatorContainer.classList.add('moderator-client');
} else {
moderatorContainer.innerText = this.gameState.moderator.name;
}
}
renderLobbyFooter() {
let gameDeckContainer = document.getElementById("game-deck");
for (let card of this.gameState.deck) {
let cardEl = document.createElement("div");
cardEl.innerText = card.quantity + 'x ' + card.role;
cardEl.classList.add('lobby-card')
}
}
}
function renderClient(client, container) {
let clientEl = document.createElement("div");
clientEl.innerText = client.name + ' (you)';
clientEl.classList.add('lobby-player');
clientEl.classList.add('lobby-player-client');
container.prepend(clientEl);
}
function getGameSize(cards) {
let quantity = 0;
for (let card of cards) {
quantity += card.quantity;
}
return quantity;
}

View File

@@ -0,0 +1,24 @@
export const templates = {
LOBBY:
"<div id='lobby-header'>" +
"<div>" +
"<label for='game-link'>Share Link</label>" +
"<div id='game-link'></div>" +
"</div>" +
"<div id='game-time'></div>" +
"<div id='game-player-count'></div>" +
"<div>" +
"<label for='moderator'>Moderator</label>" +
"<div id='moderator'></div>" +
"</div>" +
"</div>" +
"<div>" +
"<div>" +
"<label for='lobby-players'>Players</label>" +
"<div id='lobby-players'></div>" +
"</div>" +
"<div id='lobby-footer'>" +
"<div id='game-deck'></div>" +
"</div>" +
"</div>"
}

86
client/modules/Timer.js Normal file
View File

@@ -0,0 +1,86 @@
/*
A timer using setTimeout that compensates for drift. Drift can happen for several reasons:
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#reasons_for_delays
This means the timer may very well be late in executing the next call (but never early).
This timer is accurate to within a few ms for any amount of time provided. It's meant to be utilized as a Web Worker.
See: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
*/
const messageParameters = {
STOP: 'stop',
TOTAL_TIME: 'totalTime',
TICK_INTERVAL: 'tickInterval'
};
onmessage = function (e) {
if (typeof e.data === 'object'
&& e.data.hasOwnProperty(messageParameters.TOTAL_TIME)
&& e.data.hasOwnProperty(messageParameters.TICK_INTERVAL)
) {
const timer = new Singleton(e.data.totalTime, e.data.tickInterval);
timer.startTimer();
}
};
function stepFn (expected, interval, start, totalTime) {
const now = Date.now();
if (now - start >= totalTime) {
return;
}
const delta = now - expected;
expected += interval;
postMessage({ timeRemaining: (totalTime - (expected - start)) / 1000 });
Singleton.setNewTimeoutReference(setTimeout(() => {
stepFn(expected, interval, start, totalTime);
}, Math.max(0, interval - delta)
)); // take into account drift - also retain a reference to this clock tick so it can be cleared later
}
class Timer {
constructor (totalTime, tickInterval) {
this.timeoutId = undefined;
this.totalTime = totalTime;
this.tickInterval = tickInterval;
}
startTimer () {
if (!isNaN(this.totalTime) && !isNaN(this.tickInterval)) {
const interval = this.tickInterval;
const totalTime = this.totalTime;
const start = Date.now();
const expected = Date.now() + this.tickInterval;
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
stepFn(expected, interval, start, totalTime);
}, this.tickInterval);
}
}
}
class Singleton {
constructor (totalTime, tickInterval) {
if (!Singleton.instance) {
Singleton.instance = new Timer(totalTime, tickInterval);
} else {
// This allows the same timer to be configured to run for different intervals / at a different granularity.
Singleton.setNewTimerParameters(totalTime, tickInterval);
}
return Singleton.instance;
}
static setNewTimerParameters (totalTime, tickInterval) {
if (Singleton.instance) {
Singleton.instance.totalTime = totalTime;
Singleton.instance.tickInterval = tickInterval;
}
}
static setNewTimeoutReference (timeoutId) {
if (Singleton.instance) {
Singleton.instance.timeoutId = timeoutId;
}
}
}

View File

@@ -7,7 +7,7 @@ export const toast = (message, type, positionAtTop = true) => {
function buildAndInsertMessageElement (message, type, positionAtTop) {
cancelCurrentToast();
let backgroundColor;
const position = positionAtTop ? 'top:4rem;' : 'bottom: 15px;';
const position = positionAtTop ? 'top:4rem;' : 'bottom: 35px;';
switch (type) {
case 'warning':
backgroundColor = '#fff5b1';

View File

@@ -2,24 +2,41 @@ import { globals } from '../config/globals.js';
export const UserUtility = {
createNewAnonymousUserId (force = true) {
let newId;
const currentId = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
createNewAnonymousUserId (force = true, environment) {
let newId, currentId;
if (environment === globals.ENVIRONMENT.LOCAL) {
currentId = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
} else {
currentId = localStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
}
if (currentId !== null && !force) {
newId = currentId;
} else {
newId = createRandomUserId();
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
if (environment === globals.ENVIRONMENT.LOCAL) {
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
} else {
localStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
}
}
return newId;
},
setAnonymousUserId (id) {
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
setAnonymousUserId (id, environment) {
if (environment === globals.ENVIRONMENT.LOCAL) { // use sessionStorage for cookie during local development to aid in testing.
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
} else {
localStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
}
},
validateAnonUserSignature () {
const userSig = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
validateAnonUserSignature (environment) {
let userSig;
if (environment === globals.ENVIRONMENT.LOCAL) { // use sessionStorage for cookie during local development to aid in testing.
userSig = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
} else {
userSig = localStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
}
return (
userSig
&& typeof userSig === 'string'

File diff suppressed because one or more lines are too long

View File

@@ -2109,7 +2109,7 @@
module.error(error.noStorage);
return;
}
name = sessionStorage.getItem(value);
name = localStorage.getItem(value);
return (name !== undefined)
? name
: false
@@ -2153,7 +2153,7 @@
return;
}
module.verbose('Saving remote data to session storage', value, name);
sessionStorage.setItem(value, name);
localStorage.setItem(value, name);
}
},
@@ -3714,7 +3714,7 @@
minCharacters : 0, // Minimum characters required to trigger API call
filterRemoteData : false, // Whether API results should be filtered after being returned for query term
saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
saveRemoteData : true, // Whether remote name/value pairs should be stored in localStorage to allow remote data to be restored on page refresh
throttle : 200, // How long to wait after last user input to search remotely

View File

@@ -35,23 +35,3 @@ function loadCustomRoles(deckManager) {
});
deckManager.customRoleOptions = customCards;
}
function createGameForHosting(deck, hasTimer, modName, timerParams) {
XHRUtility.xhr(
'/api/games/create',
'POST',
null,
JSON.stringify(
new Game(deck, hasTimer, modName, timerParams)
)
)
.then((res) => {
if (res
&& typeof res === 'object'
&& Object.prototype.hasOwnProperty.call(res, 'content')
&& typeof res.content === 'string'
) {
window.location = ('/game/' + res.content);
}
});
}

View File

@@ -1,22 +1,49 @@
import { UserUtility } from "../modules/UserUtility.js";
import { globals } from "../config/globals.js";
import {templates} from "../modules/Templates.js";
import {GameStateRenderer} from "../modules/GameStateRenderer.js";
import {toast} from "../modules/Toast.js";
export const game = () => {
let userId = UserUtility.validateAnonUserSignature();
const splitUrl = window.location.href.split('/game/');
const accessCode = splitUrl[1];
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, userId, function (gameState) {
if (gameState === null) {
window.location.replace('/not-found');
} else {
console.log(gameState);
userId = gameState.id;
UserUtility.setAnonymousUserId(userId);
// processGameState(gameState, userId, socket);
}
});
} else {
window.location.replace('/not-found');
}
socket.emit(globals.COMMANDS.GET_ENVIRONMENT, function(environment) {
let userId = UserUtility.validateAnonUserSignature(environment);
const splitUrl = window.location.href.split('/game/');
const accessCode = splitUrl[1];
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, userId, function (gameState) {
if (gameState === null) {
window.location.replace('/not-found');
} else {
console.log(gameState);
userId = gameState.client.id;
UserUtility.setAnonymousUserId(userId, environment);
let gameStateRenderer = new GameStateRenderer(gameState);
processGameState(gameState, userId, socket, gameStateRenderer); // this socket is initialized via a script tag in the game page HTML.
setClientSocketHandlers(gameStateRenderer, socket);
}
});
} else {
window.location.replace('/not-found');
}
});
};
function processGameState (gameState, userId, socket, gameStateRenderer) {
switch (gameState.status) {
case globals.GAME_STATE.LOBBY:
document.getElementById("game-state-container").innerHTML = templates.LOBBY;
gameStateRenderer.renderLobbyPlayers();
gameStateRenderer.renderLobbyHeader();
break;
default:
break;
}
}
function setClientSocketHandlers(gameStateRenderer, socket) {
socket.on(globals.EVENTS.PLAYER_JOINED, (player) => {
toast(player.name + " joined!", "success", false);
gameStateRenderer.gameState.people.push(player);
gameStateRenderer.renderLobbyPlayers();
})
}

View File

@@ -39,7 +39,7 @@ h1 {
font-family: 'diavlo', sans-serif;
color: #ab2626;
filter: drop-shadow(2px 2px 4px black);
font-size: 40px;
font-size: 50px;
margin: 0.5em 0;
}
@@ -209,6 +209,14 @@ input {
margin: 0 0 0.5em 0;
}
.good, .compact-card.good .card-role {
color: #4b6bfa !important;
}
.evil, .compact-card.evil .card-role {
color: #e73333 !important
}
@keyframes placeholder {
0%{
background-position: 50% 0

View File

@@ -33,13 +33,17 @@
}
.selected-card {
border: 2px solid #0075F2;
border: 2px solid #c5c5c5;
}
.search {
color: black;
}
.card-role {
font-weight: bold;
}
.compact-card-right p {
font-size: 40px;
margin: 0 10px 0 0;
@@ -93,6 +97,8 @@
display: flex;
justify-content: center;
align-items: center;
width: fit-content;
margin: 0 auto;
}
#deck {
@@ -133,10 +139,11 @@ select {
#game-time label, #game-time input {
margin-right: 10px;
font-size: 25px;
}
label[for="game-time"], label[for="add-card-to-deck-form"], label[for="deck"] {
color: #0075F2;
color: whitesmoke;
font-size: 20px;
border-radius: 3px;
margin-bottom: 10px;
@@ -145,6 +152,7 @@ label[for="game-time"], label[for="add-card-to-deck-form"], label[for="deck"] {
input[type="number"] {
min-width: 3em;
font-size: 40px;
}
#add-card-to-deck-form {
@@ -232,16 +240,16 @@ input[type="number"] {
}
#step-1 div.option-selected {
border: 2px solid #0075F2;
background-color: #252730;
border: 2px solid whitesmoke;
background-color: #3a3c46;
}
#step-1 div > strong {
color: #0075F2;
color: #00a718;
}
#step-1 div:hover {
border: 2px solid #0075F2;
border: 2px solid whitesmoke;
}
.review-option {

View File

@@ -0,0 +1,82 @@
.lobby-player, #moderator {
background-color: black;
color: whitesmoke;
padding: 10px;
border-radius: 3px;
font-size: 17px;
width: fit-content;
min-width: 15em;
border: 2px solid transparent;
margin: 0.5em 0;
}
.lobby-player-client {
border: 2px solid #21ba45;
}
#moderator.moderator-client {
border: 2px solid lightgray;
}
#game-state-container {
justify-content: center;
flex-direction: row;
flex-wrap: wrap;
}
#lobby-header {
margin-bottom: 1em;
}
h1 {
text-align: center;
margin: 0.5em auto;
}
#game-state-container > div {
margin: 1em;
}
#game-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 2px solid transparent;
cursor: pointer;
margin-top: 10px;
padding: 5px;
border-radius: 3px;
background-color: #1f1f1f;
color: whitesmoke;
align-items: center;
display: flex;
}
#game-player-count {
color: whitesmoke;
margin: 0.5em 0;
font-size: 25px;
}
#game-link img {
width: 20px;
margin-left: 0.5em;
}
#game-link:hover {
border: 2px solid #0075F2;
}
label[for='moderator'] {
font-family: 'diavlo', sans-serif;
color: lightgray;
filter: drop-shadow(2px 2px 4px black);
font-size: 30px;
}
label[for='lobby-players'] {
font-family: 'diavlo', sans-serif;
color: #21ba45;
filter: drop-shadow(2px 2px 4px black);
font-size: 30px;
}

View File

@@ -12,7 +12,7 @@
align-items: center;
justify-content: center;
max-width: 17em;
max-height: 20em;
max-height: 24em;
font-family: sans-serif;
font-size: 22px;
flex-direction: column;

View File

@@ -36,6 +36,13 @@
<label for="role-name">Role Name</label>
<input id="role-name" type="text" placeholder="Name your role..." required/>
</div>
<div>
<label for="role-alignment">Role Alignment</label>
<select id="role-alignment" required>
<option value="good">Good</option>
<option value="evil">Evil</option>
</select>
</div>
<div>
<label for="role-description">Description</label>
<textarea style="resize:none" id="role-description" rows="10" cols="30" placeholder="Describe your role..." required></textarea>

View File

@@ -18,6 +18,7 @@
<link rel="stylesheet" href="../styles/modal.css">
</head>
<body>
<div class="container" id="game-state-container"></div>
<script type="module">
import { game } from "../scripts/game.js";
game();

View File

@@ -3,19 +3,27 @@ const globals = {
ACCESS_CODE_LENGTH: 6,
CLIENT_COMMANDS: {
FETCH_GAME_STATE: 'fetchGameState',
TOGGLE_READY: 'toggleReady',
PROCESS_GUESS: 'processGuess'
GET_ENVIRONMENT: 'getEnvironment'
},
STATUS: {
LOBBY: "lobby"
LOBBY: "lobby",
IN_PROGRESS: "in progress"
},
USER_SIGNATURE_LENGTH: 25,
USER_TYPES: {
MODERATOR: "moderator",
PLAYER: "player"
PLAYER: "player",
TEMPORARY_MODERATOR: "temporary moderator"
},
ERROR_MESSAGE: {
GAME_IS_FULL: "This game is full"
},
EVENTS: {
PLAYER_JOINED: "playerJoined"
},
ENVIRONMENT: {
LOCAL: "local",
PRODUCTION: "production"
}
};

View File

@@ -5,9 +5,9 @@ const path = require('path');
const fs = require('fs');
const socketIO = require('socket.io');
const app = express();
let main;
const bodyParser = require('body-parser');
const GameManager = require('./modules/GameManager.js');
const globals = require('./config/globals');
// const QueueManager = require('./modules/managers/QueueManager');
app.use(bodyParser.json()); // to support JSON-encoded bodies
@@ -15,6 +15,9 @@ app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
let main, environment;
const debugMode = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('debug');
const localServer = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('local');
const useHttps = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('https');
@@ -31,6 +34,7 @@ const port = process.env.PORT || Array
const logger = require('./modules/Logger')(debugMode);
if (localServer) {
environment = globals.ENVIRONMENT.LOCAL;
logger.log('starting main in LOCAL mode.');
if (useHttps && fs.existsSync(path.join(__dirname, './certs/localhost-key.pem')) && fs.existsSync(path.join(__dirname, './certs/localhost.pem'))) {
const key = fs.readFileSync(path.join(__dirname, './certs/localhost-key.pem'), 'utf-8');
@@ -44,7 +48,8 @@ if (localServer) {
logger.log(`navigate to http://localhost:${port}`);
}
} else {
logger.log('starting main in PRODUCTION mode. WARNING: This should not be used for local development.');
environment = globals.ENVIRONMENT.PRODUCTION;
logger.warn('starting main in PRODUCTION mode. This should not be used for local development.');
main = http.createServer(app);
const secure = require('express-force-https');
app.use(secure);
@@ -58,7 +63,7 @@ const inGame = io.of('/in-game');
/* Instantiate the singleton game manager */
const gameManager = new GameManager(logger).getInstance();
const gameManager = new GameManager(logger, environment).getInstance();
/* Instantiate the singleton queue manager */
//const queueManager = new QueueManager(matchmaking, logger).getInstance();

View File

@@ -1,6 +1,7 @@
class Game {
constructor(status, people, deck, hasTimer, timerParams=null) {
constructor(status, people, deck, hasTimer, moderator, timerParams=null) {
this.status = status;
this.moderator = moderator;
this.people = people;
this.deck = deck;
this.hasTimer = hasTimer;

View File

@@ -2,10 +2,13 @@ const globals = require('../config/globals');
const ActiveGameRunner = require('./ActiveGameRunner');
const Game = require('../model/Game');
const Person = require('../model/Person');
const GameStateCurator = require('./GameStateCurator');
const UsernameGenerator = require('./UsernameGenerator');
class GameManager {
constructor (logger) {
constructor (logger, environment) {
this.logger = logger;
this.environment = environment;
this.activeGameRunner = new ActiveGameRunner().getInstance();
this.namespace = null;
//this.gameSocketUtility = GameSocketUtility;
@@ -14,8 +17,20 @@ class GameManager {
addGameSocketHandlers = (namespace, socket) => {
this.namespace = namespace;
socket.on(globals.CLIENT_COMMANDS.FETCH_GAME_STATE, (accessCode, personId, ackFn) => {
handleRequestForGameState(this.namespace, this.logger, this.activeGameRunner, accessCode, personId, ackFn, socket);
handleRequestForGameState(
this.namespace,
this.logger,
this.activeGameRunner,
accessCode,
personId,
ackFn,
socket
);
});
socket.on(globals.CLIENT_COMMANDS.GET_ENVIRONMENT, (ackFn) => {
ackFn(this.environment);
})
}
@@ -26,13 +41,18 @@ class GameManager {
return Promise.reject('Tried to create game with invalid options: ' + gameParams);
} else {
const newAccessCode = this.generateAccessCode();
let moderator = initializeModerator(gameParams.moderatorName, gameParams.hasDedicatedModerator);
this.activeGameRunner.activeGames[newAccessCode] = new Game(
globals.STATUS.LOBBY,
initializePeopleForGame(gameParams.moderatorName, gameParams.deck),
initializePeopleForGame(gameParams.deck),
gameParams.deck,
gameParams.hasTimer,
moderator,
gameParams.timerParams
);
if (!gameParams.hasDedicatedModerator) {
this.activeGameRunner.activeGames[newAccessCode].people.push(moderator);
}
return Promise.resolve(newAccessCode);
}
}
@@ -69,14 +89,16 @@ function getRandomInt (max) {
return Math.floor(Math.random() * Math.floor(max));
}
function initializeModerator(name) {
return new Person(createRandomUserId(), name, globals.USER_TYPES.MODERATOR)
function initializeModerator(name, hasDedicatedModerator) {
const userType = hasDedicatedModerator
? globals.USER_TYPES.MODERATOR
: globals.USER_TYPES.TEMPORARY_MODERATOR;
return new Person(createRandomUserId(), name, userType)
}
function initializePeopleForGame(modName, uniqueCards) {
function initializePeopleForGame(uniqueCards) {
let people = [];
let cards = []; // this will contain copies of each card equal to the quantity.
people.push(initializeModerator(modName));
let numberOfRoles = 0;
for (let card of uniqueCards) {
for (let i = 0; i < card.quantity; i ++) {
@@ -88,7 +110,7 @@ function initializePeopleForGame(modName, uniqueCards) {
cards = shuffleArray(cards); // The deck should probably be shuffled, ey?.
for(let j = 0; j < numberOfRoles; j ++) {
people.push(new Person(createRandomUserId(), null, globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description))
people.push(new Person(createRandomUserId(), UsernameGenerator.generate(), globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description))
}
return people;
@@ -113,10 +135,10 @@ function createRandomUserId () {
}
class Singleton {
constructor (logger) {
constructor (logger, environment) {
if (!Singleton.instance) {
logger.log('CREATING SINGLETON GAME MANAGER');
Singleton.instance = new GameManager(logger);
Singleton.instance = new GameManager(logger, environment);
}
}
@@ -137,16 +159,19 @@ function handleRequestForGameState(namespace, logger, gameRunner, accessCode, pe
const game = gameRunner.activeGames[accessCode];
if (game) {
let matchingPerson = game.people.find((person) => person.id === personId);
if (!matchingPerson && game.moderator.id === personId) {
matchingPerson = game.moderator;
}
if (matchingPerson) {
if (matchingPerson.socketId === socket.id) {
logger.debug("matching person found with an established connection to the room: " + matchingPerson.name);
ackFn(getGameStateFromPerspectiveOfPerson(game, matchingPerson));
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson));
} else {
if (!roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode)) {
logger.debug("matching person found with a new connection to the room: " + matchingPerson.name);
socket.join(accessCode);
matchingPerson.socketId = socket.id;
ackFn(getGameStateFromPerspectiveOfPerson(game, matchingPerson));
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson));
} else {
rejectClientRequestForGameState(ackFn);
}
@@ -155,15 +180,21 @@ function handleRequestForGameState(namespace, logger, gameRunner, accessCode, pe
let personWithMatchingSocketId = findPersonWithMatchingSocketId(game.people, socket.id);
if (personWithMatchingSocketId) {
logger.debug("matching person found whose cookie got cleared after establishing a connection to the room: " + personWithMatchingSocketId.name);
ackFn(getGameStateFromPerspectiveOfPerson(game, personWithMatchingSocketId));
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, personWithMatchingSocketId));
} else {
let unassignedPerson = game.people.find((person) => person.assigned === false);
let unassignedPerson = game.moderator.assigned === false
? game.moderator
: game.people.find((person) => person.assigned === false);
if (unassignedPerson) {
logger.debug("completely new person with a first connection to the room: " + unassignedPerson.name);
socket.join(accessCode);
unassignedPerson.assigned = true;
unassignedPerson.socketId = socket.id;
ackFn(getGameStateFromPerspectiveOfPerson(game, unassignedPerson));
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, unassignedPerson));
socket.to(accessCode).emit(
globals.EVENTS.PLAYER_JOINED,
{ name: unassignedPerson.name }
);
} else {
rejectClientRequestForGameState(ackFn);
}
@@ -174,10 +205,6 @@ function handleRequestForGameState(namespace, logger, gameRunner, accessCode, pe
}
}
function getGameStateFromPerspectiveOfPerson(game, person) {
return person;
}
// in socket.io 2.x , the rooms property is an object. in 3.x and 4.x, it is a javascript Set.
function roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode) {
return namespace.adapter

View File

@@ -0,0 +1,84 @@
const globals = require("../config/globals")
const GameStateCurator = {
getGameStateFromPerspectiveOfPerson: (game, person) => {
return getGameStateBasedOnPermissions(game, person);
}
}
function getGameStateBasedOnPermissions(game, person) {
let client = game.status === globals.STATUS.LOBBY // people won't be able to know their role until past the lobby stage.
? { name: person.name, id: person.id }
: {
name: person.name,
id: person.id,
gameRole: person.gameRole,
roleDescription: person.roleDescription
}
switch (person.userType) {
case globals.USER_TYPES.PLAYER:
return {
status: game.status,
moderator: mapPerson(game.moderator),
userType: globals.USER_TYPES.PLAYER,
client: client,
deck: game.deck,
people: game.people
.filter((person) => {
return person.assigned === true && person.id !== client.id && person.userType !== globals.USER_TYPES.MODERATOR
})
.map((filteredPerson) => ({ name: filteredPerson.name, userType: filteredPerson.userType })),
timerParams: game.timerParams
}
case globals.USER_TYPES.MODERATOR:
return {
status: game.status,
moderator: mapPerson(game.moderator),
userType: globals.USER_TYPES.MODERATOR,
client: client,
deck: game.deck,
people: mapPeopleForModerator(game.people, client),
timerParams: game.timerParams
}
case globals.USER_TYPES.TEMPORARY_MODERATOR:
return {
status: game.status,
moderator: mapPerson(game.moderator),
userType: globals.USER_TYPES.TEMPORARY_MODERATOR,
client: client,
deck: game.deck,
people: mapPeopleForTempModerator(game.people, client),
timerParams: game.timerParams
}
default:
break;
}
}
function mapPeopleForModerator(people, client) {
return people
.filter((person) => {
return person.assigned === true && person.id !== client.id
})
.map((person) => ({
name: person.name,
gameRole: person.gameRole,
gameRoleDescription: person.gameRoleDescription
}));
}
function mapPeopleForTempModerator(people, client) {
return people
.filter((person) => {
return person.assigned === true && person.id !== client.id
})
.map((person) => ({
name: person.name,
}));
}
function mapPerson(person) {
return { name: person.name };
}
module.exports = GameStateCurator;

View File

@@ -15,6 +15,12 @@ module.exports = function (debugMode = false) {
if (!debugMode) return;
const now = new Date();
console.error('ERROR ', now.toGMTString(), ': ', message);
},
warn (message = '') {
if (!debugMode) return;
const now = new Date();
console.error('WARNING ', now.toGMTString(), ': ', message);
}
};
};

File diff suppressed because it is too large Load Diff