mirror of https://github.com/bra1n/townsquare.git
Merge pull request #171 from bra1n/jinxes
show jinxed interactions on character reference modal
This commit is contained in:
commit
2d98f44d14
|
@ -6,6 +6,7 @@
|
||||||
- added global animation toggle for better performance
|
- added global animation toggle for better performance
|
||||||
- added record vote history toggle to session menu, and clear vote history button
|
- added record vote history toggle to session menu, and clear vote history button
|
||||||
- add support for custom Fabled characters
|
- add support for custom Fabled characters
|
||||||
|
- show Jinxed interactions on character reference list
|
||||||
- add 'marked for execution' indicator
|
- add 'marked for execution' indicator
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -50,6 +50,40 @@
|
||||||
<li :class="[team]"></li>
|
<li :class="[team]"></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -62,6 +96,27 @@ export default {
|
||||||
Modal
|
Modal
|
||||||
},
|
},
|
||||||
computed: {
|
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() {
|
rolesGrouped: function() {
|
||||||
const rolesGrouped = {};
|
const rolesGrouped = {};
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach(role => {
|
||||||
|
@ -85,7 +140,7 @@ export default {
|
||||||
});
|
});
|
||||||
return players;
|
return players;
|
||||||
},
|
},
|
||||||
...mapState(["roles", "modals", "edition", "grimoire"]),
|
...mapState(["roles", "modals", "edition", "grimoire", "jinxes"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -147,6 +202,15 @@ h3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jinxed {
|
||||||
|
.name {
|
||||||
|
color: $fabled;
|
||||||
|
}
|
||||||
|
aside {
|
||||||
|
background: linear-gradient(-90deg, $fabled, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.team {
|
.team {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
@ -180,6 +244,12 @@ h3 {
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.jinxed {
|
||||||
|
.icon {
|
||||||
|
margin: 0 -5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
|
|
|
@ -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."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -7,16 +7,10 @@ import session from "./modules/session";
|
||||||
import editionJSON from "../editions.json";
|
import editionJSON from "../editions.json";
|
||||||
import rolesJSON from "../roles.json";
|
import rolesJSON from "../roles.json";
|
||||||
import fabledJSON from "../fabled.json";
|
import fabledJSON from "../fabled.json";
|
||||||
|
import jinxesJSON from "../hatred.json";
|
||||||
|
|
||||||
Vue.use(Vuex);
|
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
|
// helper functions
|
||||||
const getRolesByEdition = (edition = editionJSON[0]) => {
|
const getRolesByEdition = (edition = editionJSON[0]) => {
|
||||||
return new Map(
|
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
|
// base definition for custom roles
|
||||||
const customRole = {
|
const customRole = {
|
||||||
id: "",
|
id: "",
|
||||||
|
@ -101,7 +122,8 @@ export default new Vuex.Store({
|
||||||
edition: editionJSONbyId.get("tb"),
|
edition: editionJSONbyId.get("tb"),
|
||||||
roles: getRolesByEdition(),
|
roles: getRolesByEdition(),
|
||||||
otherTravelers: getTravelersNotInEdition(),
|
otherTravelers: getTravelersNotInEdition(),
|
||||||
fabled
|
fabled,
|
||||||
|
jinxes
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
/**
|
/**
|
||||||
|
@ -182,7 +204,7 @@ export default new Vuex.Store({
|
||||||
})
|
})
|
||||||
// clean up role.id
|
// clean up role.id
|
||||||
.map(role => {
|
.map(role => {
|
||||||
role.id = role.id.toLocaleLowerCase().replace(/[^a-z0-9]/g, "");
|
role.id = clean(role.id);
|
||||||
return role;
|
return role;
|
||||||
})
|
})
|
||||||
// map existing roles to base definition or pre-populate custom roles to ensure all properties
|
// map existing roles to base definition or pre-populate custom roles to ensure all properties
|
||||||
|
|
Loading…
Reference in New Issue