editing custom roles

This commit is contained in:
AlecM33
2022-01-11 19:57:13 -05:00
parent 3b14ae3978
commit c0c4ccdd74
14 changed files with 172 additions and 58 deletions

View File

@@ -5,7 +5,6 @@ smoothly when you don't have a deck, or when you and your friends are together v
After a long hiatus from maintaining the application, I have come back and undertaken a large-scale redesign, rewriting
most of the code and producing a result that I believe is more stable and has much more sensible client-server interaction.
It's a shame that my first attempt is what ended up in Github's Artic Code Vault :)
![player](./client/src/images/screenshots/player.PNG)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -6,6 +6,8 @@ export class DeckStateManager {
constructor() {
this.deck = null;
this.customRoleOptions = [];
this.createMode = false;
this.currentlyEditingRoleName = null;
}
addToDeck(role) {
@@ -22,6 +24,13 @@ export class DeckStateManager {
localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(this.customRoleOptions.concat(this.deck.filter(card => card.custom === true))));
}
updateCustomRoleOption(option, name, description, team) {
option.role = name;
option.description = description;
option.team = team;
localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(this.customRoleOptions.concat(this.deck.filter(card => card.custom === true))));
}
removeFromCustomRoleOptions(name) {
let option = this.customRoleOptions.find((option) => option.role === name);
if (option) {

View File

@@ -470,32 +470,41 @@ function constructCompactDeckBuilderElement(card, deckManager) {
}
function initializeRemainingEventListeners(deckManager) {
document.getElementById("add-role-form").onsubmit = (e) => {
document.getElementById("role-form").onsubmit = (e) => {
e.preventDefault();
let name = document.getElementById("role-name").value.trim();
let description = document.getElementById("role-description").value.trim();
let team = document.getElementById("role-alignment").value.toLowerCase().trim();
if (!deckManager.getCustomRoleOption(name) && !deckManager.getCard(name)) { // confirm there is no existing custom role with the same name
if (name.length > 40) {
toast('Your name is too long (max 40 characters).', "error", true);
return;
if (deckManager.createMode) {
if (!deckManager.getCustomRoleOption(name) && !deckManager.getCard(name)) { // confirm there is no existing custom role with the same name
processNewCustomRoleSubmission(name, description, team, deckManager,false);
} else {
toast("There is already a role with this name", "error", true, true, 3);
}
if (description.length > 500) {
toast('Your description is too long (max 500 characters).', "error", true);
return;
}
deckManager.addToCustomRoleOptions({role: name, description: description, team: team, custom: true});
updateCustomRoleOptionsList(deckManager, document.getElementById("deck-select"))
ModalManager.dispelModal("add-role-modal", "modal-background");
toast("Role Created", "success", true);
} else {
toast("There is already a role with this name", "error", true, true, 3);
let option = deckManager.getCustomRoleOption(deckManager.currentlyEditingRoleName);
if (name === option.role) { // did they edit the name?
processNewCustomRoleSubmission(name, description, team, deckManager,true, option);
} else {
if (!deckManager.getCustomRoleOption(name) && !deckManager.getCard(name)) {
processNewCustomRoleSubmission(name, description, team, deckManager, true, option);
} else {
toast("There is already a role with this name", "error", true, true, 3);
}
}
}
}
document.getElementById("custom-role-btn").addEventListener(
"click", () => {
let createBtn = document.getElementById("create-role-button");
createBtn.setAttribute("value", "Create");
deckManager.createMode = true;
deckManager.currentlyEditingRoleName = null;
document.getElementById("role-name").value = "";
document.getElementById("role-alignment").value = globals.ALIGNMENT.GOOD;
document.getElementById("role-description").value = "";
ModalManager.displayModal(
"add-role-modal",
"role-modal",
"modal-background",
"close-modal-button"
)
@@ -503,6 +512,28 @@ function initializeRemainingEventListeners(deckManager) {
)
}
function processNewCustomRoleSubmission(name, description, team, deckManager, isUpdate, option=null) {
if (name.length > 40) {
toast('Your name is too long (max 40 characters).', "error", true);
return;
}
if (description.length > 500) {
toast('Your description is too long (max 500 characters).', "error", true);
return;
}
if (isUpdate) {
deckManager.updateCustomRoleOption(option, name, description, team);
ModalManager.dispelModal("role-modal", "modal-background");
toast("Role Updated", "success", true);
} else {
deckManager.addToCustomRoleOptions({role: name, description: description, team: team, custom: true});
ModalManager.dispelModal("role-modal", "modal-background");
toast("Role Created", "success", true);
}
updateCustomRoleOptionsList(deckManager, document.getElementById("deck-select"));
}
function updateCustomRoleOptionsList(deckManager, selectEl) {
document.querySelectorAll('#deck-select .deck-select-role').forEach(e => e.remove());
addOptionsToList(deckManager, selectEl);
@@ -568,17 +599,34 @@ function addCustomRoleEventListeners(deckManager, select) {
document.getElementById("custom-role-info-modal-description").innerText = option.description;
alignmentEl.innerText = option.team;
ModalManager.displayModal("custom-role-info-modal", "modal-background", "close-custom-role-info-modal-button");
})
});
role.querySelector('.deck-select-edit').addEventListener('click', (e) => {
e.preventDefault();
let option = deckManager.getCustomRoleOption(name);
document.getElementById("role-name").value = option.role;
document.getElementById("role-alignment").value = option.team;
document.getElementById("role-description").value = option.description;
deckManager.createMode = false;
deckManager.currentlyEditingRoleName = option.role;
let createBtn = document.getElementById("create-role-button");
createBtn.setAttribute("value", "Update");
ModalManager.displayModal("role-modal", "modal-background", "close-modal-button");
});
});
}
function displayCustomRoleModalInAddOrEditMode() {
let ad
}
function updateDeckStatus(deckManager) {
document.querySelectorAll('.deck-role').forEach((el) => el.remove());
document.getElementById("deck-count").innerText = deckManager.getDeckSize() + " Players";
if (deckManager.getDeckSize() === 0) {
let placeholder = document.createElement("div");
placeholder.setAttribute("id", "deck-list-placeholder");
placeholder.innerText = "Add a card from the included roles below.";
placeholder.innerText = "Add a card from the available roles below.";
document.getElementById("deck-list").appendChild(placeholder);
} else {
if (document.getElementById("deck-list-placeholder")) {

View File

@@ -215,6 +215,7 @@ export class GameStateRenderer {
}
displayAvailableModerators() {
document.getElementById("transfer-mod-modal-content").innerText = "";
document.querySelectorAll('.potential-moderator').forEach((el) => {
let pointer = el.dataset.pointer;
if (pointer && this.transferModHandlers[pointer]) {

View File

@@ -4,6 +4,5 @@
*/
export const stateBucket = {
currentGameState: null,
timerWorker: null,
gameStateRequestInFlight: false
timerWorker: null
}

View File

@@ -13,9 +13,7 @@ const game = () => {
injectNavbar();
let timerWorker;
const socket = io('/in-game');
stateBucket.gameStateRequestInFlight = false;
socket.on('disconnect', () => {
stateBucket.gameStateRequestInFlight = false;
if (timerWorker) {
timerWorker.terminate();
}
@@ -24,10 +22,8 @@ const game = () => {
socket.on('connect', () => {
console.log('fired connect event');
socket.emit(globals.COMMANDS.GET_ENVIRONMENT, function (returnedEnvironment) {
if (!stateBucket.gameStateRequestInFlight) {
timerWorker = new Worker(new URL('../modules/Timer.js', import.meta.url));
prepareGamePage(returnedEnvironment, socket, timerWorker);
}
timerWorker = new Worker(new URL('../modules/Timer.js', import.meta.url));
prepareGamePage(returnedEnvironment, socket, timerWorker);
});
})
};
@@ -37,9 +33,7 @@ function prepareGamePage(environment, socket, timerWorker) {
const splitUrl = window.location.href.split('/game/');
const accessCode = splitUrl[1];
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
stateBucket.gameStateRequestInFlight = true;
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, userId, function (gameState) {
stateBucket.gameStateRequestInFlight = false;
stateBucket.currentGameState = gameState;
document.querySelector('.spinner-container')?.remove();
document.querySelector('.spinner-background')?.remove();
@@ -214,7 +208,6 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo
stateBucket.currentGameState.accessCode,
stateBucket.currentGameState.client.cookie,
function (gameState) {
stateBucket.gameStateRequestInFlight = false;
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
@@ -230,26 +223,22 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo
}
if (!socket.hasListeners(globals.EVENTS.SYNC_GAME_STATE)) {
socket.on(globals.EVENTS.SYNC_GAME_STATE, () => {
if (!stateBucket.gameStateRequestInFlight) {
stateBucket.gameStateRequestInFlight = true;
socket.emit(
globals.COMMANDS.FETCH_IN_PROGRESS_STATE,
stateBucket.currentGameState.accessCode,
stateBucket.currentGameState.client.cookie,
function (gameState) {
stateBucket.gameStateRequestInFlight = false;
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
gameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker
);
}
);
}
socket.emit(
globals.COMMANDS.FETCH_IN_PROGRESS_STATE,
stateBucket.currentGameState.accessCode,
stateBucket.currentGameState.client.cookie,
function (gameState) {
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
gameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker
);
}
);
});
}

View File

@@ -180,6 +180,26 @@ button {
border: 2px solid #1c8a36;
}
.emphasized {
font-weight: bold;
color: #00a718;
}
#how-to-use-container img {
max-width: 98%;
border: 1px solid #57566a;
border-radius: 3px;
width: 45em;
margin: 0 auto;
/* justify-self: center; */
/* align-self: center; */
display: flex;
}
#how-to-use-container h1 {
font-family: signika-negative, sans-serif;
}
input {
padding: 10px;
}
@@ -227,10 +247,22 @@ input {
flex-direction: column;
margin: 0 auto;
width: 95%;
max-width: 70em;
max-width: 64em;
line-height: 1.5;
}
#how-to-use-container h3 {
font-size: 25px;
}
#how-to-use-container h1 {
font-size: 40px !important;
}
.tutorial-image-small {
width: 30em !important;
}
#desktop-links > a:nth-child(1), #mobile-links a:nth-child(1) {
margin: 0 0.5em;
width: 50px;

View File

@@ -694,7 +694,6 @@ label[for='moderator'] {
button {
font-size: 16px;
padding: 5px;
}
#play-pause img {

View File

@@ -23,8 +23,8 @@
<div id="navbar"></div>
<div id="game-creation-container" class="container">
<div id="modal-background" class="modal-background" style="display: none"></div>
<div id="add-role-modal" class="modal" style="display: none">
<form id="add-role-form">
<div id="role-modal" class="modal" style="display: none">
<form id="role-form">
<div>
<label for="role-name">Role Name</label>
<input id="role-name" type="text" placeholder="Name your role..." required/>
@@ -32,8 +32,8 @@
<div>
<label for="role-alignment">Role Alignment</label>
<select id="role-alignment" required>
<option value="good">Good</option>
<option value="evil">Evil</option>
<option value="good">good</option>
<option value="evil">evil</option>
</select>
</div>
<div>

View File

@@ -34,12 +34,50 @@
</div>
<h1 class="">Creating a Game</h1>
<div class="how-to-use-section">
Creating a game through the app is a 4-step process:
Creating a game through the app has 3 main components:
<br>
<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 "dedicated
moderator" option.
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
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.
<br><br>
<span class="emphasized">Dedicated modertors</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 deligate.
<br><br>
<img src="../images/tutorial/moderation-option.png" alt="moderation-option"/>
<br><br>
<h3>Step Two: Build your deck</h3>
<br>
A default set of common roles are available to you on this page. <span class="emphasized">Available roles</span>
are ones that have widget where you can adjust their quantity and add them to the current game. They have been
"taken out of the box," so to speak:
<br><br>
<img class='tutorial-image-small' src="../images/tutorial/default-roles.PNG"/>
<br><br>
You also have a <span class="emphasized">custom role box</span>. These are not yet "available," in that they
are still "in the box." If you want them in the game, click the green plus button, and they will become part of the
available roles - a widget will be created, and you can increment or decrement the quantity of that card in the game.
Custom roles can be created, removed, edited, and imported/exported via a formatted text file:
<br><br>
<img class='tutorial-image-small' src="../images/tutorial/custom-roles.PNG"/>
<br><br>
<h3>Step Three: Set an optional timer</h3>
<br>
If you don't fill in these fields, the game will be untimed. If you do, you can use a time between 1 minute
and 5 hours. 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>
<p style="text-align:center;color:gray;margin-bottom:3em;">More content coming soon.</p>
</div>
</div>
<script src="/dist/howToUse-bundle.js"></script>