mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
editing custom roles
This commit is contained in:
@@ -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 :)
|
||||
|
||||

|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 68 KiB |
BIN
client/src/images/tutorial/custom-roles.PNG
Normal file
BIN
client/src/images/tutorial/custom-roles.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
client/src/images/tutorial/default-roles.PNG
Normal file
BIN
client/src/images/tutorial/default-roles.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
client/src/images/tutorial/moderation-option.png
Normal file
BIN
client/src/images/tutorial/moderation-option.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -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) {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -4,6 +4,5 @@
|
||||
*/
|
||||
export const stateBucket = {
|
||||
currentGameState: null,
|
||||
timerWorker: null,
|
||||
gameStateRequestInFlight: false
|
||||
timerWorker: null
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -694,7 +694,6 @@ label[for='moderator'] {
|
||||
|
||||
button {
|
||||
font-size: 16px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#play-pause img {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user