This commit is contained in:
Cody 2023-09-05 02:29:35 +00:00 committed by GitHub
commit aa06f880ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 94 additions and 94 deletions

View File

@ -25,7 +25,7 @@ Updated character night order to be consistent with script tool
### Version 2.15.2
- added mobile web application support
- show correct number of leaves on roles with global reminders
- fixed a bug with traveler list showing up when assigning demon bluffs
- fixed a bug with traveller list showing up when assigning demon bluffs
- fixed a bug with homebrew scripts that contained negative night order positions
---
@ -124,14 +124,14 @@ Updated character night order to be consistent with script tool
---
## Version 2.5.0
- all travelers from the base editions are now optionally available (thanks @davotronic5000)
- all travellers from the base editions are now optionally available (thanks @davotronic5000)
- night order shows player names near roles now
---
## Version 2.4.0
- added spoiler role (Pixie!)
- fixed bug with ST sending out roles that are not part of the current edition / script (ie. travelers or base set roles)
- fixed bug with ST sending out roles that are not part of the current edition / script (ie. travellers or base set roles)
- better Lycanthrope icon (thanks @AWConant)
---
@ -181,20 +181,20 @@ Updated character night order to be consistent with script tool
---
## Version 2.0.4
- fix bug with live sessions that contain travelers from a different set
- fix bug with live sessions that contain travellers from a different set
- fix server channel cleanup
---
## Version 2.0.3
- load roles that belong to different editions (like travelers) from gamestate
- load roles that belong to different editions (like travellers) from gamestate
- close session when missing custom roles and open edition modal
- added a few more metrics
---
## Version 2.0.2
- fix nomination history type not detecting travelers
- fix nomination history type not detecting travellers
- fix live session domain whitelist
- fix build path
- fix changelog version numbering

View File

@ -22,7 +22,7 @@ If you want to learn more about how to use the app as a player, [JayBotC](https:
- Public Town Square and Storyteller Grimoire (toggle with **shortcut \[G\]**)
- Supports custom script JSON generated by the [Script Tool](https://bloodontheclocktower.com/script)
- Live Session for Storyteller / Players including live voting and character distribution!
- Includes all 3 base editions, Travelers and Fabled plus all officially spoiled characters so far!
- Includes all 3 base editions, Travellers and Fabled plus all officially spoiled characters so far!
- Night sheet and reminder text for each character ability to help storytellers
- Full homebrew support for hosting and playing games with your own sets of characters
- Many other customization options!
@ -99,7 +99,7 @@ For base game characters, it is sufficient to only provide the ID, similar to wh
- **remindersGlobal**: global reminder tokens that will always be available, no matter if the character is assigned to a player or not
- **setup**: whether this token affects setup (orange leaf), like the Drunk or Baron
- **name**: the displayed name of this character
- **team**: the team of the character, has to be one of `townsfolk`, `outsider`, `minion`, `demon`, `traveler` or `fabled`<br>
- **team**: the team of the character, has to be one of `townsfolk`, `outsider`, `minion`, `demon`, `traveller` or `fabled`<br>
_Note_: if you create a custom Fabled character, it will be automatically added to the game when the custom script is loaded
- **ability**: the displayed ability text of the character

View File

@ -472,7 +472,7 @@ export default {
}
}
&.traveler .life {
&.traveller .life {
filter: grayscale(100%);
}
}
@ -487,13 +487,13 @@ export default {
transform: perspective(400px) rotateY(0deg);
}
&.traveler:not(.dead) .token {
&.traveller:not(.dead) .token {
transform: perspective(400px) scale(0.8);
pointer-events: none;
transition-delay: 0s;
}
&.traveler.dead .token {
&.traveller.dead .token {
transition-delay: 0s;
}
}
@ -636,7 +636,7 @@ li.move:not(.from) .player .overlay svg.move {
@include glow("outsider", $outsider);
@include glow("demon", $demon);
@include glow("minion", $minion);
@include glow("traveler", $traveler);
@include glow("traveller", $traveller);
.player.you .token {
animation: townsfolk-glow 5s ease-in-out infinite;

View File

@ -11,7 +11,7 @@
})`
}"
></li>
<li v-if="players.length - teams.traveler < 5">
<li v-if="players.length - teams.traveller < 5">
Please add more players!
</li>
<li>
@ -30,7 +30,7 @@
{{ teams.votes }} <font-awesome-icon class="votes" icon="vote-yea" />
</span>
</li>
<li v-if="players.length - teams.traveler >= 5">
<li v-if="players.length - teams.traveller >= 5">
<span>
{{ teams.townsfolk }}
<font-awesome-icon class="townsfolk" icon="user-friends" />
@ -56,11 +56,11 @@
:icon="teams.demon > 1 ? 'user-friends' : 'user'"
/>
</span>
<span v-if="teams.traveler">
{{ teams.traveler }}
<span v-if="teams.traveller">
{{ teams.traveller }}
<font-awesome-icon
class="traveler"
:icon="teams.traveler > 1 ? 'user-friends' : 'user'"
class="traveller"
:icon="teams.traveller > 1 ? 'user-friends' : 'user'"
/>
</span>
<span v-if="grimoire.isNight">
@ -79,11 +79,11 @@ export default {
computed: {
teams: function() {
const { players } = this.$store.state.players;
const nonTravelers = this.$store.getters["players/nonTravelers"];
const nonTravellers = this.$store.getters["players/nonTravellers"];
const alive = players.filter(player => player.isDead !== true).length;
return {
...gameJSON[nonTravelers - 5],
traveler: players.length - nonTravelers,
...gameJSON[nonTravellers - 5],
traveller: players.length - nonTravellers,
alive,
votes:
alive +
@ -160,8 +160,8 @@ export default {
.demon {
color: $demon;
}
.traveler {
color: $traveler;
.traveller {
color: $traveller;
}
}

View File

@ -146,7 +146,7 @@ export default {
},
openRoleModal(playerIndex) {
const player = this.players[playerIndex];
if (this.session.isSpectator && player && player.role.team === "traveler")
if (this.session.isSpectator && player && player.role.team === "traveller")
return;
this.selectedPlayer = playerIndex;
this.$store.commit("toggleModal", "role");

View File

@ -14,7 +14,7 @@
{{ voters.length }} vote{{ voters.length !== 1 ? "s" : "" }}
</em>
in favor
<em v-if="nominee.role.team !== 'traveler'">
<em v-if="nominee.role.team !== 'traveller'">
(majority is {{ Math.ceil(alive / 2) }})
</em>
<em v-else>(majority is {{ Math.ceil(players.length / 2) }})</em>
@ -55,7 +55,7 @@
</template>
<div class="button demon" @click="finish">Close</div>
</div>
<div class="button-group mark" v-if="nominee.role.team !== 'traveler'">
<div class="button-group mark" v-if="nominee.role.team !== 'traveller'">
<div
class="button"
:class="{
@ -155,7 +155,7 @@ export default {
},
canVote: function() {
if (!this.player) return false;
if (this.player.isVoteless && this.nominee.role.team !== "traveler")
if (this.player.isVoteless && this.nominee.role.team !== "traveller")
return false;
const session = this.session;
const players = this.players.length;

View File

@ -137,7 +137,7 @@ export default {
}
this.roles.forEach(role => {
const players = this.players.filter(p => p.role.id === role.id);
if (role.firstNight && (role.team !== "traveler" || players.length)) {
if (role.firstNight && (role.team !== "traveller" || players.length)) {
rolesFirstNight.push(Object.assign({ players }, role));
}
});
@ -153,7 +153,7 @@ export default {
const rolesOtherNight = [];
this.roles.forEach(role => {
const players = this.players.filter(p => p.role.id === role.id);
if (role.otherNight && (role.team !== "traveler" || players.length)) {
if (role.otherNight && (role.team !== "traveller" || players.length)) {
rolesOtherNight.push(Object.assign({ players }, role));
}
});

View File

@ -125,13 +125,13 @@ export default {
}
rolesGrouped[role.team].push(role);
});
delete rolesGrouped["traveler"];
delete rolesGrouped["traveller"];
return rolesGrouped;
},
playersByRole: function() {
const players = {};
this.players.forEach(({ name, role }) => {
if (role && role.id && role.team !== "traveler") {
if (role && role.id && role.team !== "traveller") {
if (!players[role.id]) {
players[role.id] = [];
}

View File

@ -75,8 +75,8 @@ export default {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
});
// add out of script traveler reminders
this.$store.state.otherTravelers.forEach(role => {
// add out of script traveller reminders
this.$store.state.otherTravellers.forEach(role => {
if (players.some(p => p.role.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
}

View File

@ -8,7 +8,7 @@
: "bluffing"
}}
</h3>
<ul class="tokens" v-if="tab === 'editionRoles' || !otherTravelers.size">
<ul class="tokens" v-if="tab === 'editionRoles' || !otherTravellers.size">
<li
v-for="role in availableRoles"
:class="[role.team]"
@ -18,9 +18,9 @@
<Token :role="role" />
</li>
</ul>
<ul class="tokens" v-if="tab === 'otherTravelers' && otherTravelers.size">
<ul class="tokens" v-if="tab === 'otherTravellers' && otherTravellers.size">
<li
v-for="role in otherTravelers.values()"
v-for="role in otherTravellers.values()"
:class="[role.team]"
:key="role.id"
@click="setRole(role)"
@ -30,7 +30,7 @@
</ul>
<div
class="button-group"
v-if="playerIndex >= 0 && otherTravelers.size && !session.isSpectator"
v-if="playerIndex >= 0 && otherTravellers.size && !session.isSpectator"
>
<span
class="button"
@ -40,9 +40,9 @@
>
<span
class="button"
:class="{ townsfolk: tab === 'otherTravelers' }"
@click="tab = 'otherTravelers'"
>Other Travelers</span
:class="{ townsfolk: tab === 'otherTravellers' }"
@click="tab = 'otherTravellers'"
>Other Travellers</span
>
</div>
</Modal>
@ -75,7 +75,7 @@ export default {
},
...mapState(["modals", "roles", "session"]),
...mapState("players", ["players"]),
...mapState(["otherTravelers"])
...mapState(["otherTravellers"])
},
data() {
return {
@ -91,7 +91,7 @@ export default {
role
});
} else {
if (this.session.isSpectator && role.team === "traveler") return;
if (this.session.isSpectator && role.team === "traveller") return;
// assign to player
const player = this.$store.state.players.players[this.playerIndex];
this.$store.commit("players/update", {
@ -133,8 +133,8 @@ ul.tokens li {
&.demon {
box-shadow: 0 0 10px $demon, 0 0 10px $demon;
}
&.traveler {
box-shadow: 0 0 10px $traveler, 0 0 10px $traveler;
&.traveller {
box-shadow: 0 0 10px $traveller, 0 0 10px $traveller;
}
&:hover {
transform: scale(1.2);
@ -142,7 +142,7 @@ ul.tokens li {
}
}
#townsquare.spectator ul.tokens li.traveler {
#townsquare.spectator ul.tokens li.traveller {
display: none;
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<Modal
class="roles"
v-if="modals.roles && nonTravelers >= 5"
v-if="modals.roles && nonTravellers >= 5"
@close="toggleModal('roles')"
>
<h3>Select the characters for {{ nonTravelers }} players:</h3>
<h3>Select the characters for {{ nonTravellers }} players:</h3>
<ul class="tokens" v-for="(teamRoles, team) in roleSelection" :key="team">
<li class="count" :class="[team]">
{{ teamRoles.reduce((a, { selected }) => a + selected, 0) }} /
{{ game[nonTravelers - 5][team] }}
{{ game[nonTravellers - 5][team] }}
</li>
<li
v-for="role in teamRoles"
@ -45,7 +45,7 @@
class="button"
@click="assignRoles"
:class="{
disabled: selectedRoles > nonTravelers || !selectedRoles
disabled: selectedRoles > nonTravellers || !selectedRoles
}"
>
<font-awesome-icon icon="people-arrows" />
@ -92,7 +92,7 @@ export default {
},
...mapState(["roles", "modals"]),
...mapState("players", ["players"]),
...mapGetters({ nonTravelers: "players/nonTravelers" })
...mapGetters({ nonTravellers: "players/nonTravellers" })
},
methods: {
selectRandomRoles() {
@ -104,8 +104,8 @@ export default {
this.roleSelection[role.team].push(role);
this.$set(role, "selected", 0);
});
delete this.roleSelection["traveler"];
const playerCount = Math.max(5, this.nonTravelers);
delete this.roleSelection["traveller"];
const playerCount = Math.max(5, this.nonTravellers);
const composition = this.game[playerCount - 5];
Object.keys(composition).forEach(team => {
for (let x = 0; x < composition[team]; x++) {
@ -121,7 +121,7 @@ export default {
});
},
assignRoles() {
if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
if (this.selectedRoles <= this.nonTravellers && this.selectedRoles) {
// generate list of selected roles and randomize it
const roles = Object.values(this.roleSelection)
.map(roles =>
@ -135,7 +135,7 @@ export default {
.sort((a, b) => a[0] - b[0])
.map(a => a[1]);
this.players.forEach(player => {
if (player.role.team !== "traveler" && roles.length) {
if (player.role.team !== "traveller" && roles.length) {
const value = roles.pop();
this.$store.commit("players/update", {
player,
@ -194,8 +194,8 @@ ul.tokens {
&.demon {
box-shadow: 0 0 10px $demon, 0 0 10px $demon;
}
&.traveler {
box-shadow: 0 0 10px $traveler, 0 0 10px $traveler;
&.traveller {
box-shadow: 0 0 10px $traveller, 0 0 10px $traveller;
}
&:hover {
transform: scale(1.2);

View File

@ -12,7 +12,7 @@
"id": "bmr",
"name": "Bad Moon Rising",
"author": "The Pandemonium Institute",
"description": "The sun is swallowed by a jagged horizon as another winter's day surrenders to the night. Flecks of orange and red decay into deeper browns, the forest transforming in silent anticipation of the coming snow.\n\nRavenous wolves howl from the bowels of a rocky crevasse beyond the town borders, sending birds scattering from their cozy rooks. Travelers hurry into the inn, seeking shelter from the gathering chill. They warm themselves with hot tea, sweet strains of music and hearty ale, unaware that strange and nefarious eyes stalk them from the ruins of this once great city.\n\nTonight, even the livestock know there is a... Bad Moon Rising.",
"description": "The sun is swallowed by a jagged horizon as another winter's day surrenders to the night. Flecks of orange and red decay into deeper browns, the forest transforming in silent anticipation of the coming snow.\n\nRavenous wolves howl from the bowels of a rocky crevasse beyond the town borders, sending birds scattering from their cozy rooks. Travellers hurry into the inn, seeking shelter from the gathering chill. They warm themselves with hot tea, sweet strains of music and hearty ale, unaware that strange and nefarious eyes stalk them from the ruins of this once great city.\n\nTonight, even the livestock know there is a... Bad Moon Rising.",
"level": "Intermediate",
"roles": [],
"isOfficial": true

View File

@ -293,7 +293,7 @@
"id": "bureaucrat",
"name": "Bureaucrat",
"edition": "tb",
"team": "traveler",
"team": "traveller",
"firstNight": 1,
"firstNightReminder": "The Bureaucrat points to a player. Put the Bureaucrat's '3 votes' reminder by the chosen player's character token.",
"otherNight": 1,
@ -306,7 +306,7 @@
"id": "thief",
"name": "Thief",
"edition": "tb",
"team": "traveler",
"team": "traveller",
"firstNight": 1,
"firstNightReminder": "The Thief points to a player. Put the Thief's 'Negative vote' reminder by the chosen player's character token.",
"otherNight": 1,
@ -319,7 +319,7 @@
"id": "gunslinger",
"name": "Gunslinger",
"edition": "tb",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -332,7 +332,7 @@
"id": "scapegoat",
"name": "Scapegoat",
"edition": "tb",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -345,7 +345,7 @@
"id": "beggar",
"name": "Beggar",
"edition": "tb",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -696,7 +696,7 @@
"id": "apprentice",
"name": "Apprentice",
"edition": "bmr",
"team": "traveler",
"team": "traveller",
"firstNight": 1,
"firstNightReminder": "Show the Apprentice the 'You are' card, then a Townsfolk or Minion token. In the Grimoire, replace the Apprentice token with that character token, and put the Apprentice's 'Is the Apprentice' reminder by that character token.",
"otherNight": 0,
@ -709,7 +709,7 @@
"id": "matron",
"name": "Matron",
"edition": "bmr",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -722,7 +722,7 @@
"id": "judge",
"name": "Judge",
"edition": "bmr",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -735,7 +735,7 @@
"id": "bishop",
"name": "Bishop",
"edition": "bmr",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -749,7 +749,7 @@
"id": "voudon",
"name": "Voudon",
"edition": "bmr",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -1094,7 +1094,7 @@
"id": "barista",
"name": "Barista",
"edition": "snv",
"team": "traveler",
"team": "traveller",
"firstNight": 1,
"firstNightReminder": "Choose a player, wake them and tell them which Barista power is affecting them. Treat them accordingly (sober/healthy/true info or activate their ability twice).",
"otherNight": 1,
@ -1108,7 +1108,7 @@
"id": "harlot",
"name": "Harlot",
"edition": "snv",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 1,
@ -1121,7 +1121,7 @@
"id": "butcher",
"name": "Butcher",
"edition": "snv",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -1134,7 +1134,7 @@
"id": "bonecollector",
"name": "Bone Collector",
"edition": "snv",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 1,
@ -1148,7 +1148,7 @@
"id": "deviant",
"name": "Deviant",
"edition": "snv",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,
@ -1730,7 +1730,7 @@
"id": "gangster",
"name": "Gangster",
"edition": "",
"team": "traveler",
"team": "traveller",
"firstNight": 0,
"firstNightReminder": "",
"otherNight": 0,

View File

@ -21,12 +21,12 @@ const getRolesByEdition = (edition = editionJSON[0]) => {
);
};
const getTravelersNotInEdition = (edition = editionJSON[0]) => {
const getTravellersNotInEdition = (edition = editionJSON[0]) => {
return new Map(
rolesJSON
.filter(
r =>
r.team === "traveler" &&
r.team === "traveller" &&
r.edition !== edition.id &&
!edition.roles.includes(r.id)
)
@ -121,7 +121,7 @@ export default new Vuex.Store({
},
edition: editionJSONbyId.get("tb"),
roles: getRolesByEdition(),
otherTravelers: getTravelersNotInEdition(),
otherTravellers: getTravellersNotInEdition(),
fabled,
jinxes
},
@ -244,10 +244,10 @@ export default new Vuex.Store({
...processedRoles.filter(r => r.team === "fabled").map(r => [r.id, r]),
...fabledJSON.map(role => [role.id, role])
]);
// update extraTravelers map to only show travelers not in this script
state.otherTravelers = new Map(
// update extraTravellers map to only show travellers not in this script
state.otherTravellers = new Map(
rolesJSON
.filter(r => r.team === "traveler" && !roles.some(i => i.id === r.id))
.filter(r => r.team === "traveller" && !roles.some(i => i.id === r.id))
.map(role => [role.id, role])
);
},
@ -255,7 +255,7 @@ export default new Vuex.Store({
if (editionJSONbyId.has(edition.id)) {
state.edition = editionJSONbyId.get(edition.id);
state.roles = getRolesByEdition(state.edition);
state.otherTravelers = getTravelersNotInEdition(state.edition);
state.otherTravellers = getTravellersNotInEdition(state.edition);
} else {
state.edition = edition;
}

View File

@ -18,11 +18,11 @@ const getters = {
alive({ players }) {
return players.filter(player => !player.isDead).length;
},
nonTravelers({ players }) {
const nonTravelers = players.filter(
player => player.role.team !== "traveler"
nonTravellers({ players }) {
const nonTravellers = players.filter(
player => player.role.team !== "traveller"
);
return Math.min(nonTravelers.length, 15);
return Math.min(nonTravellers.length, 15);
},
// calculate a Map of player => night order
nightOrder({ players, fabled }) {
@ -73,7 +73,7 @@ const actions = {
let players;
if (rootState.session.isSpectator) {
players = state.players.map(player => {
if (player.role.team !== "traveler") {
if (player.role.team !== "traveller") {
player.role = {};
}
player.reminders = [];

View File

@ -77,7 +77,7 @@ const mutations = {
addHistory(state, players) {
if (!state.isVoteHistoryAllowed && state.isSpectator) return;
if (!state.nomination || state.lockedVote <= players.length) return;
const isExile = players[state.nomination[1]].role.team === "traveler";
const isExile = players[state.nomination[1]].role.team === "traveller";
state.voteHistory.push({
timestamp: new Date(),
nominator: players[state.nomination[0]].name,

View File

@ -261,7 +261,7 @@ class LiveSession {
isDead: player.isDead,
isVoteless: player.isVoteless,
pronouns: player.pronouns,
...(player.role && player.role.team === "traveler"
...(player.role && player.role.team === "traveller"
? { roleId: player.role.id }
: {})
}));
@ -331,7 +331,7 @@ class LiveSession {
this._store.commit("players/update", { player, property, value });
}
});
// roles are special, because of travelers
// roles are special, because of travellers
if (roleId && player.role.id !== roleId) {
const role =
this._store.state.roles.get(roleId) ||
@ -343,7 +343,7 @@ class LiveSession {
value: role
});
}
} else if (!roleId && player.role.team === "traveler") {
} else if (!roleId && player.role.team === "traveller") {
this._store.commit("players/update", {
player,
property: "role",
@ -448,8 +448,8 @@ class LiveSession {
if (this._isSpectator || property === "reminders") return;
const index = this._store.state.players.players.indexOf(player);
if (property === "role") {
if (value.team && value.team === "traveler") {
// update local gamestate to remember this player as a traveler
if (value.team && value.team === "traveller") {
// update local gamestate to remember this player as a traveller
this._gamestate[index].roleId = value.id;
this._send("player", {
index,
@ -457,7 +457,7 @@ class LiveSession {
value: value.id
});
} else if (this._gamestate[index].roleId) {
// player was previously a traveler
// player was previously a traveller
delete this._gamestate[index].roleId;
this._send("player", { index, property, value: "" });
}
@ -477,9 +477,9 @@ class LiveSession {
if (!this._isSpectator) return;
const player = this._store.state.players.players[index];
if (!player) return;
// special case where a player stops being a traveler
// special case where a player stops being a traveller
if (property === "role") {
if (!value && player.role.team === "traveler") {
if (!value && player.role.team === "traveller") {
// reset to an unknown role
this._store.commit("players/update", {
player,