change spectator option to checkbox, update tutorial images
@@ -3,7 +3,7 @@ export const globals = {
|
||||
USER_SIGNATURE_LENGTH: 25,
|
||||
CLOCK_TICK_INTERVAL_MILLIS: 100,
|
||||
MAX_CUSTOM_ROLE_NAME_LENGTH: 50,
|
||||
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 500,
|
||||
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 1000,
|
||||
TOAST_DURATION_DEFAULT: 6,
|
||||
ACCESS_CODE_LENGTH: 4,
|
||||
PLAYER_ID_COOKIE_KEY: 'play-werewolf-anon-id',
|
||||
|
||||
|
After Width: | Height: | Size: 61 KiB |
BIN
client/src/images/screenshots/dedicated_mod_lobby_mobile.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 365 KiB |
|
Before Width: | Height: | Size: 68 KiB |
BIN
client/src/images/screenshots/player_inprogress_mobile.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
client/src/images/screenshots/temp_mod_inprogress_mobile.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
client/src/images/tutorial/mod-transfer.gif
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 927 KiB |
@@ -209,7 +209,10 @@ export class InProgress {
|
||||
|
||||
this.socket.on(globals.EVENT_IDS.NEW_SPECTATOR, (spectator) => {
|
||||
stateBucket.currentGameState.spectators.push(spectator);
|
||||
this.displayAvailableModerators();
|
||||
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|
||||
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
||||
this.displayAvailableModerators();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.stateBucket.currentGameState.timerParams) {
|
||||
|
||||
@@ -42,13 +42,9 @@ const joinHandler = (e) => {
|
||||
|
||||
function sendJoinRequest (e, name, accessCode) {
|
||||
document.getElementById('join-game-form').onsubmit = null;
|
||||
if (e.submitter.getAttribute('id') === 'submit-join-as-player') {
|
||||
document.getElementById('submit-join-as-player').classList.add('submitted');
|
||||
document.getElementById('submit-join-as-player').setAttribute('value', '...');
|
||||
} else {
|
||||
document.getElementById('submit-join-as-spectator').classList.add('submitted');
|
||||
document.getElementById('submit-join-as-spectator').setAttribute('value', '...');
|
||||
}
|
||||
document.getElementById('join-submit').classList.add('submitted');
|
||||
document.getElementById('join-submit').setAttribute('value', '...');
|
||||
|
||||
return XHRUtility.xhr(
|
||||
'/api/games/' + accessCode + '/players',
|
||||
'PATCH',
|
||||
@@ -58,7 +54,7 @@ function sendJoinRequest (e, name, accessCode) {
|
||||
accessCode: accessCode,
|
||||
sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
|
||||
localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION),
|
||||
joinAsSpectator: e.submitter.getAttribute('id') === 'submit-join-as-spectator'
|
||||
joinAsSpectator: document.getElementById('join-as-spectator').checked
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -66,11 +62,8 @@ function sendJoinRequest (e, name, accessCode) {
|
||||
function handleJoinError (e, res, joinHandler) {
|
||||
document.getElementById('join-game-form').onsubmit = joinHandler;
|
||||
e.submitter.classList.remove('submitted');
|
||||
if (e.submitter.getAttribute('id') === 'submit-join-as-player') {
|
||||
e.submitter.setAttribute('value', 'Join');
|
||||
} else {
|
||||
e.submitter.setAttribute('value', 'Spectate');
|
||||
}
|
||||
e.submitter.setAttribute('value', 'Join');
|
||||
|
||||
if (res.status === 404) {
|
||||
toast('This game was not found.', 'error', true, true, 'long');
|
||||
} else if (res.status === 400) {
|
||||
|
||||
@@ -34,7 +34,7 @@ body {
|
||||
|
||||
.bmc-btn {
|
||||
height: 40px !important;
|
||||
border-radius: 3px !important;
|
||||
border-radius: 5px !important;
|
||||
font-size: 18px !important;
|
||||
min-width: 180px !important;
|
||||
padding: 0 17px !important;
|
||||
@@ -202,7 +202,7 @@ label {
|
||||
input, textarea {
|
||||
background-color: transparent;
|
||||
border: 1px solid white;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
color: #d7d7d7;
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ button {
|
||||
font-family: 'signika-negative', sans-serif !important;
|
||||
padding: 10px;
|
||||
background-color: #13762b;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
color: #e7e7e7;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
@@ -259,7 +259,7 @@ button {
|
||||
|
||||
.container {
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@@ -298,7 +298,7 @@ button {
|
||||
#how-to-use-container img {
|
||||
max-width: 98%;
|
||||
border: 1px solid #57566a;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
width: 45em;
|
||||
margin: 0 auto;
|
||||
/* justify-self: center; */
|
||||
@@ -312,7 +312,7 @@ button {
|
||||
background-color: #1e1b26;
|
||||
width: fit-content;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
input {
|
||||
@@ -327,7 +327,7 @@ input {
|
||||
position: fixed;
|
||||
z-index: 1000000;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
font-family: 'signika-negative', sans-serif;
|
||||
font-weight: 100;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,0.11),
|
||||
@@ -400,7 +400,7 @@ input {
|
||||
}
|
||||
|
||||
.tutorial-image-small-portrait {
|
||||
width: 20em !important;
|
||||
width: 16em !important;
|
||||
}
|
||||
|
||||
#desktop-links > a:nth-child(1), #mobile-links a:nth-child(1) {
|
||||
@@ -536,7 +536,7 @@ input {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 20px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
opacity: 0.15;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@@ -786,7 +786,7 @@ input {
|
||||
display:inline-flex !important;
|
||||
align-items: center !important;
|
||||
color:#ffffff !important;
|
||||
background-color:#2d2c3a !important;
|
||||
background-color:#3b3a4a !important;
|
||||
border-radius: 5px !important;
|
||||
border: 1px solid transparent !important;
|
||||
padding: 7px 15px 7px 10px !important;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#confirmation {
|
||||
border-radius: 2px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
width: 85%;
|
||||
@@ -7,7 +7,7 @@
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #292834;
|
||||
background-color: #2d2c38;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 25em;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
background-color: #191920;
|
||||
color: gray;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4);
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
height: 55px;
|
||||
@@ -106,9 +106,9 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: #0f0f10;
|
||||
border: 2px solid #2d2c3a;
|
||||
border: 2px solid #3b3a4a;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -144,7 +144,7 @@
|
||||
}
|
||||
|
||||
.deck-role {
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
margin: 0.25em 0;
|
||||
padding: 0 5px;
|
||||
font-size: 18px;
|
||||
@@ -158,8 +158,8 @@
|
||||
margin: 1em 0.5em;
|
||||
background-color: #191920;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
border: 2px solid #2d2c3a;
|
||||
border-radius: 5px;
|
||||
border: 2px solid #3b3a4a;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -173,10 +173,10 @@
|
||||
|
||||
#deck-count {
|
||||
font-size: 30px;
|
||||
background-color: #2d2c3a;
|
||||
background-color: #3b3a4a;
|
||||
width: fit-content;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#deck-status-header {
|
||||
@@ -236,8 +236,8 @@
|
||||
z-index: 25;
|
||||
top: 38px;
|
||||
right: 29px;
|
||||
background-color: #2d2c3a;
|
||||
border-radius: 3px;
|
||||
background-color: #3b3a4a;
|
||||
border-radius: 5px;
|
||||
box-shadow: -3px -3px 6px rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #2d2c3a;
|
||||
background-color: #3b3a4a;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -259,7 +259,7 @@
|
||||
|
||||
#deck-good, #deck-evil {
|
||||
padding: 0;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
margin: 0.5em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -288,7 +288,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ select {
|
||||
font-family: 'signika-negative', sans-serif;
|
||||
background-color: transparent;
|
||||
color: #d7d7d7;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
min-width: 10em;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -349,7 +349,7 @@ select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
width: fit-content;
|
||||
margin: 1em 0;
|
||||
}
|
||||
@@ -362,8 +362,8 @@ select {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: #191920;
|
||||
border: 2px solid #2d2c3a;
|
||||
border-radius: 3px;
|
||||
border: 2px solid #3b3a4a;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.step {
|
||||
@@ -400,7 +400,7 @@ select {
|
||||
label[for="game-time"], label[for="add-card-to-deck-form"], label[for="deck"] {
|
||||
color: #e7e7e7;
|
||||
font-size: 20px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -440,7 +440,7 @@ input[type="number"] {
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
margin: 0.25em 0;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid transparent;
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -466,7 +466,7 @@ input[type="number"] {
|
||||
margin: 0 8px;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
@@ -593,8 +593,8 @@ input[type="number"] {
|
||||
max-width: 20em;
|
||||
margin: 0.5em;
|
||||
cursor: pointer;
|
||||
border: 2px solid #2d2c3a;
|
||||
border-radius: 3px;
|
||||
border: 2px solid #3b3a4a;
|
||||
border-radius: 5px;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
@@ -613,12 +613,12 @@ input[type="number"] {
|
||||
|
||||
.review-option {
|
||||
background-color: #191920;
|
||||
border: 2px solid #2d2c3a;
|
||||
border: 2px solid #3b3a4a;
|
||||
color: #e7e7e7;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
width: fit-content;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
margin: 0.5em 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 17px;
|
||||
width: 17em;
|
||||
width: 16em;
|
||||
border: 2px solid transparent;
|
||||
margin: 0 auto 0.25em auto;
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
max-height: 30em;
|
||||
overflow-x: hidden;
|
||||
padding: 0 25px 0 10px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#lobby-people-container label {
|
||||
@@ -33,7 +33,7 @@
|
||||
color: #b1afcd;
|
||||
text-decoration: underline;
|
||||
font-size: 17px;
|
||||
margin: 5px 0;
|
||||
margin: 5px 0 10px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ h1 {
|
||||
}
|
||||
|
||||
#end-of-game-header h2 {
|
||||
border: 1px solid #2d2c3a;
|
||||
border: 1px solid #3b3a4a;
|
||||
border-radius: 5px;
|
||||
background-color: #1a1726;
|
||||
padding: 7px;
|
||||
@@ -161,9 +161,9 @@ h1 {
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
padding: 7px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
background-color: #121314;
|
||||
border: 2px solid #2d2c3a;
|
||||
border: 2px solid #3b3a4a;
|
||||
color: #e7e7e7;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -232,7 +232,7 @@ h1 {
|
||||
|
||||
#game-role-info-container .role-info-name {
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
font-size: 20px;
|
||||
font-family: signika-negative, sans-serif;
|
||||
margin: 0.5em 0;
|
||||
@@ -265,7 +265,7 @@ h1 {
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
max-width: 17em;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
height: 23em;
|
||||
margin: 10px 20px;
|
||||
width: 100%;
|
||||
@@ -308,7 +308,7 @@ h1 {
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
max-width: 17em;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
height: 23em;
|
||||
margin: 10px 20px;
|
||||
width: 100%;
|
||||
@@ -341,7 +341,7 @@ h1 {
|
||||
margin-top: 5px;
|
||||
background-color: #262626;
|
||||
color: #e7e7e7;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
font-size: 35px;
|
||||
text-shadow: 0 3px 4px rgb(0 0 0 / 85%);
|
||||
border: 1px solid #747474;
|
||||
@@ -416,8 +416,8 @@ h1 {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #2d2c3a;
|
||||
border-radius: 3px;
|
||||
background-color: #3b3a4a;
|
||||
border-radius: 5px;
|
||||
min-width: 15em;
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ h1 {
|
||||
font-family: 'signika-negative', sans-serif;
|
||||
font-size: 25px;
|
||||
background-color: #171522;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
padding: 0 5px;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4);
|
||||
@@ -460,7 +460,7 @@ label[for='moderator'] {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
/* width: fit-content; */
|
||||
font-size: 20px;
|
||||
height: 100px;
|
||||
@@ -483,7 +483,7 @@ label[for='moderator'] {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
/* width: fit-content; */
|
||||
font-size: 20px;
|
||||
height: 100px;
|
||||
@@ -516,7 +516,7 @@ label[for='moderator'] {
|
||||
#start-game-button, #end-game-button, #restart-game-button {
|
||||
font-family: 'signika-negative', sans-serif !important;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
color: #e7e7e7;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
@@ -600,12 +600,12 @@ label[for='moderator'] {
|
||||
justify-content: center;
|
||||
margin-bottom: 1em;
|
||||
padding: 0.5em;
|
||||
border-radius: 3px;
|
||||
background-color: #2d2c3a;
|
||||
border-radius: 5px;
|
||||
background-color: #3b3a4a;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ canvas {
|
||||
.kill-player-button, .reveal-role-button {
|
||||
background-color: #434156;
|
||||
font-family: 'signika-negative', sans-serif !important;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
color: #e7e7e7;
|
||||
height: 25px;
|
||||
font-size: 16px;
|
||||
@@ -670,7 +670,7 @@ canvas {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
color: #767676;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
@@ -721,7 +721,7 @@ canvas {
|
||||
/* font-size: 18px;*/
|
||||
/* padding: 10px;*/
|
||||
/* border: 2px transparent;*/
|
||||
/* border-radius: 3px;*/
|
||||
/* border-radius: 5px;*/
|
||||
/* color: #d7d7d7;*/
|
||||
/* font-family: signika-negative, sans-serif;*/
|
||||
/*}*/
|
||||
@@ -744,8 +744,8 @@ canvas {
|
||||
}
|
||||
|
||||
#game-parameters {
|
||||
background-color: #2d2c3a;
|
||||
border-radius: 3px;
|
||||
background-color: #3b3a4a;
|
||||
border-radius: 5px;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
@@ -789,9 +789,9 @@ canvas {
|
||||
|
||||
#lobby-people-container , #game-people-container {
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
min-height: 25em;
|
||||
background-color: #292834;
|
||||
background-color: #3b3a4a;
|
||||
max-width: 35em;
|
||||
min-width: 17em;
|
||||
margin-top: 1em;
|
||||
|
||||
@@ -68,7 +68,7 @@ form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 10px 0;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ form > div {
|
||||
max-width: 90%;
|
||||
background-color: #0f0f10;
|
||||
padding: 0.5em;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
transform-origin: center;
|
||||
display: block;
|
||||
z-index: 1 !important;
|
||||
background-color: #2d2c38;
|
||||
}
|
||||
|
||||
#join-game-form .modal-button-container {
|
||||
@@ -11,18 +12,27 @@
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
#join-game-form .modal-button-container input {
|
||||
#join-game-form .modal-button-container #join-submit {
|
||||
width: 5em;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
#join-game-form .modal-button-container #submit-join-as-spectator {
|
||||
background-color: #045EA6;
|
||||
.modal-button-container > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background-color: #1e1b26;
|
||||
}
|
||||
|
||||
#join-game-form .modal-button-container #submit-join-as-spectator:hover {
|
||||
background-color: #0078D773;
|
||||
border: 2px solid #045EA6;
|
||||
.modal-button-container > div > label {
|
||||
margin: 0 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-button-container > div > input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#player-new-name {
|
||||
@@ -39,12 +49,10 @@
|
||||
|
||||
@keyframes entrance {
|
||||
0% {
|
||||
transform: translate(-50%, calc(-50% + 40px));
|
||||
opacity: 0;
|
||||
}
|
||||
95% {
|
||||
90% {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.modal {
|
||||
border-radius: 2px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
width: 85%;
|
||||
@@ -7,7 +7,7 @@
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #292834;
|
||||
background-color: #2d2c38;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 25em;
|
||||
@@ -63,7 +63,7 @@
|
||||
}
|
||||
|
||||
#custom-role-info-modal-description {
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
background-color: black;
|
||||
max-height: 10em;
|
||||
overflow: auto;
|
||||
@@ -81,7 +81,7 @@
|
||||
}
|
||||
|
||||
#custom-role-info-modal-alignment {
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
background-color: black;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta name="description" content="This resource was not found.">
|
||||
<meta property="og:title" content="Not Found">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://playwerewolf.uk.r.appspot.com/not-found">
|
||||
<meta property="og:url" content="https://play-werewolf.app/not-found">
|
||||
<meta property="og:description" content="The page you are looking for could not be found.">
|
||||
<meta property="og:image" content="image.png">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Create A Game</title>
|
||||
<meta name="description" content="Create a game of Werewolf using your custom set of roles.">
|
||||
<meta property="og:title" content="Werewolf Utility - Create A Game">
|
||||
<meta property="og:title" content="Create A Game">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://playwerewolf.uk.r.appspot.com/create">
|
||||
<meta property="og:url" content="https://play-werewolf.app/create">
|
||||
<meta property="og:description" content="Create a game of Werewolf using your custom set of roles.">
|
||||
<meta property="og:image" content="image.png">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Active Game</title>
|
||||
<meta name="description" content="View this game of Werewolf">
|
||||
<meta property="og:title" content="Play Werwolf (Mafia) - Active Game">
|
||||
<meta property="og:title" content="Active Game">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:description" content="Spectate this game of Werewolf">
|
||||
<meta property="og:image" content="image.png">
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Create A Game</title>
|
||||
<title>How to Use</title>
|
||||
<meta name="description" content="How to use this app.">
|
||||
<meta property="og:title" content="How to use">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://playwerewolf.uk.r.appspot.com/how-to-use">
|
||||
<meta property="og:url" content="https://play-werewolf.app/how-to-use">
|
||||
<meta property="og:description" content="How to use this app.">
|
||||
<meta property="og:image" content="image.png">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
@@ -55,22 +55,20 @@
|
||||
<h3>Step One: Choosing method of moderation</h3>
|
||||
<br>
|
||||
You have two options for moderation during the game. If the moderator isn't playing, you can choose the <span class="emphasized">dedicated
|
||||
moderator</span> option. Dedicated Moderators are <span class="emphasized">not dealt into the game</span>. Once they start the game, they will know
|
||||
moderator</span> option. Dedicated Moderators are not dealt into the game. Once they start the game, they will know
|
||||
everyone's role. At that point, they can kill players, reveal players' roles to everyone else, transfer their
|
||||
moderator powers, play/pause the timer (if there is one), and end the game when team good or evil wins.
|
||||
<br><br>
|
||||
Similarly, you can also choose the <span class="emphasized">temporary moderator</span> option. The key differences
|
||||
here are that you are <span class="emphasized">dealt a role</span>. You have the same powers as the dedicated
|
||||
moderator, with the exception of game knowledge - you know the same information that a regular player does.
|
||||
When you remove the first player from the game, your powers will be automatically transferred to them - they become
|
||||
the dedicated moderator, and you become a regular player.
|
||||
Similarly, you can also choose the <span class="emphasized">temporary moderator</span> option. You are dealt a role,
|
||||
and you have the same powers as the dedicated moderator, with the exception of game knowledge - you know the same
|
||||
information that a regular player does. When you remove the first player from the game (which can be yourself),
|
||||
they will automatically become the dedicated moderator, and the temporary moderator--if they are a different person--will become
|
||||
a killed player.
|
||||
<br><br>
|
||||
<span class="emphasized">Dedicated moderators</span> can <span class="emphasized">transfer their moderator powers</span>
|
||||
to a player that is out, or to a spectator. That way, if the current dedicated moderator has to leave, or simply
|
||||
does not want to moderate anymore, they can easily delegate.
|
||||
<br><br>
|
||||
<img src="../images/tutorial/moderation-option.png" class="tutorial-image-small" alt="moderation-option"/>
|
||||
<br><br>
|
||||
<h3>Step Two: Build your deck</h3>
|
||||
<br>
|
||||
There is a role box on this page that includes a list of <span class="emphasized">default roles</span> and a list of <span class="emphasized">custom roles</span>, which can be
|
||||
@@ -92,35 +90,40 @@
|
||||
<br>
|
||||
If you don't fill in these fields, the game will be untimed. If you do, <span class="emphasized">you can use a time between 1 minute
|
||||
and 5 hours.</span> The timer can be played and paused by the current moderator. Importantly, when the timer expires,
|
||||
<span class="emphasized">nothing automatically happens.</span> The timer will display 0s, but the game will not
|
||||
end. Whether or not the game ends immediately after that or continues longer is up to the moderator.
|
||||
<br><br>
|
||||
<span class="emphasized">nothing automatically happens.</span> Users will be notified that it has expired, and
|
||||
the timer will display 0s, but the game will not end. Moderators always choose to end or restart the game manually.<br><br>
|
||||
</div>
|
||||
<h1 class="how-to-use-header" id="being-the-moderator">Being the Moderator</h1>
|
||||
<div class="how-to-use-section">
|
||||
This is an example of what a <span class="emphasized">dedicated moderator</span> sees during the game:
|
||||
<br><br>
|
||||
<img class='tutorial-image-small' src="../images/tutorial/dedicated-mod.PNG"/>
|
||||
<img class='tutorial-image-small-portrait' src="../images/screenshots/dedicated_mod_inprogress_mobile.png"/>
|
||||
<br><br>
|
||||
They can see who is on which team and who has each role. The moderator kills and reveals players. They are
|
||||
separate actions - note the two buttons for each player. So if you want to play a game where people's roles are
|
||||
not revealed during the day or night, you can use the "kill" option but not the reveal option. Or, if you happen to
|
||||
have a role that reveals but is not immediately removed from the game, you can use the "reveal" option but not the
|
||||
"kill" option.
|
||||
"kill" option. You of course don't have to utilize either of these options. If you just want to use the app to deal
|
||||
cards, you are free to do that.
|
||||
<br><br>
|
||||
The <span class="emphasized">temporary moderator view</span> looks like this. They have
|
||||
The moderator also has permissions to play and pause the timer, and can end the game (revealing everyone's role)
|
||||
or restart the game, which will reset the timer, give everyone new roles, and reset the status of anyone that was
|
||||
killed or revealed.
|
||||
<br><br>
|
||||
The <span class="emphasized">temporary moderator view</span> looks like the below image. They have
|
||||
much the same abilities as a dedicated moderator, except they don't know role or alignment information and cannot
|
||||
transfer their powers. Their powers will be transferred automatically to the first person they remove from the game:
|
||||
transfer their powers. Their powers will be transferred automatically to the first person they remove from the game
|
||||
(which can be themselves).
|
||||
<br><br>
|
||||
<img class='tutorial-image-small' src="../images/tutorial/temporary-mod.PNG"/>
|
||||
<img class='tutorial-image-small-portrait' src="../images/screenshots/temp_mod_inprogress_mobile.png"/>
|
||||
<br><br>
|
||||
<h3>Transferring your moderator powers</h3>
|
||||
<br>
|
||||
<span class="emphasized">You can transfer your moderator abilities to anyone that has been removed from the game, or to anyone that happens
|
||||
to be spectating.</span> After selecting them from the list, they will then inherit the moderator's view, and you will
|
||||
become a spectator:
|
||||
to be spectating.</span> Here we select a spectator and transfer our powers to them, and then we inherit the view
|
||||
of what we were before, which is a player that was removed from the game:
|
||||
<br><br>
|
||||
<img class='tutorial-image-small' src="../images/tutorial/transfer-mod.gif"/>
|
||||
<img class='tutorial-image-small-portrait' src="../images/tutorial/mod-transfer.gif"/>
|
||||
</div>
|
||||
<h1 class="how-to-use-header" id="being-a-player">Being a Player</h1>
|
||||
<div class="how-to-use-section">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Active Game</title>
|
||||
<meta name="description" content="Join this game of werewolf!">
|
||||
<meta property="og:title" content="Werewolf Utility - Join Game">
|
||||
<meta property="og:title" content="Join a Game">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:description" content="Join this game of werewolf!">
|
||||
<meta property="og:image" content="image.png">
|
||||
@@ -43,8 +43,11 @@
|
||||
<input id="player-new-name" autocomplete="given-name" type="text"/>
|
||||
</div>
|
||||
<div class="modal-button-container">
|
||||
<input type="submit" id="submit-join-as-spectator" value="Spectate"/>
|
||||
<input type="submit" id="submit-join-as-player" value="Join"/>
|
||||
<div>
|
||||
<input type="checkbox" id="join-as-spectator" name="join as spectator">
|
||||
<label for="join-as-spectator">Join as spectator</label>
|
||||
</div>
|
||||
<input type="submit" id="join-submit" value="Join"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ const globals = {
|
||||
ACCESS_CODE_GENERATION_ATTEMPTS: 50,
|
||||
CLOCK_TICK_INTERVAL_MILLIS: 100,
|
||||
MAX_CUSTOM_ROLE_NAME_LENGTH: 50,
|
||||
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 500,
|
||||
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 1000,
|
||||
ALIGNMENT: {
|
||||
GOOD: 'good',
|
||||
EVIL: 'evil'
|
||||
|
||||
85
server/model/GameCreationRequest.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const globals = require("../config/globals");
|
||||
|
||||
class GameCreationRequest {
|
||||
constructor (
|
||||
deck,
|
||||
hasTimer,
|
||||
timerParams,
|
||||
moderatorName,
|
||||
hasDedicatedModerator
|
||||
) {
|
||||
this.deck = deck;
|
||||
this.hasTimer = hasTimer;
|
||||
this.timerParams = timerParams;
|
||||
this.moderatorName = moderatorName;
|
||||
this.hasDedicatedModerator = hasDedicatedModerator;
|
||||
}
|
||||
|
||||
static validate = (gameParams) => {
|
||||
const expectedKeys = ['deck', 'hasTimer', 'timerParams', 'moderatorName', 'hasDedicatedModerator'];
|
||||
if (gameParams === null
|
||||
|| typeof gameParams !== 'object'
|
||||
|| expectedKeys.some((key) => !Object.keys(gameParams).includes(key))
|
||||
|| !valid(gameParams)
|
||||
) {
|
||||
return Promise.reject(globals.ERROR_MESSAGE.BAD_CREATE_REQUEST);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function valid (gameParams) {
|
||||
return typeof gameParams.hasTimer === 'boolean'
|
||||
&& typeof gameParams.hasDedicatedModerator === 'boolean'
|
||||
&& typeof gameParams.moderatorName === 'string'
|
||||
&& gameParams.moderatorName.length > 0
|
||||
&& gameParams.moderatorName.length <= 30
|
||||
&& timerParamsAreValid(gameParams.hasTimer, gameParams.timerParams)
|
||||
&& deckIsValid(gameParams.deck);
|
||||
}
|
||||
|
||||
function timerParamsAreValid (hasTimer, timerParams) {
|
||||
if (hasTimer === false) {
|
||||
return timerParams === null;
|
||||
} else {
|
||||
if (timerParams === null || typeof timerParams !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (timerParams.hours === null && timerParams.minutes > 0 && timerParams.minutes < 60)
|
||||
|| (timerParams.minutes === null && timerParams.hours > 0 && timerParams.hours < 6)
|
||||
|| (timerParams.hours === 0 && timerParams.minutes > 0 && timerParams.minutes < 60)
|
||||
|| (timerParams.minutes === 0 && timerParams.hours > 0 && timerParams.hours < 6)
|
||||
|| (timerParams.hours > 0 && timerParams.hours < 6 && timerParams.minutes >= 0 && timerParams.minutes < 60);
|
||||
}
|
||||
}
|
||||
|
||||
function deckIsValid (deck) {
|
||||
if (Array.isArray(deck) && deck.length > 0) {
|
||||
for (const entry of deck) {
|
||||
if (entry !== null
|
||||
&& typeof entry === 'object'
|
||||
&& typeof entry.role === 'string'
|
||||
&& entry.role.length > 0
|
||||
&& entry.role.length <= globals.MAX_CUSTOM_ROLE_NAME_LENGTH
|
||||
&& typeof entry.team === 'string'
|
||||
&& (entry.team === globals.ALIGNMENT.GOOD || entry.team === globals.ALIGNMENT.EVIL)
|
||||
&& typeof entry.description === 'string'
|
||||
&& entry.description.length > 0
|
||||
&& entry.description.length <= globals.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH
|
||||
&& (!entry.custom || typeof entry.custom === 'boolean')
|
||||
&& typeof entry.quantity === 'number'
|
||||
&& entry.quantity >= 1
|
||||
&& entry.quantity <= 50
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = GameCreationRequest;
|
||||
@@ -3,6 +3,7 @@ const Game = require('../model/Game');
|
||||
const Person = require('../model/Person');
|
||||
const GameStateCurator = require('./GameStateCurator');
|
||||
const UsernameGenerator = require('./UsernameGenerator');
|
||||
const GameCreationRequest = require("../model/GameCreationRequest");
|
||||
|
||||
class GameManager {
|
||||
constructor (logger, environment, activeGameRunner) {
|
||||
@@ -22,41 +23,43 @@ class GameManager {
|
||||
};
|
||||
|
||||
createGame = (gameParams) => {
|
||||
const expectedKeys = ['deck', 'hasTimer', 'timerParams', 'moderatorName', 'hasDedicatedModerator'];
|
||||
if (typeof gameParams !== 'object'
|
||||
|| expectedKeys.some((key) => !Object.keys(gameParams).includes(key))
|
||||
|| !valid(gameParams)
|
||||
) {
|
||||
this.logger.error('Tried to create game with invalid options.');
|
||||
return Promise.reject(globals.ERROR_MESSAGE.BAD_CREATE_REQUEST);
|
||||
} else {
|
||||
return GameCreationRequest.validate(gameParams).then(() => {
|
||||
let req = new GameCreationRequest(
|
||||
gameParams.deck,
|
||||
gameParams.hasTimer,
|
||||
gameParams.timerParams,
|
||||
gameParams.moderatorName,
|
||||
gameParams.hasDedicatedModerator
|
||||
);
|
||||
this.pruneStaleGames();
|
||||
const newAccessCode = this.generateAccessCode(globals.ACCESS_CODE_CHAR_POOL);
|
||||
if (newAccessCode === null) {
|
||||
return Promise.reject(globals.ERROR_MESSAGE.NO_UNIQUE_ACCESS_CODE);
|
||||
}
|
||||
const moderator = initializeModerator(gameParams.moderatorName, gameParams.hasDedicatedModerator);
|
||||
const moderator = initializeModerator(req.moderatorName, req.hasDedicatedModerator);
|
||||
moderator.assigned = true;
|
||||
if (gameParams.timerParams !== null) {
|
||||
gameParams.timerParams.paused = false;
|
||||
if (req.timerParams !== null) {
|
||||
req.timerParams.paused = false;
|
||||
}
|
||||
const newGame = new Game(
|
||||
newAccessCode,
|
||||
globals.STATUS.LOBBY,
|
||||
initializePeopleForGame(gameParams.deck, moderator, this.shuffle),
|
||||
gameParams.deck,
|
||||
gameParams.hasTimer,
|
||||
initializePeopleForGame(req.deck, moderator, this.shuffle),
|
||||
req.deck,
|
||||
req.hasTimer,
|
||||
moderator,
|
||||
gameParams.hasDedicatedModerator,
|
||||
req.hasDedicatedModerator,
|
||||
moderator.id,
|
||||
new Date().toJSON(),
|
||||
gameParams.timerParams
|
||||
req.timerParams
|
||||
);
|
||||
|
||||
this.activeGameRunner.activeGames.set(newAccessCode, newGame);
|
||||
|
||||
return Promise.resolve({ accessCode: newAccessCode, cookie: moderator.cookie, environment: this.environment });
|
||||
}
|
||||
}).catch((message) => {
|
||||
return Promise.reject(message);
|
||||
});
|
||||
};
|
||||
|
||||
startGame = (game, namespace) => {
|
||||
@@ -494,54 +497,6 @@ function getGameSize (cards) {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
function valid (gameParams) {
|
||||
return typeof gameParams.hasTimer === 'boolean'
|
||||
&& typeof gameParams.hasDedicatedModerator === 'boolean'
|
||||
&& typeof gameParams.moderatorName === 'string'
|
||||
&& gameParams.moderatorName.length > 0
|
||||
&& gameParams.moderatorName.length <= 30
|
||||
&& timerParamsAreValid(gameParams.hasTimer, gameParams.timerParams)
|
||||
&& deckIsValid(gameParams.deck);
|
||||
}
|
||||
|
||||
function timerParamsAreValid (hasTimer, timerParams) {
|
||||
if (hasTimer === false) {
|
||||
return timerParams === null;
|
||||
} else {
|
||||
if (timerParams === null || typeof timerParams !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (timerParams.hours === null && timerParams.minutes > 0 && timerParams.minutes < 60)
|
||||
|| (timerParams.minutes === null && timerParams.hours > 0 && timerParams.hours < 6)
|
||||
|| (timerParams.hours === 0 && timerParams.minutes > 0 && timerParams.minutes < 60)
|
||||
|| (timerParams.minutes === 0 && timerParams.hours > 0 && timerParams.hours < 6)
|
||||
|| (timerParams.hours > 0 && timerParams.hours < 6 && timerParams.minutes >= 0 && timerParams.minutes < 60);
|
||||
}
|
||||
}
|
||||
|
||||
function deckIsValid (deck) {
|
||||
if (Array.isArray(deck) && deck.length > 0) {
|
||||
for (const entry of deck) {
|
||||
if (entry !== null && typeof entry === 'object') {
|
||||
if (typeof entry.role !== 'string' || entry.role.length > globals.MAX_CUSTOM_ROLE_NAME_LENGTH
|
||||
|| typeof entry.team !== 'string' || (entry.team !== globals.ALIGNMENT.GOOD && entry.team !== globals.ALIGNMENT.EVIL)
|
||||
|| typeof entry.description !== 'string' || entry.description.length > globals.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH
|
||||
|| (entry.custom && typeof entry.custom !== 'boolean')
|
||||
|| typeof entry.quantity !== 'number' || entry.quantity < 1 || entry.quantity > 50
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function addSpectator (game, name, logger, namespace) {
|
||||
const spectator = new Person(
|
||||
createRandomId(),
|
||||
|
||||