Add independent alignment: constants, validation, UI, and CSS

Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-25 00:37:24 +00:00
parent 554a0638eb
commit a1bcf0f6f8
11 changed files with 55 additions and 18 deletions

View File

@@ -21,7 +21,8 @@ export const STATUS = {
export const ALIGNMENT = {
GOOD: 'good',
EVIL: 'evil'
EVIL: 'evil',
INDEPENDENT: 'independent'
};
export const MESSAGES = {

View File

@@ -147,7 +147,8 @@ export class DeckStateManager {
}
const sortedDeck = this.deck.sort((a, b) => {
if (a.team !== b.team) {
return a.team === ALIGNMENT.GOOD ? -1 : 1;
const order = { good: 0, evil: 1, independent: 2 };
return order[a.team] - order[b.team];
}
return a.role.localeCompare(b.role);
});
@@ -187,11 +188,7 @@ export class DeckStateManager {
roleEl.dataset.roleId = sortedDeck[i].id;
roleEl.classList.add('added-role');
roleEl.innerHTML = HTMLFragments.DECK_SELECT_ROLE_ADDED_TO_DECK;
if (sortedDeck[i].team === ALIGNMENT.GOOD) {
roleEl.classList.add(ALIGNMENT.GOOD);
} else {
roleEl.classList.add(ALIGNMENT.EVIL);
}
roleEl.classList.add(sortedDeck[i].team);
populateRoleElementInfo(roleEl, sortedDeck, i);
document.getElementById('deck-list').appendChild(roleEl);
const minusOneHandler = (e) => {
@@ -237,8 +234,10 @@ export class DeckStateManager {
const nameEl = document.getElementById('custom-role-info-modal-name');
alignmentEl.classList.remove(ALIGNMENT.GOOD);
alignmentEl.classList.remove(ALIGNMENT.EVIL);
alignmentEl.classList.remove(ALIGNMENT.INDEPENDENT);
nameEl.classList.remove(ALIGNMENT.GOOD);
nameEl.classList.remove(ALIGNMENT.EVIL);
nameEl.classList.remove(ALIGNMENT.INDEPENDENT);
e.preventDefault();
nameEl.innerText = sortedDeck[i].role;
nameEl.classList.add(sortedDeck[i].team);
@@ -256,6 +255,7 @@ export class DeckStateManager {
function populateRoleElementInfo (roleEl, sortedDeck, i) {
roleEl.classList.remove(ALIGNMENT.GOOD);
roleEl.classList.remove(ALIGNMENT.EVIL);
roleEl.classList.remove(ALIGNMENT.INDEPENDENT);
roleEl.classList.add(sortedDeck[i].team);
roleEl.querySelector('.role-name').innerHTML =
`<span class="role-quantity"></span>

View File

@@ -463,11 +463,7 @@ function renderReviewAndCreateStep (containerId, stepNumber, game, deckManager)
for (const card of game.deck) {
const roleEl = document.createElement('div');
roleEl.innerText = card.quantity + 'x ' + card.role;
if (card.team === ALIGNMENT.GOOD) {
roleEl.classList.add(ALIGNMENT.GOOD);
} else {
roleEl.classList.add(ALIGNMENT.EVIL);
}
roleEl.classList.add(card.team);
div.querySelector('#roles-option').appendChild(roleEl);
}

View File

@@ -31,7 +31,8 @@ export class RoleBox {
loadDefaultRoles = () => {
this.defaultRoles = defaultRoles.sort((a, b) => {
if (a.team !== b.team) {
return a.team === ALIGNMENT.GOOD ? -1 : 1;
const order = { good: 0, evil: 1, independent: 2 };
return order[a.team] - order[b.team];
}
return a.role.localeCompare(b.role);
}).map((role) => {
@@ -202,7 +203,8 @@ export class RoleBox {
}
this.customRoles.sort((a, b) => {
if (a.team !== b.team) {
return a.team === ALIGNMENT.GOOD ? -1 : 1;
const order = { good: 0, evil: 1, independent: 2 };
return order[a.team] - order[b.team];
}
return a.role.localeCompare(b.role);
});
@@ -282,8 +284,10 @@ export class RoleBox {
const nameEl = document.getElementById('custom-role-info-modal-name');
alignmentEl.classList.remove(ALIGNMENT.GOOD);
alignmentEl.classList.remove(ALIGNMENT.EVIL);
alignmentEl.classList.remove(ALIGNMENT.INDEPENDENT);
nameEl.classList.remove(ALIGNMENT.GOOD);
nameEl.classList.remove(ALIGNMENT.EVIL);
nameEl.classList.remove(ALIGNMENT.INDEPENDENT);
e.preventDefault();
let role;
if (isCustom) {

View File

@@ -295,6 +295,10 @@ export class InProgress {
&& ((p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR)
|| p.killed)
);
const teamIndependent = this.stateBucket.currentGameState.people.filter((p) => p.alignment === ALIGNMENT.INDEPENDENT
&& ((p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR)
|| p.killed)
);
this.renderGroupOfPlayers(
teamEvil,
this.killPlayerHandlers,
@@ -315,6 +319,16 @@ export class InProgress {
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
this.socket
);
this.renderGroupOfPlayers(
teamIndependent,
this.killPlayerHandlers,
this.revealRoleHandlers,
this.stateBucket.currentGameState.accessCode,
ALIGNMENT.INDEPENDENT,
this.stateBucket.currentGameState.people.find(person =>
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
this.socket
);
document.getElementById('players-alive-label').innerText =
'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' +
this.stateBucket.currentGameState.gameSize + ' Alive';
@@ -460,9 +474,12 @@ function renderPlayerRole (gameState) {
if (gameState.client.alignment === ALIGNMENT.GOOD) {
document.getElementById('game-role').classList.add('game-role-good');
name.classList.add('good');
} else {
} else if (gameState.client.alignment === ALIGNMENT.EVIL) {
document.getElementById('game-role').classList.add('game-role-evil');
name.classList.add('evil');
} else if (gameState.client.alignment === ALIGNMENT.INDEPENDENT) {
document.getElementById('game-role').classList.add('game-role-independent');
name.classList.add('independent');
}
name.setAttribute('title', gameState.client.gameRole);
if (gameState.client.out) {

View File

@@ -149,7 +149,11 @@ export const SharedStateUtil = {
document.getElementById('role-info-button').addEventListener('click', (e) => {
const deck = stateBucket.currentGameState.deck;
deck.sort((a, b) => {
return a.team === ALIGNMENT.GOOD ? -1 : 1;
if (a.team !== b.team) {
const order = { good: 0, evil: 1, independent: 2 };
return order[a.team] - order[b.team];
}
return a.role.localeCompare(b.role);
});
e.preventDefault();
document.getElementById('role-info-prompt').innerHTML = HTMLFragments.ROLE_INFO_MODAL;

View File

@@ -608,6 +608,15 @@ input {
font-weight: bold;
}
.independent, .compact-card.independent .card-role {
color: #d4a027 !important;
font-weight: bold;
}
.independent-players, #deck-independent {
border: 2px solid rgba(212, 160, 39, 0.39);
}
@keyframes placeholder {
0%{
background-position: 50% 0

View File

@@ -547,6 +547,10 @@ h1 {
color: #e15656 !important;
}
#game-role #role-name.independent {
color: #d4a027 !important;
}
#role-image {
user-select: none;
-ms-user-select: none;

View File

@@ -11,6 +11,7 @@ export const hiddenMenus =
<select id="role-alignment" required>
<option value="good">good</option>
<option value="evil">evil</option>
<option value="independent">independent</option>
</select>
</div>
<div>

View File

@@ -24,7 +24,8 @@ const LOG_LEVEL = {
const ALIGNMENT = {
GOOD: 'good',
EVIL: 'evil'
EVIL: 'evil',
INDEPENDENT: 'independent'
};
const REDIS_CHANNELS = {

View File

@@ -39,7 +39,7 @@ class GameCreationRequest {
&& entry.role.length > 0
&& entry.role.length <= PRIMITIVES.MAX_CUSTOM_ROLE_NAME_LENGTH
&& typeof entry.team === 'string'
&& (entry.team === ALIGNMENT.GOOD || entry.team === ALIGNMENT.EVIL)
&& (entry.team === ALIGNMENT.GOOD || entry.team === ALIGNMENT.EVIL || entry.team === ALIGNMENT.INDEPENDENT)
&& typeof entry.description === 'string'
&& entry.description.length > 0
&& entry.description.length <= PRIMITIVES.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH