show jinxed interactions on character reference modal

This commit is contained in:
Steffen 2021-05-10 21:58:16 +02:00
parent 184a77a039
commit d86ee3b7cf
No known key found for this signature in database
GPG Key ID: 764D74E98267DFC6
4 changed files with 266 additions and 10 deletions

View File

@ -6,6 +6,7 @@
- added global animation toggle for better performance
- added record vote history toggle to session menu, and clear vote history button
- add support for custom Fabled characters
- show Jinxed interactions on character reference list
---

View File

@ -50,6 +50,40 @@
<li :class="[team]"></li>
</ul>
</div>
<div class="team jinxed" v-if="jinxed.length">
<aside>
<h4>Jinxed</h4>
</aside>
<ul>
<li v-for="(jinx, index) in jinxed" :key="index">
<span
class="icon"
:style="{
backgroundImage: `url(${require('../../assets/icons/' +
jinx.first.id +
'.png')})`
}"
></span>
<span
class="icon"
:style="{
backgroundImage: `url(${require('../../assets/icons/' +
jinx.second.id +
'.png')})`
}"
></span>
<div class="role">
<span class="name"
>{{ jinx.first.name }} & {{ jinx.second.name }}</span
>
<span class="ability">{{ jinx.reason }}</span>
</div>
</li>
<li></li>
<li></li>
</ul>
</div>
</Modal>
</template>
@ -62,6 +96,27 @@ export default {
Modal
},
computed: {
/**
* Return a list of jinxes in the form of role IDs and a reason
* @returns {*[]} [{first, second, reason}]
*/
jinxed: function() {
const jinxed = [];
this.roles.forEach(role => {
if (this.jinxes.get(role.id)) {
this.jinxes.get(role.id).forEach((reason, second) => {
if (this.roles.get(second)) {
jinxed.push({
first: role,
second: this.roles.get(second),
reason
});
}
});
}
});
return jinxed;
},
rolesGrouped: function() {
const rolesGrouped = {};
this.roles.forEach(role => {
@ -85,7 +140,7 @@ export default {
});
return players;
},
...mapState(["roles", "modals", "edition", "grimoire"]),
...mapState(["roles", "modals", "edition", "grimoire", "jinxes"]),
...mapState("players", ["players"])
},
methods: {
@ -147,6 +202,15 @@ h3 {
}
}
.jinxed {
.name {
color: $fabled;
}
aside {
background: linear-gradient(-90deg, $fabled, transparent);
}
}
.team {
display: flex;
align-items: stretch;
@ -180,6 +244,12 @@ h3 {
transform-origin: center;
font-size: 80%;
}
&.jinxed {
.icon {
margin: 0 -5px;
}
}
}
ul {

163
src/hatred.json Normal file
View File

@ -0,0 +1,163 @@
[{
"id": "Chambermaid",
"hatred": [{
"id": "Mathematician",
"reason": "The Chambermaid learns if the Mathematician wakes tonight or not, even though the Chambermaid wakes first."
}]
},
{
"id": "Butler",
"hatred": [{
"id": "Cannibal",
"reason": "If the Cannibal gains the Butler ability, the Cannibal learns this."
}]
},
{
"id": "Mutant",
"hatred": [{
"id": "Undertaker",
"reason": "If the Mutant causes a second execution, the Undertaker learns either one or both executed characters (Storyteller's choice)."
}]
},
{
"id": "Lunatic",
"hatred": [{
"id": "Mathematician",
"reason": "The Mathematician learns if the Lunatic attacks a different player(s) than the real Demon attacked."
}]
},
{
"id": "Pit-Hag",
"hatred": [{
"id": "Politician",
"reason": "A Pit-Hag can not create an evil Politician."
},
{
"id": "Heretic",
"reason": "A Pit-Hag can not create a Heretic. "
}
]
},
{
"id": "Cerenovus",
"hatred": [{
"id": "Undertaker",
"reason": "If the Cerenovus causes a second execution, the Undertaker learns either one or both executed characters (Storyteller's choice)."
},
{
"id": "Goblin",
"reason": "The Cerenovus may choose to make a player mad that they are the Goblin."
}
]
},
{
"id": "Leviathan",
"hatred": [{
"id": "Soldier",
"reason": "If Leviathan nominates and executes the Soldier, the Soldier does not die."
},
{
"id": "Monk",
"reason": "If Leviathan nominates and executes the player the Monk chose, that player does not die."
},
{
"id": "Innkeeper",
"reason": "If Leviathan nominates and executes a player the Innkeeper chose, that player does not die."
},
{
"id": "Ravenkeeper",
"reason": "If Leviathan is in play & the Ravenkeeper dies by execution, they wake that night to use their ability."
},
{
"id": "Sage",
"reason": "If Leviathan is in play & the Sage dies by execution, they wake that night to use their ability."
},
{
"id": "Mayor",
"reason": "If Leviathan is in play & no execution occurs on day 5, good wins."
}
]
},
{
"id": "Lil' Monsta",
"hatred": [{
"id": "Scarlet Woman",
"reason": "If there are 5 or more players alive and the player holding the Lil' Monsta token dies, the Scarlet Woman is given the Lil' Monsta token tonight."
},
{
"id": "Poppy Grower",
"reason": "If the Poppy Grower is in play, Minions don't wake together. They are woken one by one, until one of them chooses to take the Lil' Monsta token."
}
]
},
{
"id": "Lycanthrope",
"hatred": [{
"id": "Gambler",
"reason": "If the Lycanthrope is alive and the Gambler kills themself at night, no other players can die tonight."
}]
},
{
"id": "Legion",
"hatred": [{
"id": "Preacher",
"reason": "Only 1 jinxed character can be in play. Evil players start knowing which player and character it is."
}]
},
{
"id": "Fang Gu",
"hatred": [{
"id": "Scarlet Woman",
"reason": "If the Fang Gu chooses an Outsider and dies, the Scarlet Woman does not become the Fang Gu."
}]
},
{
"id": "Spy",
"hatred": [{
"id": "Poppy Grower",
"reason": "If the Poppy Grower is in play, the Spy does not see the Grimoire until the Poppy Grower dies."
},
{
"id": "Heretic",
"reason": "Only 1 jinxed character can be in play."
}
]
},
{
"id": "Widow",
"hatred": [{
"id": "Poppy Grower",
"reason": "If the Poppy Grower is in play, the Widow does not see the Grimoire until the Poppy Grower dies."
},
{
"id": "Heretic",
"reason": "Only 1 jinxed character can be in play."
}
]
},
{
"id": "Godfather",
"hatred": [{
"id": "Heretic",
"reason": "Only 1 jinxed character can be in play."
}]
},
{
"id": "Marionette",
"hatred": [
{
"id": "Lil' Monsta",
"reason": "The Marionette neighbors a Minion, not the Demon. The Marionette is not woken to choose who takes the Lil' Monsta token."
},
{
"id": "Poppy Grower",
"reason": "When the Poppy Grower dies, the Demon learns the Marionette but the Marionette learns nothing."
},
{
"id": "Balloonist",
"reason": "If the Marionette thinks that they are the Balloonist, +1 Outsider was added."
}
]
}
]

View File

@ -7,16 +7,10 @@ import session from "./modules/session";
import editionJSON from "../editions.json";
import rolesJSON from "../roles.json";
import fabledJSON from "../fabled.json";
import jinxesJSON from "../hatred.json";
Vue.use(Vuex);
// global data maps
const editionJSONbyId = new Map(
editionJSON.map(edition => [edition.id, edition])
);
const rolesJSONbyId = new Map(rolesJSON.map(role => [role.id, role]));
const fabled = new Map(fabledJSON.map(role => [role.id, role]));
// helper functions
const getRolesByEdition = (edition = editionJSON[0]) => {
return new Map(
@ -52,6 +46,33 @@ const toggle = key => ({ grimoire }, val) => {
}
};
const clean = id => id.toLocaleLowerCase().replace(/[^a-z0-9]/g, "");
// global data maps
const editionJSONbyId = new Map(
editionJSON.map(edition => [edition.id, edition])
);
const rolesJSONbyId = new Map(rolesJSON.map(role => [role.id, role]));
const fabled = new Map(fabledJSON.map(role => [role.id, role]));
// jinxes
let jinxes = {};
try {
// Note: can't fetch live list due to lack of CORS headers
// fetch("https://bloodontheclocktower.com/script/data/hatred.json")
// .then(res => res.json())
// .then(jinxesJSON => {
jinxes = new Map(
jinxesJSON.map(({ id, hatred }) => [
clean(id),
new Map(hatred.map(({ id, reason }) => [clean(id), reason]))
])
);
// });
} catch (e) {
console.error("couldn't load jinxes", e);
}
// base definition for custom roles
const customRole = {
id: "",
@ -101,7 +122,8 @@ export default new Vuex.Store({
edition: editionJSONbyId.get("tb"),
roles: getRolesByEdition(),
otherTravelers: getTravelersNotInEdition(),
fabled
fabled,
jinxes
},
getters: {
/**
@ -182,7 +204,7 @@ export default new Vuex.Store({
})
// clean up role.id
.map(role => {
role.id = role.id.toLocaleLowerCase().replace(/[^a-z0-9]/g, "");
role.id = clean(role.id);
return role;
})
// map existing roles to base definition or pre-populate custom roles to ensure all properties