Merge branch 'develop'

This commit is contained in:
Dave 2023-05-27 18:34:14 +01:00
commit 53c1ad614c
7 changed files with 161 additions and 92 deletions

View file

@ -87,7 +87,7 @@ export default {
Gradients, Gradients,
}, },
computed: { computed: {
...mapState(["grimoire", "session"]), ...mapState(["grimoire", "session", "modals"]),
...mapState("players", ["players"]), ...mapState("players", ["players"]),
}, },
data() { data() {
@ -97,7 +97,7 @@ export default {
}, },
methods: { methods: {
keyup({ key, ctrlKey, metaKey }) { keyup({ key, ctrlKey, metaKey }) {
if (ctrlKey || metaKey) return; if (ctrlKey || metaKey || this.modals.role) return;
switch (key.toLocaleLowerCase()) { switch (key.toLocaleLowerCase()) {
case "g": case "g":
this.$store.commit("toggleGrimoire"); this.$store.commit("toggleGrimoire");

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -1,7 +1,5 @@
<template> <template>
<div class="token" @click="setRole" :class="[role.id]"> <div class="token" @click="setRole" :class="[role.id]">
<span <span
class="icon" class="icon"
v-if="role.id" v-if="role.id"
@ -29,7 +27,6 @@
<span class="leaf-orange" v-if="role.setup"></span> <span class="leaf-orange" v-if="role.setup"></span>
<svg viewBox="0 0 150 150" class="name"> <svg viewBox="0 0 150 150" class="name">
<path <path
d="M 13 75 C 13 160, 138 160, 138 75" d="M 13 75 C 13 160, 138 160, 138 75"
id="curve" id="curve"
@ -43,19 +40,14 @@
class="label mozilla" class="label mozilla"
:font-size="nameToFontSize(role.name)" :font-size="nameToFontSize(role.name)"
> >
<textPath xlink:href="#curve">{{ role.name }}</textPath>
<textPath xlink:href="#curve"> {{ role.name }} </textPath>
</text> </text>
</svg> </svg>
<div class="edition" :class="[`edition-${role.edition}`, role.team]"></div> <div class="edition" :class="[`edition-${role.edition}`, role.team]"></div>
<div class="ability" v-if="role.ability"> {{ role.ability }} </div> <div class="ability" v-if="role.ability">{{ role.ability }}</div>
</div> </div>
</template> </template>
<script> <script>
@ -70,7 +62,7 @@ export default {
}, },
}, },
computed: { computed: {
reminderLeaves: function() { reminderLeaves: function () {
return ( return (
(this.role.reminders || []).length + (this.role.reminders || []).length +
(this.role.remindersGlobal || []).length (this.role.remindersGlobal || []).length
@ -83,7 +75,7 @@ export default {
}, },
methods: { methods: {
nameToFontSize(name) { nameToFontSize(name) {
name && name.length > 10 ? "90%" : "110%"; return name && name.length > 10 ? "90%" : "110%";
}, },
setRole() { setRole() {
this.$emit("set-role"); this.$emit("set-role");
@ -245,4 +237,3 @@ export default {
} }
} }
</style> </style>

View file

@ -21,7 +21,7 @@
: require('../../assets/icons/' + : require('../../assets/icons/' +
(reminder.imageAlt || reminder.role) + (reminder.imageAlt || reminder.role) +
'.png') '.png')
})` })`,
}" }"
></span> ></span>
<span class="text">{{ reminder.name }}</span> <span class="text">{{ reminder.name }}</span>
@ -39,12 +39,14 @@ import { mapMutations, mapState } from "vuex";
* @param role The role for which the reminder should be generated * @param role The role for which the reminder should be generated
* @return {function(*): {image: string|string[]|string|*, role: *, name: *, imageAlt: string|*}} * @return {function(*): {image: string|string[]|string|*, role: *, name: *, imageAlt: string|*}}
*/ */
const mapReminder = ({ id, image, imageAlt }) => name => ({ const mapReminder =
role: id, ({ id, image, imageAlt }) =>
image, (name) => ({
imageAlt, role: id,
name image,
}); imageAlt,
name,
});
export default { export default {
components: { Modal }, components: { Modal },
@ -53,31 +55,40 @@ export default {
availableReminders() { availableReminders() {
let reminders = []; let reminders = [];
const { players, bluffs } = this.$store.state.players; const { players, bluffs } = this.$store.state.players;
this.$store.state.roles.forEach(role => { this.$store.state.roles.forEach((role) => {
// add reminders from player roles // add reminders from player roles
if (players.some(p => p.role.id === role.id)) { if (players.some((p) => p.role.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))]; reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
} }
// add reminders from bluff/other roles // add reminders from bluff/other roles
else if (bluffs.some(bluff => bluff.id === role.id)) { else if (bluffs.some((bluff) => bluff.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))]; reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
} }
// add global reminders // add global reminders
if (role.remindersGlobal && role.remindersGlobal.length) { if (role.remindersGlobal && role.remindersGlobal.length) {
reminders = [ reminders = [
...reminders, ...reminders,
...role.remindersGlobal.map(mapReminder(role)) ...role.remindersGlobal.map(mapReminder(role)),
]; ];
} }
}); });
// add fabled reminders // add fabled reminders
this.$store.state.players.fabled.forEach(role => { this.$store.state.players.fabled.forEach((role) => {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))]; reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
}); });
// add out of script traveler reminders // add out of script traveler reminders
this.$store.state.otherTravelers.forEach(role => { this.$store.state.otherTravelers.forEach((role) => {
if (players.some(p => p.role.id === role.id)) { if (players.some((p) => p.role.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
}
});
// add out of script role reminders
this.$store.state.otherRoles.forEach((role) => {
if (players.some((p) => p.role.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
} else if (bluffs.some((bluff) => bluff.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))]; reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
} }
}); });
@ -88,7 +99,7 @@ export default {
return reminders; return reminders;
}, },
...mapState(["modals", "grimoire"]), ...mapState(["modals", "grimoire"]),
...mapState("players", ["players"]) ...mapState("players", ["players"]),
}, },
methods: { methods: {
addReminder(reminder) { addReminder(reminder) {
@ -104,12 +115,12 @@ export default {
this.$store.commit("players/update", { this.$store.commit("players/update", {
player, player,
property: "reminders", property: "reminders",
value value,
}); });
this.$store.commit("toggleModal", "reminder"); this.$store.commit("toggleModal", "reminder");
}, },
...mapMutations(["toggleModal"]) ...mapMutations(["toggleModal"]),
} },
}; };
</script> </script>

View file

@ -8,7 +8,26 @@
: "bluffing" : "bluffing"
}} }}
</h3> </h3>
<ul class="tokens" v-if="tab === 'editionRoles' || !otherTravelers.size"> <div
v-if="fabled.find((r) => r.id === 'plusone') && tab === 'editionRoles'"
>
<span>Find a Plus One character: </span>
<input type="text" v-model="filter" />
</div>
<ul class="tokens" v-if="filteredRoles.length > 0">
<li
v-for="role in filteredRoles.slice(0, 10)"
:class="[role.team]"
:key="role.id"
@click="setRole(role)"
>
<token :role="role" />
</li>
</ul>
<ul
class="tokens"
v-if="tab === 'editionRoles' || !otherTravelers.length > 0"
>
<li <li
v-for="role in availableRoles" v-for="role in availableRoles"
:class="[role.team]" :class="[role.team]"
@ -18,9 +37,12 @@
<Token :role="role" /> <Token :role="role" />
</li> </li>
</ul> </ul>
<ul class="tokens" v-if="tab === 'otherTravelers' && otherTravelers.size"> <ul
class="tokens"
v-if="tab === 'otherTravelers' && otherTravelers.length > 0"
>
<li <li
v-for="role in otherTravelers.values()" v-for="role in otherTravelers"
:class="[role.team]" :class="[role.team]"
:key="role.id" :key="role.id"
@click="setRole(role)" @click="setRole(role)"
@ -30,7 +52,9 @@
</ul> </ul>
<div <div
class="button-group" class="button-group"
v-if="playerIndex >= 0 && otherTravelers.size && !session.isSpectator" v-if="
playerIndex >= 0 && otherTravelers.length > 0 && !session.isSpectator
"
> >
<span <span
class="button" class="button"
@ -57,15 +81,24 @@ export default {
components: { Token, Modal }, components: { Token, Modal },
props: ["playerIndex"], props: ["playerIndex"],
computed: { computed: {
filteredRoles() {
if (this.filter === "") {
return [];
}
var filteredRoles = this.otherRoles.filter((role) => {
return role.name.toLowerCase().includes(this.filter.toLowerCase());
});
return filteredRoles;
},
availableRoles() { availableRoles() {
const availableRoles = []; const availableRoles = [];
const players = this.$store.state.players.players; const players = this.$store.state.players.players;
this.$store.state.roles.forEach(role => { this.$store.state.roles.forEach((role) => {
// don't show bluff roles that are already assigned to players // don't show bluff roles that are already assigned to players
if ( if (
this.playerIndex >= 0 || this.playerIndex >= 0 ||
(this.playerIndex < 0 && (this.playerIndex < 0 &&
!players.some(player => player.role.id === role.id)) !players.some((player) => player.role.id === role.id))
) { ) {
availableRoles.push(role); availableRoles.push(role);
} }
@ -74,12 +107,14 @@ export default {
return availableRoles; return availableRoles;
}, },
...mapState(["modals", "roles", "session"]), ...mapState(["modals", "roles", "session"]),
...mapState("players", ["players"]), ...mapState("players", ["players", "fabled"]),
...mapState(["otherTravelers"]) ...mapState(["otherTravelers"]),
...mapState(["otherRoles"]),
}, },
data() { data() {
return { return {
tab: "editionRoles" tab: "editionRoles",
filter: "",
}; };
}, },
methods: { methods: {
@ -88,7 +123,7 @@ export default {
// assign to bluff slot (index < 0) // assign to bluff slot (index < 0)
this.$store.commit("players/setBluff", { this.$store.commit("players/setBluff", {
index: this.playerIndex * -1 - 1, index: this.playerIndex * -1 - 1,
role role,
}); });
} else { } else {
if (this.session.isSpectator && role.team === "traveler") return; if (this.session.isSpectator && role.team === "traveler") return;
@ -97,7 +132,7 @@ export default {
this.$store.commit("players/update", { this.$store.commit("players/update", {
player, player,
property: "role", property: "role",
value: role value: role,
}); });
} }
this.tab = "editionRoles"; this.tab = "editionRoles";
@ -105,10 +140,11 @@ export default {
}, },
close() { close() {
this.tab = "editionRoles"; this.tab = "editionRoles";
this.filter = "";
this.toggleModal("role"); this.toggleModal("role");
}, },
...mapMutations(["toggleModal"]) ...mapMutations(["toggleModal"]),
} },
}; };
</script> </script>

View file

@ -142,5 +142,15 @@
"name": "Deus ex Fiasco", "name": "Deus ex Fiasco",
"team": "fabled", "team": "fabled",
"ability": "Once per game, the Storyteller will make a \"mistake\", correct it and publicly admit to it." "ability": "Once per game, the Storyteller will make a \"mistake\", correct it and publicly admit to it."
},
{
"id": "plusone",
"firstNightReminder": "",
"otherNightReminder": "",
"reminders": [],
"setup": true,
"name": "Plussy McOneface",
"team": "fabled",
"ability": "One role from off the script will be in play, it can be either a player or a demon bluff."
} }
] ]

View file

@ -15,45 +15,59 @@ Vue.use(Vuex);
const getRolesByEdition = (edition = editionJSON[0]) => { const getRolesByEdition = (edition = editionJSON[0]) => {
return new Map( return new Map(
rolesJSON rolesJSON
.filter(r => r.edition === edition.id || edition.roles.includes(r.id)) .filter((r) => r.edition === edition.id || edition.roles.includes(r.id))
.sort((a, b) => b.team.localeCompare(a.team)) .sort((a, b) => b.team.localeCompare(a.team))
.map(role => [role.id, role]) .map((role) => [role.id, role])
); );
}; };
const getTravelersNotInEdition = (edition = editionJSON[0]) => { const getTravelersNotInEdition = (edition = editionJSON[0]) => {
return new Map( return rolesJSON.filter(
rolesJSON (r) =>
.filter( r.team === "traveler" &&
r => r.edition !== edition.id &&
r.team === "traveler" && !edition.roles.includes(r.id)
r.edition !== edition.id &&
!edition.roles.includes(r.id)
)
.map(role => [role.id, role])
); );
}; };
const set = key => ({ grimoire }, val) => { const getRolesNotInEdition = (edition = editionJSON[0]) => {
grimoire[key] = val; return rolesJSON.filter(
(r) =>
r.team !== "traveler" &&
r.edition !== edition.id &&
!edition.roles.includes(r.id)
);
}; };
const toggle = key => ({ grimoire }, val) => { const getRoleById = (id) => {
if (val === true || val === false) { return rolesJSON.find((r) => r.id === id);
``;
};
const set =
(key) =>
({ grimoire }, val) => {
grimoire[key] = val; grimoire[key] = val;
} else { };
grimoire[key] = !grimoire[key];
}
};
const clean = id => id.toLocaleLowerCase().replace(/[^a-z0-9]/g, ""); const toggle =
(key) =>
({ grimoire }, val) => {
if (val === true || val === false) {
grimoire[key] = val;
} else {
grimoire[key] = !grimoire[key];
}
};
const clean = (id) => id.toLocaleLowerCase().replace(/[^a-z0-9]/g, "");
// global data maps // global data maps
const editionJSONbyId = new Map( const editionJSONbyId = new Map(
editionJSON.map(edition => [edition.id, edition]) editionJSON.map((edition) => [edition.id, edition])
); );
const rolesJSONbyId = new Map(rolesJSON.map(role => [role.id, role])); const rolesJSONbyId = new Map(rolesJSON.map((role) => [role.id, role]));
const fabled = new Map(fabledJSON.map(role => [role.id, role])); const fabled = new Map(fabledJSON.map((role) => [role.id, role]));
// jinxes // jinxes
let jinxes = {}; let jinxes = {};
@ -65,7 +79,7 @@ try {
jinxes = new Map( jinxes = new Map(
jinxesJSON.map(({ id, hatred }) => [ jinxesJSON.map(({ id, hatred }) => [
clean(id), clean(id),
new Map(hatred.map(({ id, reason }) => [clean(id), reason])) new Map(hatred.map(({ id, reason }) => [clean(id), reason])),
]) ])
); );
// }); // });
@ -88,13 +102,13 @@ const customRole = {
remindersGlobal: [], remindersGlobal: [],
setup: false, setup: false,
team: "townsfolk", team: "townsfolk",
isCustom: true isCustom: true,
}; };
export default new Vuex.Store({ export default new Vuex.Store({
modules: { modules: {
players, players,
session session,
}, },
state: { state: {
grimoire: { grimoire: {
@ -106,7 +120,7 @@ export default new Vuex.Store({
isMuted: false, isMuted: false,
isImageOptIn: false, isImageOptIn: false,
zoom: 0, zoom: 0,
background: "" background: "",
}, },
modals: { modals: {
edition: false, edition: false,
@ -117,13 +131,14 @@ export default new Vuex.Store({
reminder: false, reminder: false,
role: false, role: false,
roles: false, roles: false,
voteHistory: false voteHistory: false,
}, },
edition: editionJSONbyId.get("tb"), edition: editionJSONbyId.get("tb"),
roles: getRolesByEdition(), roles: getRolesByEdition(),
otherTravelers: getTravelersNotInEdition(), otherTravelers: getTravelersNotInEdition(),
otherRoles: getRolesNotInEdition(),
fabled, fabled,
jinxes jinxes,
}, },
getters: { getters: {
/** /**
@ -138,9 +153,9 @@ export default new Vuex.Store({
const strippedProps = [ const strippedProps = [
"firstNightReminder", "firstNightReminder",
"otherNightReminder", "otherNightReminder",
"isCustom" "isCustom",
]; ];
roles.forEach(role => { roles.forEach((role) => {
if (!role.isCustom) { if (!role.isCustom) {
customRoles.push({ id: role.id }); customRoles.push({ id: role.id });
} else { } else {
@ -159,7 +174,8 @@ export default new Vuex.Store({
}); });
return customRoles; return customRoles;
}, },
rolesJSONbyId: () => rolesJSONbyId rolesJSONbyId: () => rolesJSONbyId,
roleById: getRoleById,
}, },
mutations: { mutations: {
setZoom: set("zoom"), setZoom: set("zoom"),
@ -188,7 +204,7 @@ export default new Vuex.Store({
setCustomRoles(state, roles) { setCustomRoles(state, roles) {
const processedRoles = roles const processedRoles = roles
// replace numerical role object keys with matching key names // replace numerical role object keys with matching key names
.map(role => { .map((role) => {
if (role[0]) { if (role[0]) {
const customKeys = Object.keys(customRole); const customKeys = Object.keys(customRole);
const mappedRole = {}; const mappedRole = {};
@ -203,19 +219,19 @@ export default new Vuex.Store({
} }
}) })
// clean up role.id // clean up role.id
.map(role => { .map((role) => {
role.id = clean(role.id); 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
.map( .map(
role => (role) =>
rolesJSONbyId.get(role.id) || rolesJSONbyId.get(role.id) ||
state.roles.get(role.id) || state.roles.get(role.id) ||
Object.assign({}, customRole, role) Object.assign({}, customRole, role)
) )
// default empty icons and placeholders, clean up firstNight / otherNight // default empty icons and placeholders, clean up firstNight / otherNight
.map(role => { .map((role) => {
if (rolesJSONbyId.get(role.id)) return role; if (rolesJSONbyId.get(role.id)) return role;
role.imageAlt = // map team to generic icon role.imageAlt = // map team to generic icon
{ {
@ -223,32 +239,36 @@ export default new Vuex.Store({
outsider: "outsider", outsider: "outsider",
minion: "minion", minion: "minion",
demon: "evil", demon: "evil",
fabled: "fabled" fabled: "fabled",
}[role.team] || "custom"; }[role.team] || "custom";
role.firstNight = Math.abs(role.firstNight); role.firstNight = Math.abs(role.firstNight);
role.otherNight = Math.abs(role.otherNight); role.otherNight = Math.abs(role.otherNight);
return role; return role;
}) })
// filter out roles that don't match an existing role and also don't have name/ability/team // filter out roles that don't match an existing role and also don't have name/ability/team
.filter(role => role.name && role.ability && role.team) .filter((role) => role.name && role.ability && role.team)
// sort by team // sort by team
.sort((a, b) => b.team.localeCompare(a.team)); .sort((a, b) => b.team.localeCompare(a.team));
// convert to Map without Fabled // convert to Map without Fabled
state.roles = new Map( state.roles = new Map(
processedRoles processedRoles
.filter(role => role.team !== "fabled") .filter((role) => role.team !== "fabled")
.map(role => [role.id, role]) .map((role) => [role.id, role])
); );
// update Fabled to include custom Fabled from this script // update Fabled to include custom Fabled from this script
state.fabled = new Map([ state.fabled = new Map([
...processedRoles.filter(r => r.team === "fabled").map(r => [r.id, r]), ...processedRoles
...fabledJSON.map(role => [role.id, role]) .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 // update extraTravelers map to only show travelers not in this script
state.otherTravelers = new Map( state.otherTravelers = new Map(
rolesJSON rolesJSON
.filter(r => r.team === "traveler" && !roles.some(i => i.id === r.id)) .filter(
.map(role => [role.id, role]) (r) => r.team === "traveler" && !roles.some((i) => i.id === r.id)
)
.map((role) => [role.id, role])
); );
}, },
setEdition(state, edition) { setEdition(state, edition) {
@ -256,11 +276,12 @@ export default new Vuex.Store({
state.edition = editionJSONbyId.get(edition.id); state.edition = editionJSONbyId.get(edition.id);
state.roles = getRolesByEdition(state.edition); state.roles = getRolesByEdition(state.edition);
state.otherTravelers = getTravelersNotInEdition(state.edition); state.otherTravelers = getTravelersNotInEdition(state.edition);
state.otherRoles = getRolesNotInEdition(state.edition);
} else { } else {
state.edition = edition; state.edition = edition;
} }
state.modals.edition = false; state.modals.edition = false;
} },
}, },
plugins: [persistence, socket] plugins: [persistence, socket],
}); });