mirror of
https://github.com/bra1n/townsquare.git
synced 2025-04-04 22:24:36 +00:00
Merge branch 'develop' into night_order
This commit is contained in:
commit
a4dc926d73
44 changed files with 4716 additions and 19054 deletions
10
.eslintrc.js
10
.eslintrc.js
|
@ -1,14 +1,16 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
node: true
|
node: true,
|
||||||
},
|
},
|
||||||
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
|
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2020
|
ecmaVersion: 2020,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
|
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
}
|
"vue/multi-word-component-names": "off",
|
||||||
|
"vue/no-reserved-component-names": "off",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
||||||
- name: Setup node version
|
- name: Setup node version
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '18'
|
||||||
- name: Checkout 🛎️
|
- name: Checkout 🛎️
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install and Build 🔧
|
- name: Install and Build 🔧
|
||||||
|
|
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -2,6 +2,21 @@
|
||||||
|
|
||||||
## Upcomming Version
|
## Upcomming Version
|
||||||
- Updating night order (and its print)
|
- Updating night order (and its print)
|
||||||
|
- Correcting automatic adding/deletion of Fabled
|
||||||
|
|
||||||
|
### Version 3.17.0
|
||||||
|
|
||||||
|
- Updating english jinxes
|
||||||
|
- Adding an asterisk in the roles reference
|
||||||
|
- Correcting the message when someone wants to exile a Traveller
|
||||||
|
- Correcting Leviathan's english description
|
||||||
|
- Correcting "Late Night Drive By"'s name
|
||||||
|
- Adding a token "Used" to the Doomsayer
|
||||||
|
- Updated packages & Dockerfile for node >=18
|
||||||
|
|
||||||
|
### Version 3.16.0 (merged upstream 2.16.2)
|
||||||
|
- fixed custom script format to support new script tool JSON
|
||||||
|
- updated packages to be compatible with Node >= 18 again
|
||||||
|
|
||||||
### Version 3.15.0
|
### Version 3.15.0
|
||||||
- Night order bubbles always on for storyteller
|
- Night order bubbles always on for storyteller
|
||||||
|
@ -159,7 +174,7 @@ Add localization capabilities and french translation
|
||||||
---
|
---
|
||||||
|
|
||||||
### Version 2.16.1
|
### Version 2.16.1
|
||||||
Updated character night order to be consistent with script tool
|
- Updated character night order to be consistent with script tool
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
11
Dockerfile
11
Dockerfile
|
@ -1,10 +1,11 @@
|
||||||
FROM node:16
|
FROM node:18
|
||||||
RUN apt update && apt install -y\
|
RUN apt update && apt install -y\
|
||||||
git
|
git\
|
||||||
WORKDIR /app
|
&& apt clean
|
||||||
|
WORKDIR /app/townsquare
|
||||||
COPY package*.json .
|
COPY package*.json .
|
||||||
RUN npm install
|
|
||||||
# npm rebuild avoids having misconfigurations if npm install has been run in the folder from windows before building the docker image
|
|
||||||
RUN npm rebuild
|
RUN npm rebuild
|
||||||
|
RUN npm clean-install
|
||||||
|
# npm rebuild avoids having misconfigurations if npm install has been run in the folder from windows before building the docker image
|
||||||
COPY . .
|
COPY . .
|
||||||
CMD ["npm","run","serve"]
|
CMD ["npm","run","serve"]
|
||||||
|
|
|
@ -6,12 +6,15 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- "${SSH_KEYS}:/root/.ssh"
|
- "${SSH_KEYS}:/root/.ssh"
|
||||||
|
- botc_front:/app/townsquare
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
ports:
|
ports:
|
||||||
- "${NODE_PORT}:8080"
|
- "${NODE_PORT}:8080"
|
||||||
working_dir: /app
|
working_dir: /app/townsquare
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
max-size: "200k"
|
max-size: "200k"
|
||||||
|
volumes:
|
||||||
|
botc_front:
|
||||||
|
|
22390
package-lock.json
generated
22390
package-lock.json
generated
File diff suppressed because it is too large
Load diff
22
package.json
22
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "townsquare",
|
"name": "townsquare",
|
||||||
"version": "3.15.0",
|
"version": "3.17.0",
|
||||||
"description": "Blood on the Clocktower Town Square",
|
"description": "Blood on the Clocktower Town Square",
|
||||||
"author": "Pingumaskt",
|
"author": "Pingumaskt",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -15,22 +15,22 @@
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.1",
|
"@fortawesome/free-brands-svg-icons": "^5.15.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||||
"@fortawesome/vue-fontawesome": "^0.1.10",
|
"@fortawesome/vue-fontawesome": "^0.1.10",
|
||||||
"@vue/cli-service": "^4.5.9",
|
"@vue/cli-service": "^5.0.8",
|
||||||
"prom-client": "^13.0.0",
|
"prom-client": "^13.0.0",
|
||||||
"sass": "^1.30.0",
|
"sass": "^1.30.0",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
"vue-template-compiler": "^2.6.12",
|
"vue-template-compiler": "^2.7.15",
|
||||||
"vuex": "^3.6.0",
|
"vuex": "^3.6.0",
|
||||||
"ws": "^7.4.6"
|
"ws": "^7.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-eslint": "^4.5.9",
|
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||||
"@vue/eslint-config-prettier": "^6.0.0",
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^8.53.0",
|
||||||
"eslint-plugin-prettier": "^3.2.0",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^9.18.1",
|
||||||
"prettier": "^1.19.1"
|
"prettier": "^3.0.3"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"botc",
|
"botc",
|
||||||
|
@ -44,6 +44,6 @@
|
||||||
"url": "https://github.com//bra1n/townsquare.git"
|
"url": "https://github.com//bra1n/townsquare.git"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16"
|
"node": "^18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
48
src/App.vue
48
src/App.vue
|
@ -5,11 +5,11 @@
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:class="{
|
:class="{
|
||||||
night: grimoire.isNight,
|
night: grimoire.isNight,
|
||||||
static: grimoire.isStatic
|
static: grimoire.isStatic,
|
||||||
}"
|
}"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url('${background}')`,
|
backgroundImage: `url('${background}')`,
|
||||||
backgroundColor: `${backgroundColor}`
|
backgroundColor: `${backgroundColor}`,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
|
@ -70,24 +70,24 @@ export default {
|
||||||
Menu,
|
Menu,
|
||||||
EditionModal,
|
EditionModal,
|
||||||
RolesModal,
|
RolesModal,
|
||||||
Gradients
|
Gradients,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["grimoire", "session", "edition"]),
|
...mapState(["grimoire", "session", "edition"]),
|
||||||
...mapState("players", ["players"]),
|
...mapState("players", ["players"]),
|
||||||
background: function() {
|
background: function () {
|
||||||
if (this.grimoire.isStreamerMode) {
|
if (this.grimoire.isStreamerMode) {
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
return this.grimoire.background || this.edition.background || "none";
|
return this.grimoire.background || this.edition.background || "none";
|
||||||
},
|
},
|
||||||
backgroundColor: function() {
|
backgroundColor: function () {
|
||||||
return this.grimoire.isStreamerMode ? "#00FF00" : "transparent";
|
return this.grimoire.isStreamerMode ? "#00FF00" : "transparent";
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
version
|
version,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -136,8 +136,8 @@ export default {
|
||||||
case "escape":
|
case "escape":
|
||||||
this.$store.commit("toggleModal");
|
this.$store.commit("toggleModal");
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -147,7 +147,8 @@ export default {
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Papyrus";
|
font-family: "Papyrus";
|
||||||
src: url("assets/fonts/papyrus.eot"); /* IE9*/
|
src: url("assets/fonts/papyrus.eot"); /* IE9*/
|
||||||
src: url("assets/fonts/papyrus.eot?#iefix") format("embedded-opentype"),
|
src:
|
||||||
|
url("assets/fonts/papyrus.eot?#iefix") format("embedded-opentype"),
|
||||||
/* IE6-IE8 */ url("assets/fonts/papyrus.woff2") format("woff2"),
|
/* IE6-IE8 */ url("assets/fonts/papyrus.woff2") format("woff2"),
|
||||||
/* chrome firefox */ url("assets/fonts/papyrus.woff") format("woff"),
|
/* chrome firefox */ url("assets/fonts/papyrus.woff") format("woff"),
|
||||||
/* chrome firefox */ url("assets/fonts/papyrus.ttf") format("truetype"),
|
/* chrome firefox */ url("assets/fonts/papyrus.ttf") format("truetype"),
|
||||||
|
@ -270,13 +271,12 @@ ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: solid 0.125em transparent;
|
border: solid 0.125em transparent;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
box-shadow: inset 0 1px 1px #9c9c9c, 0 0 10px #000;
|
box-shadow:
|
||||||
background: radial-gradient(
|
inset 0 1px 1px #9c9c9c,
|
||||||
at 0 -15%,
|
0 0 10px #000;
|
||||||
rgba(#fff, 0.07) 70%,
|
background:
|
||||||
rgba(#fff, 0) 71%
|
radial-gradient(at 0 -15%, rgba(#fff, 0.07) 70%, rgba(#fff, 0) 71%) 0 0/ 80%
|
||||||
)
|
90% no-repeat content-box,
|
||||||
0 0/ 80% 90% no-repeat content-box,
|
|
||||||
linear-gradient(#4e4e4e, #040404) content-box,
|
linear-gradient(#4e4e4e, #040404) content-box,
|
||||||
linear-gradient(#292929, #010101) border-box;
|
linear-gradient(#292929, #010101) border-box;
|
||||||
color: white;
|
color: white;
|
||||||
|
@ -303,7 +303,8 @@ ul {
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
&.townsfolk {
|
&.townsfolk {
|
||||||
background: radial-gradient(
|
background:
|
||||||
|
radial-gradient(
|
||||||
at 0 -15%,
|
at 0 -15%,
|
||||||
rgba(255, 255, 255, 0.07) 70%,
|
rgba(255, 255, 255, 0.07) 70%,
|
||||||
rgba(255, 255, 255, 0) 71%
|
rgba(255, 255, 255, 0) 71%
|
||||||
|
@ -311,13 +312,16 @@ ul {
|
||||||
0 0/80% 90% no-repeat content-box,
|
0 0/80% 90% no-repeat content-box,
|
||||||
linear-gradient(#0031ad, rgba(5, 0, 0, 0.22)) content-box,
|
linear-gradient(#0031ad, rgba(5, 0, 0, 0.22)) content-box,
|
||||||
linear-gradient(#292929, #001142) border-box;
|
linear-gradient(#292929, #001142) border-box;
|
||||||
box-shadow: inset 0 1px 1px #002c9c, 0 0 10px #000;
|
box-shadow:
|
||||||
|
inset 0 1px 1px #002c9c,
|
||||||
|
0 0 10px #000;
|
||||||
&:hover:not(.disabled) {
|
&:hover:not(.disabled) {
|
||||||
color: #008cf7;
|
color: #008cf7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.demon {
|
&.demon {
|
||||||
background: radial-gradient(
|
background:
|
||||||
|
radial-gradient(
|
||||||
at 0 -15%,
|
at 0 -15%,
|
||||||
rgba(255, 255, 255, 0.07) 70%,
|
rgba(255, 255, 255, 0.07) 70%,
|
||||||
rgba(255, 255, 255, 0) 71%
|
rgba(255, 255, 255, 0) 71%
|
||||||
|
@ -325,7 +329,9 @@ ul {
|
||||||
0 0/80% 90% no-repeat content-box,
|
0 0/80% 90% no-repeat content-box,
|
||||||
linear-gradient(#ad0000, rgba(5, 0, 0, 0.22)) content-box,
|
linear-gradient(#ad0000, rgba(5, 0, 0, 0.22)) content-box,
|
||||||
linear-gradient(#292929, #420000) border-box;
|
linear-gradient(#292929, #420000) border-box;
|
||||||
box-shadow: inset 0 1px 1px #9c0000, 0 0 10px #000;
|
box-shadow:
|
||||||
|
inset 0 1px 1px #9c0000,
|
||||||
|
0 0 10px #000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
src/assets/icons/dawn.png
Normal file
BIN
src/assets/icons/dawn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
BIN
src/assets/icons/dusk.png
Normal file
BIN
src/assets/icons/dusk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
|
@ -2,7 +2,7 @@
|
||||||
{
|
{
|
||||||
"id": "_meta",
|
"id": "_meta",
|
||||||
"logo": "https://cdn.shopify.com/oxygen/57467011226/524442/h11pcta59/build/_assets/Godfather_website_purple-3JVCH57E.png",
|
"logo": "https://cdn.shopify.com/oxygen/57467011226/524442/h11pcta59/build/_assets/Godfather_website_purple-3JVCH57E.png",
|
||||||
"name": "Late night drive by 1.6",
|
"name": "Late night drive by",
|
||||||
"author": "Aero"
|
"author": "Aero"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
timerName: String,
|
timerName: String,
|
||||||
timerDuration: Number
|
timerDuration: Number,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
style() {
|
style() {
|
||||||
return `--timer: ${this.timerDuration}`;
|
return `--timer: ${this.timerDuration}`;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,10 @@ export default {
|
||||||
["demon", "#ce0100", "#000"],
|
["demon", "#ce0100", "#000"],
|
||||||
["townsfolk", "#1f65ff", "#000"],
|
["townsfolk", "#1f65ff", "#000"],
|
||||||
["minion", "#ff6900", "#000"],
|
["minion", "#ff6900", "#000"],
|
||||||
["default", "#4E4E4E", "#000"]
|
["default", "#4E4E4E", "#000"],
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -30,14 +30,14 @@ import { mapMutations, mapState, mapGetters } from "vuex";
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["locale"]),
|
...mapState(["locale"]),
|
||||||
...mapGetters({ nightOrder: "players/nightOrder" })
|
...mapGetters({ nightOrder: "players/nightOrder" }),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
language: window.navigator.userLanguage || window.navigator.language
|
language: window.navigator.userLanguage || window.navigator.language,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: mapMutations(["toggleMenu"])
|
methods: mapMutations(["toggleMenu"]),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,9 @@
|
||||||
class="nomlog-summary"
|
class="nomlog-summary"
|
||||||
v-show="session.voteHistory.length && session.sessionId"
|
v-show="session.voteHistory.length && session.sessionId"
|
||||||
@click="toggleModal('voteHistory')"
|
@click="toggleModal('voteHistory')"
|
||||||
:title="
|
:title="`${session.voteHistory.length} recent ${
|
||||||
`${session.voteHistory.length} recent ${
|
session.voteHistory.length == 1 ? 'nomination' : 'nominations'
|
||||||
session.voteHistory.length == 1 ? 'nomination' : 'nominations'
|
}`"
|
||||||
}`
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="book-dead" />
|
<font-awesome-icon icon="book-dead" />
|
||||||
{{ session.voteHistory.length }}
|
{{ session.voteHistory.length }}
|
||||||
|
@ -17,15 +15,13 @@
|
||||||
class="session"
|
class="session"
|
||||||
:class="{
|
:class="{
|
||||||
spectator: session.isSpectator,
|
spectator: session.isSpectator,
|
||||||
reconnecting: session.isReconnecting
|
reconnecting: session.isReconnecting,
|
||||||
}"
|
}"
|
||||||
v-if="session.sessionId"
|
v-if="session.sessionId"
|
||||||
@click="leaveSession"
|
@click="leaveSession"
|
||||||
:title="
|
:title="`${session.playerCount} other players in this session${
|
||||||
`${session.playerCount} other players in this session${
|
session.ping ? ' (' + session.ping + 'ms latency)' : ''
|
||||||
session.ping ? ' (' + session.ping + 'ms latency)' : ''
|
}`"
|
||||||
}`
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="broadcast-tower" />
|
<font-awesome-icon icon="broadcast-tower" />
|
||||||
{{ session.playerCount }}
|
{{ session.playerCount }}
|
||||||
|
@ -76,7 +72,7 @@
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
:icon="[
|
:icon="[
|
||||||
'fas',
|
'fas',
|
||||||
grimoire.isOrganVoteMode ? 'check-square' : 'square'
|
grimoire.isOrganVoteMode ? 'check-square' : 'square',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</em>
|
</em>
|
||||||
|
@ -87,7 +83,7 @@
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
:icon="[
|
:icon="[
|
||||||
'fas',
|
'fas',
|
||||||
grimoire.isNightOrder ? 'check-square' : 'square'
|
grimoire.isNightOrder ? 'check-square' : 'square',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</em>
|
</em>
|
||||||
|
@ -108,7 +104,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li @click="setBackground">
|
<li @click="setBackground">
|
||||||
{{ locale.menu.grimoire.background }}
|
{{ locale.menu.grimoire.background }}
|
||||||
<em><font-awesome-icon icon="image"/></em>
|
<em><font-awesome-icon icon="image" /></em>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!edition.isOfficial" @click="imageOptIn">
|
<li v-if="!edition.isOfficial" @click="imageOptIn">
|
||||||
<small>{{ locale.menu.grimoire.customImages }}</small>
|
<small>{{ locale.menu.grimoire.customImages }}</small>
|
||||||
|
@ -116,7 +112,7 @@
|
||||||
><font-awesome-icon
|
><font-awesome-icon
|
||||||
:icon="[
|
:icon="[
|
||||||
'fas',
|
'fas',
|
||||||
grimoire.isImageOptIn ? 'check-square' : 'square'
|
grimoire.isImageOptIn ? 'check-square' : 'square',
|
||||||
]"
|
]"
|
||||||
/></em>
|
/></em>
|
||||||
</li>
|
</li>
|
||||||
|
@ -126,7 +122,7 @@
|
||||||
><font-awesome-icon
|
><font-awesome-icon
|
||||||
:icon="[
|
:icon="[
|
||||||
'fas',
|
'fas',
|
||||||
grimoire.isStreamerMode ? 'check-square' : 'square'
|
grimoire.isStreamerMode ? 'check-square' : 'square',
|
||||||
]"
|
]"
|
||||||
/></em>
|
/></em>
|
||||||
</li>
|
</li>
|
||||||
|
@ -178,11 +174,11 @@
|
||||||
</li>
|
</li>
|
||||||
<li @click="copySessionUrl">
|
<li @click="copySessionUrl">
|
||||||
{{ locale.menu.session.link }}
|
{{ locale.menu.session.link }}
|
||||||
<em><font-awesome-icon icon="copy"/></em>
|
<em><font-awesome-icon icon="copy" /></em>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!session.isSpectator" @click="distributeRoles">
|
<li v-if="!session.isSpectator" @click="distributeRoles">
|
||||||
{{ locale.menu.session.sendRoles }}
|
{{ locale.menu.session.sendRoles }}
|
||||||
<em><font-awesome-icon icon="theater-masks"/></em>
|
<em><font-awesome-icon icon="theater-masks" /></em>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="session.voteHistory.length || !session.isSpectator"
|
v-if="session.voteHistory.length || !session.isSpectator"
|
||||||
|
@ -205,11 +201,11 @@
|
||||||
</li>
|
</li>
|
||||||
<li @click="randomizeSeatings" v-if="players.length > 2">
|
<li @click="randomizeSeatings" v-if="players.length > 2">
|
||||||
{{ locale.menu.players.randomize }}
|
{{ locale.menu.players.randomize }}
|
||||||
<em><font-awesome-icon icon="dice"/></em>
|
<em><font-awesome-icon icon="dice" /></em>
|
||||||
</li>
|
</li>
|
||||||
<li @click="clearPlayers" v-if="players.length">
|
<li @click="clearPlayers" v-if="players.length">
|
||||||
{{ locale.menu.players.removeAll }}
|
{{ locale.menu.players.removeAll }}
|
||||||
<em><font-awesome-icon icon="trash-alt"/></em>
|
<em><font-awesome-icon icon="trash-alt" /></em>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -229,11 +225,11 @@
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!session.isSpectator" @click="toggleModal('fabled')">
|
<li v-if="!session.isSpectator" @click="toggleModal('fabled')">
|
||||||
{{ locale.menu.characters.addFabled }}
|
{{ locale.menu.characters.addFabled }}
|
||||||
<em><font-awesome-icon icon="dragon"/></em>
|
<em><font-awesome-icon icon="dragon" /></em>
|
||||||
</li>
|
</li>
|
||||||
<li @click="clearRoles" v-if="players.length">
|
<li @click="clearRoles" v-if="players.length">
|
||||||
{{ locale.menu.characters.removeAll }}
|
{{ locale.menu.characters.removeAll }}
|
||||||
<em><font-awesome-icon icon="trash-alt"/></em>
|
<em><font-awesome-icon icon="trash-alt" /></em>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -250,7 +246,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li @click="toggleModal('gameState')">
|
<li @click="toggleModal('gameState')">
|
||||||
{{ locale.menu.help.gameState }}
|
{{ locale.menu.help.gameState }}
|
||||||
<em><font-awesome-icon icon="file-code"/></em>
|
<em><font-awesome-icon icon="file-code" /></em>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://discord.gg/gD3AB8qCrw" target="_blank">
|
<a href="https://discord.gg/gD3AB8qCrw" target="_blank">
|
||||||
|
@ -284,11 +280,11 @@ import { mapMutations, mapState } from "vuex";
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["grimoire", "session", "edition", "locale"]),
|
...mapState(["grimoire", "session", "edition", "locale"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tab: "grimoire"
|
tab: "grimoire",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -302,7 +298,7 @@ export default {
|
||||||
if (this.session.sessionId) return;
|
if (this.session.sessionId) return;
|
||||||
const sessionId = prompt(
|
const sessionId = prompt(
|
||||||
this.locale.prompt.createSession,
|
this.locale.prompt.createSession,
|
||||||
Math.round(Math.random() * 10000)
|
Math.round(Math.random() * 10000),
|
||||||
);
|
);
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
this.$store.commit("session/clearVoteHistory");
|
this.$store.commit("session/clearVoteHistory");
|
||||||
|
@ -326,7 +322,7 @@ export default {
|
||||||
(() => {
|
(() => {
|
||||||
this.$store.commit("session/distributeRoles", false);
|
this.$store.commit("session/distributeRoles", false);
|
||||||
}).bind(this),
|
}).bind(this),
|
||||||
2000
|
2000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -409,9 +405,9 @@ export default {
|
||||||
"toggleNightOrder",
|
"toggleNightOrder",
|
||||||
"toggleStatic",
|
"toggleStatic",
|
||||||
"setZoom",
|
"setZoom",
|
||||||
"toggleModal"
|
"toggleModal",
|
||||||
])
|
]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
'no-vote': player.isVoteless,
|
'no-vote': player.isVoteless,
|
||||||
you: session.sessionId && player.id && player.id === session.playerId,
|
you: session.sessionId && player.id && player.id === session.playerId,
|
||||||
'vote-yes': session.votes[index],
|
'vote-yes': session.votes[index],
|
||||||
'vote-lock': voteLocked
|
'vote-lock': voteLocked,
|
||||||
},
|
},
|
||||||
player.role.team
|
player.role.team,
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="shroud" @click="toggleStatus()"></div>
|
<div class="shroud" @click="toggleStatus()"></div>
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
class="night-order first"
|
class="night-order first"
|
||||||
v-if="
|
v-if="
|
||||||
nightOrder.get(player).first &&
|
nightOrder.get(player).first &&
|
||||||
(grimoire.isNightOrder || !session.isSpectator)
|
(grimoire.isNightOrder || !session.isSpectator)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<em>{{ nightOrder.get(player).first }}.</em>
|
<em>{{ nightOrder.get(player).first }}.</em>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
class="night-order other"
|
class="night-order other"
|
||||||
v-if="
|
v-if="
|
||||||
nightOrder.get(player).other &&
|
nightOrder.get(player).other &&
|
||||||
(grimoire.isNightOrder || !session.isSpectator)
|
(grimoire.isNightOrder || !session.isSpectator)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<em>{{ nightOrder.get(player).other }}.</em>
|
<em>{{ nightOrder.get(player).other }}.</em>
|
||||||
|
@ -53,8 +53,8 @@
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
v-if="
|
v-if="
|
||||||
!grimoire.isOrganVoteMode ||
|
!grimoire.isOrganVoteMode ||
|
||||||
!session.isSpectator ||
|
!session.isSpectator ||
|
||||||
player.id == session.playerId
|
player.id == session.playerId
|
||||||
"
|
"
|
||||||
icon="hand-paper"
|
icon="hand-paper"
|
||||||
class="vote"
|
class="vote"
|
||||||
|
@ -64,8 +64,8 @@
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
v-if="
|
v-if="
|
||||||
grimoire.isOrganVoteMode &&
|
grimoire.isOrganVoteMode &&
|
||||||
session.isSpectator &&
|
session.isSpectator &&
|
||||||
player.id !== session.playerId
|
player.id !== session.playerId
|
||||||
"
|
"
|
||||||
icon="question"
|
icon="question"
|
||||||
class="vote"
|
class="vote"
|
||||||
|
@ -75,8 +75,8 @@
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
v-if="
|
v-if="
|
||||||
!grimoire.isOrganVoteMode ||
|
!grimoire.isOrganVoteMode ||
|
||||||
!session.isSpectator ||
|
!session.isSpectator ||
|
||||||
player.id == session.playerId
|
player.id == session.playerId
|
||||||
"
|
"
|
||||||
icon="times"
|
icon="times"
|
||||||
class="vote"
|
class="vote"
|
||||||
|
@ -86,8 +86,8 @@
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
v-if="
|
v-if="
|
||||||
grimoire.isOrganVoteMode &&
|
grimoire.isOrganVoteMode &&
|
||||||
session.isSpectator &&
|
session.isSpectator &&
|
||||||
player.id !== session.playerId
|
player.id !== session.playerId
|
||||||
"
|
"
|
||||||
icon="question"
|
icon="question"
|
||||||
class="vote"
|
class="vote"
|
||||||
|
@ -159,7 +159,7 @@
|
||||||
@click="changePronouns"
|
@click="changePronouns"
|
||||||
v-if="
|
v-if="
|
||||||
!session.isSpectator ||
|
!session.isSpectator ||
|
||||||
(session.isSpectator && player.id === session.playerId)
|
(session.isSpectator && player.id === session.playerId)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="venus-mars" />{{
|
<font-awesome-icon icon="venus-mars" />{{
|
||||||
|
@ -230,10 +230,12 @@
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
reminder.image && grimoire.isImageOptIn
|
reminder.image && grimoire.isImageOptIn
|
||||||
? reminder.image
|
? reminder.image
|
||||||
: require('../assets/icons/' +
|
: require(
|
||||||
(reminder.imageAlt || reminder.role) +
|
'../assets/icons/' +
|
||||||
'.png')
|
(reminder.imageAlt || reminder.role) +
|
||||||
})`
|
'.png',
|
||||||
|
)
|
||||||
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span class="text">{{ reminder.name }}</span>
|
<span class="text">{{ reminder.name }}</span>
|
||||||
|
@ -252,22 +254,22 @@ import { mapGetters, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Token
|
Token,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
player: {
|
player: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState("players", ["players"]),
|
...mapState("players", ["players"]),
|
||||||
...mapState(["grimoire", "session", "locale"]),
|
...mapState(["grimoire", "session", "locale"]),
|
||||||
...mapGetters({ nightOrder: "players/nightOrder" }),
|
...mapGetters({ nightOrder: "players/nightOrder" }),
|
||||||
index: function() {
|
index: function () {
|
||||||
return this.players.indexOf(this.player);
|
return this.players.indexOf(this.player);
|
||||||
},
|
},
|
||||||
voteLocked: function() {
|
voteLocked: function () {
|
||||||
const session = this.session;
|
const session = this.session;
|
||||||
const players = this.players.length;
|
const players = this.players.length;
|
||||||
if (!session.nomination) return false;
|
if (!session.nomination) return false;
|
||||||
|
@ -275,7 +277,7 @@ export default {
|
||||||
(this.index - 1 + players - session.nomination[1]) % players;
|
(this.index - 1 + players - session.nomination[1]) % players;
|
||||||
return indexAdjusted < session.lockedVote - 1;
|
return indexAdjusted < session.lockedVote - 1;
|
||||||
},
|
},
|
||||||
zoom: function() {
|
zoom: function () {
|
||||||
if (this.players.length < 7) {
|
if (this.players.length < 7) {
|
||||||
return { width: 18 + this.grimoire.zoom + "vmin" };
|
return { width: 18 + this.grimoire.zoom + "vmin" };
|
||||||
} else if (this.players.length <= 10) {
|
} else if (this.players.length <= 10) {
|
||||||
|
@ -285,12 +287,12 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
return { width: 12 + this.grimoire.zoom + "vmin" };
|
return { width: 12 + this.grimoire.zoom + "vmin" };
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isSwap: false
|
isSwap: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -346,7 +348,7 @@ export default {
|
||||||
this.$store.commit("players/update", {
|
this.$store.commit("players/update", {
|
||||||
player: this.player,
|
player: this.player,
|
||||||
property,
|
property,
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
if (closeMenu) {
|
if (closeMenu) {
|
||||||
this.isMenuOpen = false;
|
this.isMenuOpen = false;
|
||||||
|
@ -383,10 +385,10 @@ export default {
|
||||||
if (!this.voteLocked) return;
|
if (!this.voteLocked) return;
|
||||||
this.$store.commit("session/voteSync", [
|
this.$store.commit("session/voteSync", [
|
||||||
this.index,
|
this.index,
|
||||||
!this.session.votes[this.index]
|
!this.session.votes[this.index],
|
||||||
]);
|
]);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -924,7 +926,10 @@ li.move:not(.from) .player .overlay svg.move {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15%;
|
top: 15%;
|
||||||
text-shadow: 0 1px 1px #f6dfbd, 0 -1px 1px #f6dfbd, 1px 0 1px #f6dfbd,
|
text-shadow:
|
||||||
|
0 1px 1px #f6dfbd,
|
||||||
|
0 -1px 1px #f6dfbd,
|
||||||
|
1px 0 1px #f6dfbd,
|
||||||
-1px 0 1px #f6dfbd;
|
-1px 0 1px #f6dfbd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
role.image && grimoire.isImageOptIn
|
role.image && grimoire.isImageOptIn
|
||||||
? role.image
|
? role.image
|
||||||
: require('../assets/icons/' + (role.imageAlt || role.id) + '.png')
|
: require('../assets/icons/' + (role.imageAlt || role.id) + '.png')
|
||||||
})`
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
|
@ -54,29 +54,29 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
role: {
|
role: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
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
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
...mapState(["grimoire"])
|
...mapState(["grimoire"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
nameToFontSize: name => (name && name.length > 10 ? "90%" : "110%")
|
nameToFontSize: (name) => (name && name.length > 10 ? "90%" : "110%"),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setRole() {
|
setRole() {
|
||||||
this.$emit("set-role");
|
this.$emit("set-role");
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
edition.logo && grimoire.isImageOptIn
|
edition.logo && grimoire.isImageOptIn
|
||||||
? edition.logo
|
? edition.logo
|
||||||
: require('../assets/editions/' + edition.id + '.png')
|
: require('../assets/editions/' + edition.id + '.png')
|
||||||
})`
|
})`,
|
||||||
}"
|
}"
|
||||||
></li>
|
></li>
|
||||||
<li v-if="players.length - teams.traveler < 5">
|
<li v-if="players.length - teams.traveler < 5">
|
||||||
|
@ -99,15 +99,15 @@ import Countdown from "./Countdown";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Countdown
|
Countdown,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
teams: function() {
|
teams: function () {
|
||||||
const { players } = this.$store.state.players;
|
const { players } = this.$store.state.players;
|
||||||
const nonTravelers = this.$store.getters["players/nonTravelers"];
|
const nonTravelers = this.$store.getters["players/nonTravelers"];
|
||||||
const alive = players.filter(player => player.isDead !== true).length;
|
const alive = players.filter((player) => player.isDead !== true).length;
|
||||||
const aliveNT = players.filter(
|
const aliveNT = players.filter(
|
||||||
player => player.isDead !== true && player.role.team !== "traveler"
|
(player) => player.isDead !== true && player.role.team !== "traveler",
|
||||||
).length;
|
).length;
|
||||||
return {
|
return {
|
||||||
...gameJSON[nonTravelers - 5],
|
...gameJSON[nonTravelers - 5],
|
||||||
|
@ -117,16 +117,16 @@ export default {
|
||||||
votes:
|
votes:
|
||||||
alive +
|
alive +
|
||||||
players.filter(
|
players.filter(
|
||||||
player => player.isDead === true && player.isVoteless !== true
|
(player) => player.isDead === true && player.isVoteless !== true,
|
||||||
).length
|
).length,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
countdownStyle: function() {
|
countdownStyle: function () {
|
||||||
return `--timer: ${this.$store.state.grimoire.timer.duration}`;
|
return `--timer: ${this.$store.state.grimoire.timer.duration}`;
|
||||||
},
|
},
|
||||||
...mapState(["edition", "grimoire", "locale"]),
|
...mapState(["edition", "grimoire", "locale"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -153,7 +153,10 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-shadow: 0 2px 1px black, 0 -2px 1px black, 2px 0 1px black,
|
text-shadow:
|
||||||
|
0 2px 1px black,
|
||||||
|
0 -2px 1px black,
|
||||||
|
2px 0 1px black,
|
||||||
-2px 0 1px black;
|
-2px 0 1px black;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
:class="{
|
:class="{
|
||||||
public: grimoire.isPublic,
|
public: grimoire.isPublic,
|
||||||
spectator: session.isSpectator,
|
spectator: session.isSpectator,
|
||||||
vote: session.nomination
|
vote: session.nomination,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<ul class="circle" :class="['size-' + players.length]">
|
<ul class="circle" :class="['size-' + players.length]">
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
from: Math.max(swap, move, nominate) === index,
|
from: Math.max(swap, move, nominate) === index,
|
||||||
swap: swap > -1,
|
swap: swap > -1,
|
||||||
move: move > -1,
|
move: move > -1,
|
||||||
nominate: nominate > -1
|
nominate: nominate > -1,
|
||||||
}"
|
}"
|
||||||
></Player>
|
></Player>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
class="night-order first"
|
class="night-order first"
|
||||||
v-if="
|
v-if="
|
||||||
nightOrder.get(role).first &&
|
nightOrder.get(role).first &&
|
||||||
(grimoire.isNightOrder || !session.isSpectator)
|
(grimoire.isNightOrder || !session.isSpectator)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<em>{{ nightOrder.get(role).first }}.</em>
|
<em>{{ nightOrder.get(role).first }}.</em>
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
class="night-order other"
|
class="night-order other"
|
||||||
v-if="
|
v-if="
|
||||||
nightOrder.get(role).other &&
|
nightOrder.get(role).other &&
|
||||||
(grimoire.isNightOrder || !session.isSpectator)
|
(grimoire.isNightOrder || !session.isSpectator)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<em>{{ nightOrder.get(role).other }}.</em>
|
<em>{{ nightOrder.get(role).other }}.</em>
|
||||||
|
@ -184,12 +184,12 @@ export default {
|
||||||
Player,
|
Player,
|
||||||
Token,
|
Token,
|
||||||
RoleModal,
|
RoleModal,
|
||||||
ReminderModal
|
ReminderModal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({ nightOrder: "players/nightOrder" }),
|
...mapGetters({ nightOrder: "players/nightOrder" }),
|
||||||
...mapState(["grimoire", "roles", "session", "locale"]),
|
...mapState(["grimoire", "roles", "session", "locale"]),
|
||||||
...mapState("players", ["players", "bluffs", "fabled"])
|
...mapState("players", ["players", "bluffs", "fabled"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -204,7 +204,7 @@ export default {
|
||||||
timerName: "Timer",
|
timerName: "Timer",
|
||||||
timerDuration: 1,
|
timerDuration: 1,
|
||||||
timerOn: false,
|
timerOn: false,
|
||||||
timerEnder: false
|
timerEnder: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -259,7 +259,7 @@ export default {
|
||||||
if (this.session.isSpectator || this.session.lockedVote) return;
|
if (this.session.isSpectator || this.session.lockedVote) return;
|
||||||
if (
|
if (
|
||||||
confirm(
|
confirm(
|
||||||
`Do you really want to remove ${this.players[playerIndex].name}?`
|
`Do you really want to remove ${this.players[playerIndex].name}?`,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const { nomination } = this.session;
|
const { nomination } = this.session;
|
||||||
|
@ -274,7 +274,7 @@ export default {
|
||||||
// update nomination array if removed player has lower index
|
// update nomination array if removed player has lower index
|
||||||
this.$store.commit("session/setNomination", [
|
this.$store.commit("session/setNomination", [
|
||||||
nomination[0] > playerIndex ? nomination[0] - 1 : nomination[0],
|
nomination[0] > playerIndex ? nomination[0] - 1 : nomination[0],
|
||||||
nomination[1] > playerIndex ? nomination[1] - 1 : nomination[1]
|
nomination[1] > playerIndex ? nomination[1] - 1 : nomination[1],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,7 +290,7 @@ export default {
|
||||||
if (this.session.nomination) {
|
if (this.session.nomination) {
|
||||||
// update nomination if one of the involved players is swapped
|
// update nomination if one of the involved players is swapped
|
||||||
const swapTo = this.players.indexOf(to);
|
const swapTo = this.players.indexOf(to);
|
||||||
const updatedNomination = this.session.nomination.map(nom => {
|
const updatedNomination = this.session.nomination.map((nom) => {
|
||||||
if (nom === this.swap) return swapTo;
|
if (nom === this.swap) return swapTo;
|
||||||
if (nom === swapTo) return this.swap;
|
if (nom === swapTo) return this.swap;
|
||||||
return nom;
|
return nom;
|
||||||
|
@ -304,7 +304,7 @@ export default {
|
||||||
}
|
}
|
||||||
this.$store.commit("players/swap", [
|
this.$store.commit("players/swap", [
|
||||||
this.swap,
|
this.swap,
|
||||||
this.players.indexOf(to)
|
this.players.indexOf(to),
|
||||||
]);
|
]);
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,7 @@ export default {
|
||||||
if (this.session.nomination) {
|
if (this.session.nomination) {
|
||||||
// update nomination if it is affected by the move
|
// update nomination if it is affected by the move
|
||||||
const moveTo = this.players.indexOf(to);
|
const moveTo = this.players.indexOf(to);
|
||||||
const updatedNomination = this.session.nomination.map(nom => {
|
const updatedNomination = this.session.nomination.map((nom) => {
|
||||||
if (nom === this.move) return moveTo;
|
if (nom === this.move) return moveTo;
|
||||||
if (nom > this.move && nom <= moveTo) return nom - 1;
|
if (nom > this.move && nom <= moveTo) return nom - 1;
|
||||||
if (nom < this.move && nom >= moveTo) return nom + 1;
|
if (nom < this.move && nom >= moveTo) return nom + 1;
|
||||||
|
@ -333,7 +333,7 @@ export default {
|
||||||
}
|
}
|
||||||
this.$store.commit("players/move", [
|
this.$store.commit("players/move", [
|
||||||
this.move,
|
this.move,
|
||||||
this.players.indexOf(to)
|
this.players.indexOf(to),
|
||||||
]);
|
]);
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
|
@ -359,7 +359,7 @@ export default {
|
||||||
renameTimer() {
|
renameTimer() {
|
||||||
let newName = prompt(
|
let newName = prompt(
|
||||||
this.locale.townsquare.timer.prompt.name,
|
this.locale.townsquare.timer.prompt.name,
|
||||||
this.timerName
|
this.timerName,
|
||||||
);
|
);
|
||||||
if (newName === "") {
|
if (newName === "") {
|
||||||
newName = this.locale.townsquare.timer.default.text;
|
newName = this.locale.townsquare.timer.default.text;
|
||||||
|
@ -372,7 +372,8 @@ export default {
|
||||||
},
|
},
|
||||||
setNominationTimer() {
|
setNominationTimer() {
|
||||||
this.timerDuration = 2;
|
this.timerDuration = 2;
|
||||||
this.timerName = this.timerName = this.locale.townsquare.timer.nominations.text;
|
this.timerName = this.timerName =
|
||||||
|
this.locale.townsquare.timer.nominations.text;
|
||||||
},
|
},
|
||||||
setDuskTimer() {
|
setDuskTimer() {
|
||||||
this.timerDuration = 1;
|
this.timerDuration = 1;
|
||||||
|
@ -399,7 +400,7 @@ export default {
|
||||||
let timerText = this.locale.townsquare.timer.debate.text;
|
let timerText = this.locale.townsquare.timer.debate.text;
|
||||||
timerText = timerText.replace(
|
timerText = timerText.replace(
|
||||||
"$accusee",
|
"$accusee",
|
||||||
this.players[this.session.nomination[1]].name
|
this.players[this.session.nomination[1]].name,
|
||||||
);
|
);
|
||||||
this.timerName = timerText;
|
this.timerName = timerText;
|
||||||
},
|
},
|
||||||
|
@ -422,12 +423,13 @@ export default {
|
||||||
this.$store.commit("setTimer", {});
|
this.$store.commit("setTimer", {});
|
||||||
this.timerOn = false;
|
this.timerOn = false;
|
||||||
clearTimeout(this.timerEnder);
|
clearTimeout(this.timerEnder);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@use "sass:math";
|
||||||
@import "../vars.scss";
|
@import "../vars.scss";
|
||||||
|
|
||||||
#townsquare {
|
#townsquare {
|
||||||
|
@ -473,14 +475,14 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin on-circle($item-count) {
|
@mixin on-circle($item-count) {
|
||||||
$angle: (360 / $item-count);
|
$angle: math.div(360, $item-count);
|
||||||
$rot: 0;
|
$rot: 0;
|
||||||
|
|
||||||
// rotation and tooltip placement
|
// rotation and tooltip placement
|
||||||
@for $i from 1 through $item-count {
|
@for $i from 1 through $item-count {
|
||||||
&:nth-child(#{$i}) {
|
&:nth-child(#{$i}) {
|
||||||
transform: rotate($rot * 1deg);
|
transform: rotate($rot * 1deg);
|
||||||
@if $i - 1 <= $item-count / 2 {
|
@if $i - 1 <= math.div($item-count, 2) {
|
||||||
// first half of players
|
// first half of players
|
||||||
z-index: $item-count - $i + 1;
|
z-index: $item-count - $i + 1;
|
||||||
// open menu on the left
|
// open menu on the left
|
||||||
|
@ -544,15 +546,15 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// move reminders closer to the sides of the circle
|
// move reminders closer to the sides of the circle
|
||||||
$q: $item-count / 4;
|
$q: math.div($item-count, 4);
|
||||||
$x: $i - 1;
|
$x: $i - 1;
|
||||||
@if $x < $q or ($x >= $item-count / 2 and $x < $q * 3) {
|
@if $x < $q or ($x >= math.div($item-count, 2) and $x < $q * 3) {
|
||||||
.player {
|
.player {
|
||||||
margin-bottom: -10% + 20% * (1 - ($x % $q / $q));
|
margin-bottom: -10% + 20% * (1 - math.div($x % $q, $q));
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
.player {
|
.player {
|
||||||
margin-bottom: -10% + 20% * ($x % $q / $q);
|
margin-bottom: -10% + 20% * math.div($x % $q, $q);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,21 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="overlay">
|
<div class="overlay">
|
||||||
<audio src="../assets/sounds/countdown.mp3" preload="auto"></audio>
|
<audio src="../assets/sounds/countdown.mp3" preload="auto"></audio>
|
||||||
<em class="blue">{{ nominator.name }}</em> {{ locale.vote.nominated }}
|
<em class="blue">{{ nominator.name }}</em>
|
||||||
|
{{
|
||||||
|
nominee.role.team == "traveler"
|
||||||
|
? locale.vote.callexile
|
||||||
|
: locale.vote.nominates
|
||||||
|
}}
|
||||||
<em>{{ nominee.name }}</em
|
<em>{{ nominee.name }}</em
|
||||||
>!
|
>{{ locale.vote.exclam }}
|
||||||
<br />
|
<br />
|
||||||
<em
|
<em
|
||||||
class="blue"
|
class="blue"
|
||||||
v-if="
|
v-if="
|
||||||
!grimoire.isOrganVoteMode ||
|
!grimoire.isOrganVoteMode ||
|
||||||
nominee.role.team == 'traveler' ||
|
nominee.role.team == 'traveler' ||
|
||||||
!session.isSpectator
|
!session.isSpectator
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ voters.length }} {{ locale.vote.votes }}
|
{{ voters.length }} {{ locale.vote.votes }}
|
||||||
|
@ -71,7 +76,7 @@
|
||||||
<div
|
<div
|
||||||
class="button"
|
class="button"
|
||||||
:class="{
|
:class="{
|
||||||
disabled: session.nomination[1] === session.markedPlayer
|
disabled: session.nomination[1] === session.markedPlayer,
|
||||||
}"
|
}"
|
||||||
@click="setMarked"
|
@click="setMarked"
|
||||||
>
|
>
|
||||||
|
@ -137,44 +142,46 @@ import Countdown from "./Countdown";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Countdown
|
Countdown,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState("players", ["players"]),
|
...mapState("players", ["players"]),
|
||||||
...mapState(["session", "grimoire", "locale"]),
|
...mapState(["session", "grimoire", "locale"]),
|
||||||
...mapGetters({ alive: "players/alive" }),
|
...mapGetters({ alive: "players/alive" }),
|
||||||
nominator: function() {
|
nominator: function () {
|
||||||
return this.players[this.session.nomination[0]];
|
return this.players[this.session.nomination[0]];
|
||||||
},
|
},
|
||||||
nominatorStyle: function() {
|
nominatorStyle: function () {
|
||||||
const players = this.players.length;
|
const players = this.players.length;
|
||||||
const nomination = this.session.nomination[0];
|
const nomination = this.session.nomination[0];
|
||||||
return {
|
return {
|
||||||
transform: `rotate(${Math.round((nomination / players) * 360)}deg)`,
|
transform: `rotate(${Math.round((nomination / players) * 360)}deg)`,
|
||||||
transitionDuration: this.session.votingSpeed - 100 + "ms"
|
transitionDuration: this.session.votingSpeed - 100 + "ms",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
nominee: function() {
|
nominee: function () {
|
||||||
return this.players[this.session.nomination[1]];
|
return this.players[this.session.nomination[1]];
|
||||||
},
|
},
|
||||||
nomineeStyle: function() {
|
nomineeStyle: function () {
|
||||||
const players = this.players.length;
|
const players = this.players.length;
|
||||||
const nomination = this.session.nomination[1];
|
const nomination = this.session.nomination[1];
|
||||||
const lock = this.session.lockedVote;
|
const lock = this.session.lockedVote;
|
||||||
const rotation = (360 * (nomination + Math.min(lock, players))) / players;
|
const rotation = (360 * (nomination + Math.min(lock, players))) / players;
|
||||||
return {
|
return {
|
||||||
transform: `rotate(${Math.round(rotation)}deg)`,
|
transform: `rotate(${Math.round(rotation)}deg)`,
|
||||||
transitionDuration: this.session.votingSpeed - 100 + "ms"
|
transitionDuration: this.session.votingSpeed - 100 + "ms",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
player: function() {
|
player: function () {
|
||||||
return this.players.find(p => p.id === this.session.playerId);
|
return this.players.find((p) => p.id === this.session.playerId);
|
||||||
},
|
},
|
||||||
currentVote: function() {
|
currentVote: function () {
|
||||||
const index = this.players.findIndex(p => p.id === this.session.playerId);
|
const index = this.players.findIndex(
|
||||||
|
(p) => p.id === this.session.playerId,
|
||||||
|
);
|
||||||
return index >= 0 ? !!this.session.votes[index] : undefined;
|
return index >= 0 ? !!this.session.votes[index] : undefined;
|
||||||
},
|
},
|
||||||
canVote: function() {
|
canVote: function () {
|
||||||
if (!this.player) return false;
|
if (!this.player) return false;
|
||||||
if (this.player.isVoteless && this.nominee.role.team !== "traveler")
|
if (this.player.isVoteless && this.nominee.role.team !== "traveler")
|
||||||
return false;
|
return false;
|
||||||
|
@ -185,26 +192,27 @@ export default {
|
||||||
(index - 1 + players - session.nomination[1]) % players;
|
(index - 1 + players - session.nomination[1]) % players;
|
||||||
return indexAdjusted >= session.lockedVote - 1;
|
return indexAdjusted >= session.lockedVote - 1;
|
||||||
},
|
},
|
||||||
voters: function() {
|
voters: function () {
|
||||||
const nomination = this.session.nomination[1];
|
const nomination = this.session.nomination[1];
|
||||||
const voters = Array(this.players.length)
|
const voters = Array(this.players.length)
|
||||||
.fill("")
|
.fill("")
|
||||||
.map((x, index) =>
|
.map((x, index) =>
|
||||||
this.session.votes[index] ? this.players[index].name : ""
|
this.session.votes[index] ? this.players[index].name : "",
|
||||||
);
|
);
|
||||||
const reorder = [
|
const reorder = [
|
||||||
...voters.slice(nomination + 1),
|
...voters.slice(nomination + 1),
|
||||||
...voters.slice(0, nomination + 1)
|
...voters.slice(0, nomination + 1),
|
||||||
];
|
];
|
||||||
return (this.session.lockedVote
|
return (
|
||||||
? reorder.slice(0, this.session.lockedVote - 1)
|
this.session.lockedVote
|
||||||
: reorder
|
? reorder.slice(0, this.session.lockedVote - 1)
|
||||||
).filter(n => !!n);
|
: reorder
|
||||||
}
|
).filter((n) => !!n);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
voteTimer: null
|
voteTimer: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -254,7 +262,9 @@ export default {
|
||||||
},
|
},
|
||||||
vote(vote) {
|
vote(vote) {
|
||||||
if (!this.canVote) return false;
|
if (!this.canVote) return false;
|
||||||
const index = this.players.findIndex(p => p.id === this.session.playerId);
|
const index = this.players.findIndex(
|
||||||
|
(p) => p.id === this.session.playerId,
|
||||||
|
);
|
||||||
if (index >= 0 && !!this.session.votes[index] !== vote) {
|
if (index >= 0 && !!this.session.votes[index] !== vote) {
|
||||||
this.$store.commit("session/voteSync", [index, vote]);
|
this.$store.commit("session/voteSync", [index, vote]);
|
||||||
}
|
}
|
||||||
|
@ -270,8 +280,8 @@ export default {
|
||||||
},
|
},
|
||||||
removeMarked() {
|
removeMarked() {
|
||||||
this.$store.commit("session/setMarkedPlayer", -1);
|
this.$store.commit("session/setMarkedPlayer", -1);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -289,7 +299,10 @@ export default {
|
||||||
background: url("../assets/demon-head.png") center center no-repeat;
|
background: url("../assets/demon-head.png") center center no-repeat;
|
||||||
background-size: auto 75%;
|
background-size: auto 75%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: 0 1px 2px #000000, 0 -1px 2px #000000, 1px 0 2px #000000,
|
text-shadow:
|
||||||
|
0 1px 2px #000000,
|
||||||
|
0 -1px 2px #000000,
|
||||||
|
1px 0 2px #000000,
|
||||||
-1px 0 2px #000000;
|
-1px 0 2px #000000;
|
||||||
|
|
||||||
.mark .button {
|
.mark .button {
|
||||||
|
|
|
@ -39,12 +39,12 @@
|
||||||
class="edition"
|
class="edition"
|
||||||
:class="['edition-' + edition.id]"
|
:class="['edition-' + edition.id]"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${require('../../assets/editions/' +
|
backgroundImage: `url(${require(
|
||||||
edition.id +
|
'../../assets/editions/' + edition.id + '.png',
|
||||||
'.png')})`
|
)})`,
|
||||||
}"
|
}"
|
||||||
:key="edition.id"
|
:key="edition.id"
|
||||||
@click="setEdition(edition)"
|
@click="runEdition(edition)"
|
||||||
>
|
>
|
||||||
{{ edition.name }}
|
{{ edition.name }}
|
||||||
</li>
|
</li>
|
||||||
|
@ -120,15 +120,15 @@ import Modal from "./Modal";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
data: function() {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
tab: "official"
|
tab: "official",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["modals", "locale", "editions"])
|
...mapState(["modals", "locale", "editions"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openUpload() {
|
openUpload() {
|
||||||
|
@ -178,6 +178,9 @@ export default {
|
||||||
},
|
},
|
||||||
parseRoles(roles) {
|
parseRoles(roles) {
|
||||||
if (!roles || !roles.length) return;
|
if (!roles || !roles.length) return;
|
||||||
|
roles = roles.map((role) =>
|
||||||
|
typeof role === "string" ? { id: role } : role,
|
||||||
|
);
|
||||||
const metaIndex = roles.findIndex(({ id }) => id === "_meta");
|
const metaIndex = roles.findIndex(({ id }) => id === "_meta");
|
||||||
let meta = {};
|
let meta = {};
|
||||||
if (metaIndex > -1) {
|
if (metaIndex > -1) {
|
||||||
|
@ -186,21 +189,24 @@ export default {
|
||||||
this.$store.commit("setCustomRoles", roles);
|
this.$store.commit("setCustomRoles", roles);
|
||||||
this.$store.commit(
|
this.$store.commit(
|
||||||
"setEdition",
|
"setEdition",
|
||||||
Object.assign({}, meta, { id: "custom" })
|
Object.assign({}, meta, { id: "custom" }),
|
||||||
);
|
);
|
||||||
// check for fabled and set those too, if present
|
// set fabled
|
||||||
if (roles.some(({ id }) => this.$store.state.fabled.has(id))) {
|
const fabled = [];
|
||||||
const fabled = [];
|
roles.forEach((role) => {
|
||||||
roles.forEach(({ id }) => {
|
if (this.$store.state.fabled.has(role.id || role)) {
|
||||||
if (this.$store.state.fabled.has(id)) {
|
fabled.push(this.$store.state.fabled.get(role.id || role));
|
||||||
fabled.push(this.$store.state.fabled.get(id));
|
}
|
||||||
}
|
});
|
||||||
});
|
this.$store.commit("players/setFabled", { fabled });
|
||||||
this.$store.commit("players/setFabled", { fabled });
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
...mapMutations(["toggleModal", "setEdition"])
|
runEdition(edition) {
|
||||||
}
|
this.$store.commit("setEdition", edition);
|
||||||
|
// The editions contain no Fabled
|
||||||
|
this.$store.commit("players/setFabled", { fabled: [] });
|
||||||
|
},
|
||||||
|
...mapMutations(["toggleModal", "setEdition"]),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -221,8 +227,12 @@ ul.editions {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000,
|
text-shadow:
|
||||||
1px 1px 0 #000, 0 0 5px rgba(0, 0, 0, 0.75);
|
-1px -1px 0 #000,
|
||||||
|
1px -1px 0 #000,
|
||||||
|
-1px 1px 0 #000,
|
||||||
|
1px 1px 0 #000,
|
||||||
|
0 0 5px rgba(0, 0, 0, 0.75);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: red;
|
color: red;
|
||||||
|
|
|
@ -20,26 +20,28 @@ export default {
|
||||||
...mapState(["modals", "fabled", "grimoire", "locale"]),
|
...mapState(["modals", "fabled", "grimoire", "locale"]),
|
||||||
fabled() {
|
fabled() {
|
||||||
const fabled = [];
|
const fabled = [];
|
||||||
this.$store.state.fabled.forEach(role => {
|
this.$store.state.fabled.forEach((role) => {
|
||||||
// don't show fabled that are already in play
|
// don't show fabled that are already in play
|
||||||
if (
|
if (
|
||||||
!this.$store.state.players.fabled.some(fable => fable.id === role.id)
|
!this.$store.state.players.fabled.some(
|
||||||
|
(fable) => fable.id === role.id,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
fabled.push(role);
|
fabled.push(role);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return fabled;
|
return fabled;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setFabled(role) {
|
setFabled(role) {
|
||||||
this.$store.commit("players/setFabled", {
|
this.$store.commit("players/setFabled", {
|
||||||
fabled: role
|
fabled: role,
|
||||||
});
|
});
|
||||||
this.$store.commit("toggleModal", "fabled");
|
this.$store.commit("toggleModal", "fabled");
|
||||||
},
|
},
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,10 @@ import { mapMutations, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
gamestate: function() {
|
gamestate: function () {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
bluffs: this.players.bluffs.map(({ id }) => id),
|
bluffs: this.players.bluffs.map(({ id }) => id),
|
||||||
edition: this.edition.isOfficial
|
edition: this.edition.isOfficial
|
||||||
|
@ -40,27 +40,27 @@ export default {
|
||||||
roles: this.edition.isOfficial
|
roles: this.edition.isOfficial
|
||||||
? ""
|
? ""
|
||||||
: this.$store.getters.customRolesStripped,
|
: this.$store.getters.customRolesStripped,
|
||||||
fabled: this.players.fabled.map(fabled =>
|
fabled: this.players.fabled.map((fabled) =>
|
||||||
fabled.isCustom ? fabled : { id: fabled.id }
|
fabled.isCustom ? fabled : { id: fabled.id },
|
||||||
),
|
),
|
||||||
players: this.players.players.map(player => ({
|
players: this.players.players.map((player) => ({
|
||||||
...player,
|
...player,
|
||||||
role: player.role.id || {}
|
role: player.role.id || {},
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
...mapState(["modals", "players", "edition", "roles", "session", "locale"])
|
...mapState(["modals", "players", "edition", "roles", "session", "locale"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
input: ""
|
input: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copy: function() {
|
copy: function () {
|
||||||
navigator.clipboard.writeText(this.input || this.gamestate);
|
navigator.clipboard.writeText(this.input || this.gamestate);
|
||||||
},
|
},
|
||||||
load: function() {
|
load: function () {
|
||||||
if (this.session.isSpectator) return;
|
if (this.session.isSpectator) return;
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(this.input || this.gamestate);
|
const data = JSON.parse(this.input || this.gamestate);
|
||||||
|
@ -75,30 +75,30 @@ export default {
|
||||||
bluffs.forEach((role, index) => {
|
bluffs.forEach((role, index) => {
|
||||||
this.$store.commit("players/setBluff", {
|
this.$store.commit("players/setBluff", {
|
||||||
index,
|
index,
|
||||||
role: this.$store.state.roles.get(role) || {}
|
role: this.$store.state.roles.get(role) || {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (fabled) {
|
if (fabled) {
|
||||||
this.$store.commit("players/setFabled", {
|
this.$store.commit("players/setFabled", {
|
||||||
fabled: fabled.map(
|
fabled: fabled.map(
|
||||||
f =>
|
(f) =>
|
||||||
this.$store.state.fabled.get(f) ||
|
this.$store.state.fabled.get(f) ||
|
||||||
this.$store.state.fabled.get(f.id) ||
|
this.$store.state.fabled.get(f.id) ||
|
||||||
f
|
f,
|
||||||
)
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (players) {
|
if (players) {
|
||||||
this.$store.commit(
|
this.$store.commit(
|
||||||
"players/set",
|
"players/set",
|
||||||
players.map(player => ({
|
players.map((player) => ({
|
||||||
...player,
|
...player,
|
||||||
role:
|
role:
|
||||||
this.$store.state.roles.get(player.role) ||
|
this.$store.state.roles.get(player.role) ||
|
||||||
this.$store.getters.rolesJSONbyId.get(player.role) ||
|
this.$store.getters.rolesJSONbyId.get(player.role) ||
|
||||||
{}
|
{},
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.toggleModal("gameState");
|
this.toggleModal("gameState");
|
||||||
|
@ -106,8 +106,8 @@ export default {
|
||||||
alert("Unable to parse JSON: " + e);
|
alert("Unable to parse JSON: " + e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -31,16 +31,16 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data: function() {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
isMaximized: false
|
isMaximized: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
this.$emit("close");
|
this.$emit("close");
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,18 @@
|
||||||
}}</small
|
}}</small
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
class="player"
|
||||||
|
v-if="
|
||||||
|
(role.id == 'dawn' || role.team == 'fabled') &&
|
||||||
|
!session.isSpectator &&
|
||||||
|
players.length &&
|
||||||
|
players[0].role.id
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<br />
|
||||||
|
<small> </small>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
|
@ -44,10 +56,12 @@
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
role.image && grimoire.isImageOptIn
|
role.image && grimoire.isImageOptIn
|
||||||
? role.image
|
? role.image
|
||||||
: require('../../assets/icons/' +
|
: require(
|
||||||
(role.imageAlt || role.id) +
|
'../../assets/icons/' +
|
||||||
'.png')
|
(role.imageAlt || role.id) +
|
||||||
})`
|
'.png',
|
||||||
|
)
|
||||||
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span class="reminder" v-if="role.firstNightReminder">
|
<span class="reminder" v-if="role.firstNightReminder">
|
||||||
|
@ -69,10 +83,12 @@
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
role.image && grimoire.isImageOptIn
|
role.image && grimoire.isImageOptIn
|
||||||
? role.image
|
? role.image
|
||||||
: require('../../assets/icons/' +
|
: require(
|
||||||
(role.imageAlt || role.id) +
|
'../../assets/icons/' +
|
||||||
'.png')
|
(role.imageAlt || role.id) +
|
||||||
})`
|
'.png',
|
||||||
|
)
|
||||||
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span class="name">
|
<span class="name">
|
||||||
|
@ -88,6 +104,20 @@
|
||||||
}}</small
|
}}</small
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
class="player"
|
||||||
|
v-if="
|
||||||
|
(role.id == 'dawn' ||
|
||||||
|
role.id == 'dusk' ||
|
||||||
|
role.team == 'fabled') &&
|
||||||
|
!session.isSpectator &&
|
||||||
|
players.length &&
|
||||||
|
players[0].role.id
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<br />
|
||||||
|
<small> </small>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="reminder" v-if="role.otherNightReminder">
|
<span class="reminder" v-if="role.otherNightReminder">
|
||||||
{{ role.otherNightReminder }}
|
{{ role.otherNightReminder }}
|
||||||
|
@ -104,70 +134,140 @@ import { mapMutations, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
rolesFirstNight: function() {
|
rolesFirstNight: function () {
|
||||||
const rolesFirstNight = [];
|
const rolesFirstNight = [];
|
||||||
|
// Ajouter le matin à l'ordre nocturne
|
||||||
|
rolesFirstNight.push({
|
||||||
|
id: "dawn",
|
||||||
|
name: this.locale.modal.nightOrder.dawn,
|
||||||
|
firstNight: Infinity,
|
||||||
|
team: "default",
|
||||||
|
players: [],
|
||||||
|
firstNightReminder: this.locale.modal.nightOrder.dawnDescription1,
|
||||||
|
});
|
||||||
|
var toymaker = false;
|
||||||
|
// Ajout des fabuleux
|
||||||
|
this.fabled.forEach((fabled) => {
|
||||||
|
if (fabled.firstNight) {
|
||||||
|
rolesFirstNight.push(Object.assign({ players: [] }, fabled));
|
||||||
|
} else if (fabled.id == "toymaker") {
|
||||||
|
toymaker = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.roles.forEach((role) => {
|
||||||
|
const players = this.players.filter((p) => p.role.id === role.id);
|
||||||
|
if (role.firstNight && role.team !== "traveler") {
|
||||||
|
rolesFirstNight.push(Object.assign({ players }, role));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Ajout des Voyageurs, en n'ajoutant qu'une fois ceux en double
|
||||||
|
const seenTravelers = [];
|
||||||
|
var nbTravelers = 0;
|
||||||
|
this.players.forEach((player) => {
|
||||||
|
if (player.role.team == "traveler") {
|
||||||
|
nbTravelers++;
|
||||||
|
if (!seenTravelers.includes(player.role.id)) {
|
||||||
|
seenTravelers.push(player.role.id);
|
||||||
|
if (player.role.firstNight) {
|
||||||
|
const players = this.players.filter(
|
||||||
|
(p) => p.role.id === player.role.id,
|
||||||
|
);
|
||||||
|
rolesFirstNight.push(Object.assign({ players }, player.role));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
// Ajouter minion / demon infos à l'ordre nocturne
|
// Ajouter minion / demon infos à l'ordre nocturne
|
||||||
if (this.players.length > 6) {
|
if (this.players.length - nbTravelers > 6 || toymaker) {
|
||||||
rolesFirstNight.push(
|
rolesFirstNight.push(
|
||||||
{
|
{
|
||||||
id: "evil",
|
id: "minion",
|
||||||
name: this.locale.modal.nightOrder.minionInfo,
|
name: this.locale.modal.nightOrder.minionInfo,
|
||||||
firstNight: 5,
|
firstNight: 7,
|
||||||
team: "minion",
|
team: "minion",
|
||||||
players: this.players.filter(p => p.role.team === "minion"),
|
players: this.players.filter((p) => p.role.team === "minion"),
|
||||||
firstNightReminder: this.locale.modal.nightOrder
|
firstNightReminder:
|
||||||
.minionInfoDescription
|
this.locale.modal.nightOrder.minionInfoDescription,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "evil",
|
id: "evil",
|
||||||
name: this.locale.modal.nightOrder.demonInfo,
|
name: this.locale.modal.nightOrder.demonInfo,
|
||||||
firstNight: 8,
|
firstNight: 10,
|
||||||
team: "demon",
|
team: "demon",
|
||||||
players: this.players.filter(p => p.role.team === "demon"),
|
players: this.players.filter((p) => p.role.team === "demon"),
|
||||||
firstNightReminder: this.locale.modal.nightOrder
|
firstNightReminder:
|
||||||
.demonInfoDescription
|
this.locale.modal.nightOrder.demonInfoDescription,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.roles.forEach(role => {
|
|
||||||
const players = this.players.filter(p => p.role.id === role.id);
|
|
||||||
if (role.firstNight && (role.team !== "traveler" || players.length)) {
|
|
||||||
rolesFirstNight.push(Object.assign({ players }, role));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.fabled
|
|
||||||
.filter(({ firstNight }) => firstNight)
|
|
||||||
.forEach(fabled => {
|
|
||||||
rolesFirstNight.push(Object.assign({ players: [] }, fabled));
|
|
||||||
});
|
|
||||||
rolesFirstNight.sort((a, b) => a.firstNight - b.firstNight);
|
rolesFirstNight.sort((a, b) => a.firstNight - b.firstNight);
|
||||||
return rolesFirstNight;
|
return rolesFirstNight;
|
||||||
},
|
},
|
||||||
rolesOtherNight: function() {
|
rolesOtherNight: function () {
|
||||||
const rolesOtherNight = [];
|
const rolesOtherNight = [];
|
||||||
this.roles.forEach(role => {
|
rolesOtherNight.push(
|
||||||
const players = this.players.filter(p => p.role.id === role.id);
|
{
|
||||||
if (role.otherNight && (role.team !== "traveler" || players.length)) {
|
id: "dusk",
|
||||||
|
name: this.locale.modal.nightOrder.dusk,
|
||||||
|
team: "default",
|
||||||
|
otherNight: 1,
|
||||||
|
players: [],
|
||||||
|
otherNightReminder: this.locale.modal.nightOrder.duskDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "dawn",
|
||||||
|
name: this.locale.modal.nightOrder.dawn,
|
||||||
|
team: "default",
|
||||||
|
otherNight: Infinity,
|
||||||
|
players: [],
|
||||||
|
otherNightReminder: this.locale.modal.nightOrder.dawnDescription2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.fabled
|
||||||
|
.filter(({ otherNight }) => otherNight)
|
||||||
|
.forEach((fabled) => {
|
||||||
|
rolesOtherNight.push(Object.assign({ players: [] }, fabled));
|
||||||
|
});
|
||||||
|
this.roles.forEach((role) => {
|
||||||
|
const players = this.players.filter((p) => p.role.id === role.id);
|
||||||
|
if (role.otherNight && role.team !== "traveler") {
|
||||||
rolesOtherNight.push(Object.assign({ players }, role));
|
rolesOtherNight.push(Object.assign({ players }, role));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.fabled
|
// Ajout des Voyageurs, en n'ajoutant qu'une fois ceux en double
|
||||||
.filter(({ otherNight }) => otherNight)
|
const seenTravelers = [];
|
||||||
.forEach(fabled => {
|
this.players.forEach((player) => {
|
||||||
rolesOtherNight.push(Object.assign({ players: [] }, fabled));
|
if (
|
||||||
});
|
player.role.otherNight &&
|
||||||
|
player.role.team == "traveler" &&
|
||||||
|
!seenTravelers.includes(player.role.id)
|
||||||
|
) {
|
||||||
|
const players = this.players.filter(
|
||||||
|
(p) => p.role.id === player.role.id,
|
||||||
|
);
|
||||||
|
seenTravelers.push(player.role.id);
|
||||||
|
rolesOtherNight.push(Object.assign({ players }, player.role));
|
||||||
|
}
|
||||||
|
});
|
||||||
rolesOtherNight.sort((a, b) => a.otherNight - b.otherNight);
|
rolesOtherNight.sort((a, b) => a.otherNight - b.otherNight);
|
||||||
return rolesOtherNight;
|
return rolesOtherNight;
|
||||||
},
|
},
|
||||||
...mapState(["roles", "modals", "edition", "grimoire", "locale"]),
|
...mapState([
|
||||||
...mapState("players", ["players", "fabled"])
|
"roles",
|
||||||
|
"modals",
|
||||||
|
"edition",
|
||||||
|
"grimoire",
|
||||||
|
"locale",
|
||||||
|
"session",
|
||||||
|
]),
|
||||||
|
...mapState("players", ["players", "fabled"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -251,15 +351,32 @@ h4 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.traveler {
|
||||||
|
.name {
|
||||||
|
background: linear-gradient(90deg, $traveler, transparent 35%);
|
||||||
|
.night .other & {
|
||||||
|
background: linear-gradient(-90deg, $traveler, transparent 35%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.default {
|
||||||
|
.name {
|
||||||
|
background: linear-gradient(90deg, $default, transparent 35%);
|
||||||
|
.night .other & {
|
||||||
|
background: linear-gradient(-90deg, $default, transparent 35%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ul {
|
ul {
|
||||||
li {
|
li {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
.icon {
|
.icon {
|
||||||
width: 6vh;
|
width: 5vh;
|
||||||
background-size: cover;
|
background-size: 100% auto;
|
||||||
background-position: 0 0;
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -273,7 +390,7 @@ ul {
|
||||||
.name {
|
.name {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 15%;
|
width: 5%;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 110%;
|
font-size: 110%;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
|
@ -32,10 +32,12 @@
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
role.image && grimoire.isImageOptIn
|
role.image && grimoire.isImageOptIn
|
||||||
? role.image
|
? role.image
|
||||||
: require('../../assets/icons/' +
|
: require(
|
||||||
(role.imageAlt || role.id) +
|
'../../assets/icons/' +
|
||||||
'.png')
|
(role.imageAlt || role.id) +
|
||||||
})`
|
'.png',
|
||||||
|
)
|
||||||
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<div class="role">
|
<div class="role">
|
||||||
|
@ -60,17 +62,17 @@
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${require('../../assets/icons/' +
|
backgroundImage: `url(${require(
|
||||||
jinx.first.id +
|
'../../assets/icons/' + jinx.first.id + '.png',
|
||||||
'.png')})`
|
)})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${require('../../assets/icons/' +
|
backgroundImage: `url(${require(
|
||||||
jinx.second.id +
|
'../../assets/icons/' + jinx.second.id + '.png',
|
||||||
'.png')})`
|
)})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<div class="role">
|
<div class="role">
|
||||||
|
@ -84,6 +86,7 @@
|
||||||
<li></li>
|
<li></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="asterisk">{{ locale.modal.reference.notfirstnight }}</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -93,23 +96,23 @@ import { mapMutations, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
/**
|
/**
|
||||||
* Return a list of jinxes in the form of role IDs and a reason
|
* Return a list of jinxes in the form of role IDs and a reason
|
||||||
* @returns {*[]} [{first, second, reason}]
|
* @returns {*[]} [{first, second, reason}]
|
||||||
*/
|
*/
|
||||||
jinxed: function() {
|
jinxed: function () {
|
||||||
const jinxed = [];
|
const jinxed = [];
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach((role) => {
|
||||||
if (this.jinxes.get(role.id)) {
|
if (this.jinxes.get(role.id)) {
|
||||||
this.jinxes.get(role.id).forEach((reason, second) => {
|
this.jinxes.get(role.id).forEach((reason, second) => {
|
||||||
if (this.roles.get(second)) {
|
if (this.roles.get(second)) {
|
||||||
jinxed.push({
|
jinxed.push({
|
||||||
first: role,
|
first: role,
|
||||||
second: this.roles.get(second),
|
second: this.roles.get(second),
|
||||||
reason
|
reason,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -117,9 +120,9 @@ export default {
|
||||||
});
|
});
|
||||||
return jinxed;
|
return jinxed;
|
||||||
},
|
},
|
||||||
rolesGrouped: function() {
|
rolesGrouped: function () {
|
||||||
const rolesGrouped = {};
|
const rolesGrouped = {};
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach((role) => {
|
||||||
if (!rolesGrouped[role.team]) {
|
if (!rolesGrouped[role.team]) {
|
||||||
rolesGrouped[role.team] = [];
|
rolesGrouped[role.team] = [];
|
||||||
}
|
}
|
||||||
|
@ -128,7 +131,7 @@ export default {
|
||||||
delete rolesGrouped["traveler"];
|
delete rolesGrouped["traveler"];
|
||||||
return rolesGrouped;
|
return rolesGrouped;
|
||||||
},
|
},
|
||||||
playersByRole: function() {
|
playersByRole: function () {
|
||||||
const players = {};
|
const players = {};
|
||||||
this.players.forEach(({ name, role }) => {
|
this.players.forEach(({ name, role }) => {
|
||||||
if (role && role.id && role.team !== "traveler") {
|
if (role && role.id && role.team !== "traveler") {
|
||||||
|
@ -141,11 +144,11 @@ export default {
|
||||||
return players;
|
return players;
|
||||||
},
|
},
|
||||||
...mapState(["roles", "modals", "edition", "grimoire", "jinxes", "locale"]),
|
...mapState(["roles", "modals", "edition", "grimoire", "jinxes", "locale"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -211,6 +214,12 @@ h3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.asterisk {
|
||||||
|
font-size: 60%;
|
||||||
|
text-align: right;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.team {
|
.team {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
|
@ -18,10 +18,12 @@
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
reminder.image && grimoire.isImageOptIn
|
reminder.image && grimoire.isImageOptIn
|
||||||
? reminder.image
|
? reminder.image
|
||||||
: require('../../assets/icons/' +
|
: require(
|
||||||
(reminder.imageAlt || reminder.role) +
|
'../../assets/icons/' +
|
||||||
'.png')
|
(reminder.imageAlt || reminder.role) +
|
||||||
})`
|
'.png',
|
||||||
|
)
|
||||||
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span class="text">{{ reminder.name }}</span>
|
<span class="text">{{ reminder.name }}</span>
|
||||||
|
@ -39,12 +41,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 +57,31 @@ 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))];
|
reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -86,12 +90,12 @@ export default {
|
||||||
reminders.push({ role: "evil", name: this.locale.modal.reminder.evil });
|
reminders.push({ role: "evil", name: this.locale.modal.reminder.evil });
|
||||||
reminders.push({
|
reminders.push({
|
||||||
role: "custom",
|
role: "custom",
|
||||||
name: this.locale.modal.reminder.custom
|
name: this.locale.modal.reminder.custom,
|
||||||
});
|
});
|
||||||
return reminders;
|
return reminders;
|
||||||
},
|
},
|
||||||
...mapState(["modals", "grimoire", "locale"]),
|
...mapState(["modals", "grimoire", "locale"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addReminder(reminder) {
|
addReminder(reminder) {
|
||||||
|
@ -107,12 +111,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>
|
||||||
|
|
||||||
|
|
|
@ -60,12 +60,12 @@ export default {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -75,11 +75,11 @@ export default {
|
||||||
},
|
},
|
||||||
...mapState(["modals", "roles", "session", "locale"]),
|
...mapState(["modals", "roles", "session", "locale"]),
|
||||||
...mapState("players", ["players"]),
|
...mapState("players", ["players"]),
|
||||||
...mapState(["otherTravelers"])
|
...mapState(["otherTravelers"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tab: "editionRoles"
|
tab: "editionRoles",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -88,7 +88,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 +97,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";
|
||||||
|
@ -107,8 +107,8 @@ export default {
|
||||||
this.tab = "editionRoles";
|
this.tab = "editionRoles";
|
||||||
this.toggleModal("role");
|
this.toggleModal("role");
|
||||||
},
|
},
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -122,19 +122,29 @@ ul.tokens li {
|
||||||
transition: transform 500ms ease;
|
transition: transform 500ms ease;
|
||||||
|
|
||||||
&.townsfolk {
|
&.townsfolk {
|
||||||
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff;
|
box-shadow:
|
||||||
|
0 0 10px $townsfolk,
|
||||||
|
0 0 10px #004cff;
|
||||||
}
|
}
|
||||||
&.outsider {
|
&.outsider {
|
||||||
box-shadow: 0 0 10px $outsider, 0 0 10px $outsider;
|
box-shadow:
|
||||||
|
0 0 10px $outsider,
|
||||||
|
0 0 10px $outsider;
|
||||||
}
|
}
|
||||||
&.minion {
|
&.minion {
|
||||||
box-shadow: 0 0 10px $minion, 0 0 10px $minion;
|
box-shadow:
|
||||||
|
0 0 10px $minion,
|
||||||
|
0 0 10px $minion;
|
||||||
}
|
}
|
||||||
&.demon {
|
&.demon {
|
||||||
box-shadow: 0 0 10px $demon, 0 0 10px $demon;
|
box-shadow:
|
||||||
|
0 0 10px $demon,
|
||||||
|
0 0 10px $demon;
|
||||||
}
|
}
|
||||||
&.traveler {
|
&.traveler {
|
||||||
box-shadow: 0 0 10px $traveler, 0 0 10px $traveler;
|
box-shadow:
|
||||||
|
0 0 10px $traveler,
|
||||||
|
0 0 10px $traveler;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
<h3>
|
<h3>
|
||||||
{{
|
{{
|
||||||
locale.modal.roles.titleStart +
|
locale.modal.roles.titleStart +
|
||||||
nonTravelers +
|
nonTravelers +
|
||||||
locale.modal.roles.titleEnd
|
locale.modal.roles.titleEnd
|
||||||
}}
|
}}
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="tokens" v-for="(teamRoles, team) in roleSelection" :key="team">
|
<ul class="tokens" v-for="(teamRoles, team) in roleSelection" :key="team">
|
||||||
|
@ -48,14 +48,14 @@
|
||||||
class="button"
|
class="button"
|
||||||
@click="assignRoles"
|
@click="assignRoles"
|
||||||
:class="{
|
:class="{
|
||||||
disabled: selectedRoles > nonTravelers || !selectedRoles
|
disabled: selectedRoles > nonTravelers || !selectedRoles,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="people-arrows" />
|
<font-awesome-icon icon="people-arrows" />
|
||||||
{{
|
{{
|
||||||
locale.modal.roles.assignStart +
|
locale.modal.roles.assignStart +
|
||||||
selectedRoles +
|
selectedRoles +
|
||||||
locale.modal.roles.assignEnd
|
locale.modal.roles.assignEnd
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="button" @click="selectRandomRoles">
|
<div class="button" @click="selectRandomRoles">
|
||||||
|
@ -72,39 +72,39 @@ import gameJSON from "./../../game";
|
||||||
import Token from "./../Token";
|
import Token from "./../Token";
|
||||||
import { mapGetters, mapMutations, mapState } from "vuex";
|
import { mapGetters, mapMutations, mapState } from "vuex";
|
||||||
|
|
||||||
const randomElement = arr => arr[Math.floor(Math.random() * arr.length)];
|
const randomElement = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Token,
|
Token,
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
data: function() {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
roleSelection: {},
|
roleSelection: {},
|
||||||
game: gameJSON,
|
game: gameJSON,
|
||||||
allowMultiple: false
|
allowMultiple: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
selectedRoles: function() {
|
selectedRoles: function () {
|
||||||
return Object.values(this.roleSelection)
|
return Object.values(this.roleSelection)
|
||||||
.map(roles => roles.reduce((a, { selected }) => a + selected, 0))
|
.map((roles) => roles.reduce((a, { selected }) => a + selected, 0))
|
||||||
.reduce((a, b) => a + b, 0);
|
.reduce((a, b) => a + b, 0);
|
||||||
},
|
},
|
||||||
hasSelectedSetupRoles: function() {
|
hasSelectedSetupRoles: function () {
|
||||||
return Object.values(this.roleSelection).some(roles =>
|
return Object.values(this.roleSelection).some((roles) =>
|
||||||
roles.some(role => role.selected && role.setup)
|
roles.some((role) => role.selected && role.setup),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
...mapState(["roles", "modals", "locale"]),
|
...mapState(["roles", "modals", "locale"]),
|
||||||
...mapState("players", ["players"]),
|
...mapState("players", ["players"]),
|
||||||
...mapGetters({ nonTravelers: "players/nonTravelers" })
|
...mapGetters({ nonTravelers: "players/nonTravelers" }),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectRandomRoles() {
|
selectRandomRoles() {
|
||||||
this.roleSelection = {};
|
this.roleSelection = {};
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach((role) => {
|
||||||
if (!this.roleSelection[role.team]) {
|
if (!this.roleSelection[role.team]) {
|
||||||
this.$set(this.roleSelection, role.team, []);
|
this.$set(this.roleSelection, role.team, []);
|
||||||
}
|
}
|
||||||
|
@ -114,11 +114,11 @@ export default {
|
||||||
delete this.roleSelection["traveler"];
|
delete this.roleSelection["traveler"];
|
||||||
const playerCount = Math.max(5, this.nonTravelers);
|
const playerCount = Math.max(5, this.nonTravelers);
|
||||||
const composition = this.game[playerCount - 5];
|
const composition = this.game[playerCount - 5];
|
||||||
Object.keys(composition).forEach(team => {
|
Object.keys(composition).forEach((team) => {
|
||||||
for (let x = 0; x < composition[team]; x++) {
|
for (let x = 0; x < composition[team]; x++) {
|
||||||
if (this.roleSelection[team]) {
|
if (this.roleSelection[team]) {
|
||||||
const available = this.roleSelection[team].filter(
|
const available = this.roleSelection[team].filter(
|
||||||
role => !role.selected
|
(role) => !role.selected,
|
||||||
);
|
);
|
||||||
if (available.length) {
|
if (available.length) {
|
||||||
randomElement(available).selected = 1;
|
randomElement(available).selected = 1;
|
||||||
|
@ -131,32 +131,32 @@ export default {
|
||||||
if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
|
if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
|
||||||
// generate list of selected roles and randomize it
|
// generate list of selected roles and randomize it
|
||||||
const roles = Object.values(this.roleSelection)
|
const roles = Object.values(this.roleSelection)
|
||||||
.map(roles =>
|
.map((roles) =>
|
||||||
roles
|
roles
|
||||||
// duplicate roles selected more than once and filter unselected
|
// duplicate roles selected more than once and filter unselected
|
||||||
.reduce((a, r) => [...a, ...Array(r.selected).fill(r)], [])
|
.reduce((a, r) => [...a, ...Array(r.selected).fill(r)], []),
|
||||||
)
|
)
|
||||||
// flatten into a single array
|
// flatten into a single array
|
||||||
.reduce((a, b) => [...a, ...b], [])
|
.reduce((a, b) => [...a, ...b], [])
|
||||||
.map(a => [Math.random(), a])
|
.map((a) => [Math.random(), a])
|
||||||
.sort((a, b) => a[0] - b[0])
|
.sort((a, b) => a[0] - b[0])
|
||||||
.map(a => a[1]);
|
.map((a) => a[1]);
|
||||||
this.players.forEach(player => {
|
this.players.forEach((player) => {
|
||||||
if (player.role.team !== "traveler" && roles.length) {
|
if (player.role.team !== "traveler" && roles.length) {
|
||||||
const value = roles.pop();
|
const value = roles.pop();
|
||||||
this.$store.commit("players/update", {
|
this.$store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.$store.commit("toggleModal", "roles");
|
this.$store.commit("toggleModal", "roles");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
},
|
},
|
||||||
mounted: function() {
|
mounted: function () {
|
||||||
if (!Object.keys(this.roleSelection).length) {
|
if (!Object.keys(this.roleSelection).length) {
|
||||||
this.selectRandomRoles();
|
this.selectRandomRoles();
|
||||||
}
|
}
|
||||||
|
@ -164,8 +164,8 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
roles() {
|
roles() {
|
||||||
this.selectRandomRoles();
|
this.selectRandomRoles();
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -190,19 +190,29 @@ ul.tokens {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.townsfolk {
|
&.townsfolk {
|
||||||
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff;
|
box-shadow:
|
||||||
|
0 0 10px $townsfolk,
|
||||||
|
0 0 10px #004cff;
|
||||||
}
|
}
|
||||||
&.outsider {
|
&.outsider {
|
||||||
box-shadow: 0 0 10px $outsider, 0 0 10px $outsider;
|
box-shadow:
|
||||||
|
0 0 10px $outsider,
|
||||||
|
0 0 10px $outsider;
|
||||||
}
|
}
|
||||||
&.minion {
|
&.minion {
|
||||||
box-shadow: 0 0 10px $minion, 0 0 10px $minion;
|
box-shadow:
|
||||||
|
0 0 10px $minion,
|
||||||
|
0 0 10px $minion;
|
||||||
}
|
}
|
||||||
&.demon {
|
&.demon {
|
||||||
box-shadow: 0 0 10px $demon, 0 0 10px $demon;
|
box-shadow:
|
||||||
|
0 0 10px $demon,
|
||||||
|
0 0 10px $demon;
|
||||||
}
|
}
|
||||||
&.traveler {
|
&.traveler {
|
||||||
box-shadow: 0 0 10px $traveler, 0 0 10px $traveler;
|
box-shadow:
|
||||||
|
0 0 10px $traveler,
|
||||||
|
0 0 10px $traveler;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
:icon="[
|
:icon="[
|
||||||
'fas',
|
'fas',
|
||||||
session.isVoteHistoryAllowed ? 'check-square' : 'square'
|
session.isVoteHistoryAllowed ? 'check-square' : 'square',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
{{ locale.modal.voteHistory.accessibility }}
|
{{ locale.modal.voteHistory.accessibility }}
|
||||||
|
@ -49,16 +49,8 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(vote, index) in session.voteHistory" :key="index">
|
<tr v-for="(vote, index) in session.voteHistory" :key="index">
|
||||||
<td>
|
<td>
|
||||||
{{
|
{{ vote.timestamp.getHours().toString().padStart(2, "0") }}:{{
|
||||||
vote.timestamp
|
vote.timestamp.getMinutes().toString().padStart(2, "0")
|
||||||
.getHours()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0")
|
|
||||||
}}:{{
|
|
||||||
vote.timestamp
|
|
||||||
.getMinutes()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0")
|
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ vote.nominator }}</td>
|
<td>{{ vote.nominator }}</td>
|
||||||
|
@ -76,8 +68,8 @@
|
||||||
vote.votes == null
|
vote.votes == null
|
||||||
? 'minus-square'
|
? 'minus-square'
|
||||||
: vote.votes.length >= vote.majority
|
: vote.votes.length >= vote.majority
|
||||||
? 'check-square'
|
? 'check-square'
|
||||||
: 'square'
|
: 'square',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -100,10 +92,10 @@ import { mapMutations, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["session", "modals", "locale"])
|
...mapState(["session", "modals", "locale"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clearVoteHistory() {
|
clearVoteHistory() {
|
||||||
|
@ -112,11 +104,11 @@ export default {
|
||||||
setRecordVoteHistory() {
|
setRecordVoteHistory() {
|
||||||
this.$store.commit(
|
this.$store.commit(
|
||||||
"session/setVoteHistoryAllowed",
|
"session/setVoteHistoryAllowed",
|
||||||
!this.session.isVoteHistoryAllowed
|
!this.session.isVoteHistoryAllowed,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
10
src/main.js
10
src/main.js
|
@ -56,17 +56,17 @@ const faIcons = [
|
||||||
"VolumeMute",
|
"VolumeMute",
|
||||||
"VoteYea",
|
"VoteYea",
|
||||||
"WindowMaximize",
|
"WindowMaximize",
|
||||||
"WindowMinimize"
|
"WindowMinimize",
|
||||||
];
|
];
|
||||||
const fabIcons = ["Github", "Discord"];
|
const fabIcons = ["Github", "Discord"];
|
||||||
library.add(
|
library.add(
|
||||||
...faIcons.map(i => fas["fa" + i]),
|
...faIcons.map((i) => fas["fa" + i]),
|
||||||
...fabIcons.map(i => fab["fa" + i])
|
...fabIcons.map((i) => fab["fa" + i]),
|
||||||
);
|
);
|
||||||
Vue.component("font-awesome-icon", FontAwesomeIcon);
|
Vue.component("font-awesome-icon", FontAwesomeIcon);
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
render: h => h(App),
|
render: (h) => h(App),
|
||||||
store
|
store,
|
||||||
}).$mount("#app");
|
}).$mount("#app");
|
||||||
|
|
|
@ -14,9 +14,9 @@ Vue.use(Vuex);
|
||||||
const getRolesByEdition = (edition = editionJSON.official[0]) => {
|
const getRolesByEdition = (edition = editionJSON.official[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]),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,35 +24,39 @@ const getTravelersNotInEdition = (edition = editionJSON.official[0]) => {
|
||||||
return new Map(
|
return new Map(
|
||||||
rolesJSON
|
rolesJSON
|
||||||
.filter(
|
.filter(
|
||||||
r =>
|
(r) =>
|
||||||
r.team === "traveler" &&
|
r.team === "traveler" &&
|
||||||
r.edition !== edition.id &&
|
r.edition !== edition.id &&
|
||||||
!edition.roles.includes(r.id)
|
!edition.roles.includes(r.id),
|
||||||
)
|
)
|
||||||
.map(role => [role.id, role])
|
.map((role) => [role.id, role]),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const set = key => ({ grimoire }, val) => {
|
const set =
|
||||||
grimoire[key] = val;
|
(key) =>
|
||||||
};
|
({ grimoire }, val) => {
|
||||||
|
|
||||||
const toggle = key => ({ grimoire }, val) => {
|
|
||||||
if (val === true || val === false) {
|
|
||||||
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.official.map(edition => [edition.id, edition])
|
editionJSON.official.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 = {};
|
||||||
|
@ -64,8 +68,8 @@ 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])),
|
||||||
])
|
]),
|
||||||
);
|
);
|
||||||
// });
|
// });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -87,13 +91,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: {
|
||||||
|
@ -111,8 +115,8 @@ export default new Vuex.Store({
|
||||||
background: "",
|
background: "",
|
||||||
timer: {
|
timer: {
|
||||||
name: "",
|
name: "",
|
||||||
duration: 0
|
duration: 0,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
modals: {
|
modals: {
|
||||||
edition: false,
|
edition: false,
|
||||||
|
@ -123,7 +127,7 @@ 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"),
|
||||||
editions: editionJSON,
|
editions: editionJSON,
|
||||||
|
@ -131,7 +135,7 @@ export default new Vuex.Store({
|
||||||
otherTravelers: getTravelersNotInEdition(),
|
otherTravelers: getTravelersNotInEdition(),
|
||||||
fabled,
|
fabled,
|
||||||
jinxes,
|
jinxes,
|
||||||
locale
|
locale,
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
/**
|
/**
|
||||||
|
@ -146,9 +150,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 {
|
||||||
|
@ -167,7 +171,7 @@ export default new Vuex.Store({
|
||||||
});
|
});
|
||||||
return customRoles;
|
return customRoles;
|
||||||
},
|
},
|
||||||
rolesJSONbyId: () => rolesJSONbyId
|
rolesJSONbyId: () => rolesJSONbyId,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setZoom: set("zoom"),
|
setZoom: set("zoom"),
|
||||||
|
@ -202,7 +206,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 = {};
|
||||||
|
@ -217,19 +221,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
|
||||||
{
|
{
|
||||||
|
@ -237,32 +241,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) {
|
||||||
|
@ -274,7 +282,7 @@ export default new Vuex.Store({
|
||||||
state.edition = edition;
|
state.edition = edition;
|
||||||
}
|
}
|
||||||
state.modals.edition = false;
|
state.modals.edition = false;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
plugins: [persistence, socket]
|
plugins: [persistence, socket],
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"id": "doomsayer",
|
"id": "doomsayer",
|
||||||
"firstNightReminder": "",
|
"firstNightReminder": "",
|
||||||
"otherNightReminder": "",
|
"otherNightReminder": "",
|
||||||
"reminders": [],
|
"reminders": ["Used"],
|
||||||
"setup": false,
|
"setup": false,
|
||||||
"name": "Doomsayer",
|
"name": "Doomsayer",
|
||||||
"team": "fabled",
|
"team": "fabled",
|
||||||
|
@ -132,15 +132,5 @@
|
||||||
"name": "Storm Catcher",
|
"name": "Storm Catcher",
|
||||||
"team": "fabled",
|
"team": "fabled",
|
||||||
"ability": "Name a good character. If in play, they can only die by execution, but evil players learn which player it is."
|
"ability": "Name a good character. If in play, they can only die by execution, but evil players learn which player it is."
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "deusexfiasco",
|
|
||||||
"firstNightReminder": "",
|
|
||||||
"otherNightReminder": "",
|
|
||||||
"reminders": ["Whoops"],
|
|
||||||
"setup": false,
|
|
||||||
"name": "Deus ex Fiasco",
|
|
||||||
"team": "fabled",
|
|
||||||
"ability": "Once per game, the Storyteller will make a \"mistake\", correct it and publicly admit to it."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Heretic",
|
"id": "Heretic",
|
||||||
"reason": "A Pit-Hag can not create a Heretic. "
|
"reason": "A Pit-Hag cannot create a Heretic. "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Damsel",
|
"id": "Damsel",
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Politician",
|
"id": "Politician",
|
||||||
"reason": "A Pit-hag can not create an evil Politician."
|
"reason": "A Pit-hag cannot create an evil Politician."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -69,19 +69,19 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ravenkeeper",
|
"id": "Ravenkeeper",
|
||||||
"reason": "If Leviathan is in play & the Ravenkeeper dies by execution, they wake that night to use their ability."
|
"reason": "If Leviathan is in play and the Ravenkeeper dies by execution, they wake that night to use their ability."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Sage",
|
"id": "Sage",
|
||||||
"reason": "If Leviathan is in play & the Sage dies by execution, they wake that night to use their ability."
|
"reason": "If Leviathan is in play and the Sage dies by execution, they wake that night to use their ability."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Farmer",
|
"id": "Farmer",
|
||||||
"reason": "If Leviathan is in play & a Farmer dies by execution, a good player becomes a Farmer that night."
|
"reason": "If Leviathan is in play and a Farmer dies by execution, a good player becomes a Farmer that night."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Mayor",
|
"id": "Mayor",
|
||||||
"reason": "If Leviathan is in play & no execution occurs on day 5, good wins."
|
"reason": "If Leviathan is in play and no execution occurs on day 5, good wins."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -107,11 +107,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Magician",
|
"id": "Magician",
|
||||||
"reason": "Only 1 jinxed character can be in play. "
|
"reason": "Only one jinxed character can be in play. "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Scarlet Woman",
|
"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."
|
"reason": "If there are five or more players alive and the player holding the Lil' Monsta token dies, the Scarlet Woman is given the Lil' Monsta token tonight."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Gambler",
|
"id": "Gambler",
|
||||||
"reason": "If the Lycanthrope is alive and the Gambler kills themself at night, no other players can die tonight."
|
"reason": "If the Lycanthrope is alive and the Gambler kills themselves at night, no other players can die tonight."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -129,11 +129,11 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Engineer",
|
"id": "Engineer",
|
||||||
"reason": "Legion and the Engineer can not both be in play at the start of the game. If the Engineer creates Legion, most players (including all evil players) become evil Legion."
|
"reason": "Legion and the Engineer cannot both be in play at the start of the game. If the Engineer creates Legion, most players (including all evil players) become evil Legion."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Preacher",
|
"id": "Preacher",
|
||||||
"reason": "Only 1 jinxed character can be in play."
|
"reason": "Only one jinxed character can be in play."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -151,11 +151,11 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Magician",
|
"id": "Magician",
|
||||||
"reason": "When the Spy sees the Grimoire, the Demon and Magician's character tokens are removed."
|
"reason": "When the Spy sees the Grimoire, the Demon and the Magician's character tokens are removed."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Alchemist",
|
"id": "Alchemist",
|
||||||
"reason": "The Alchemist can not have the Spy ability."
|
"reason": "The Alchemist cannot have the Spy ability."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Poppy Grower",
|
"id": "Poppy Grower",
|
||||||
|
@ -163,11 +163,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Damsel",
|
"id": "Damsel",
|
||||||
"reason": "Only 1 jinxed character can be in play. "
|
"reason": "Only one jinxed character can be in play. "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Heretic",
|
"id": "Heretic",
|
||||||
"reason": "Only 1 jinxed character can be in play."
|
"reason": "Only one jinxed character can be in play."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -176,7 +176,7 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Magician",
|
"id": "Magician",
|
||||||
"reason": "When the Widow sees the Grimoire, the Demon and Magician's character tokens are removed."
|
"reason": "When the Widow sees the Grimoire, the Demon and the Magician's character tokens are removed."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Poppy Grower",
|
"id": "Poppy Grower",
|
||||||
|
@ -184,15 +184,15 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Alchemist",
|
"id": "Alchemist",
|
||||||
"reason": "The Alchemist can not have the Widow ability."
|
"reason": "The Alchemist cannot have the Widow ability."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Damsel",
|
"id": "Damsel",
|
||||||
"reason": "Only 1 jinxed character can be in play."
|
"reason": "Only one jinxed character can be in play."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Heretic",
|
"id": "Heretic",
|
||||||
"reason": "Only 1 jinxed character can be in play."
|
"reason": "Only one jinxed character can be in play."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -201,7 +201,7 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Heretic",
|
"id": "Heretic",
|
||||||
"reason": "Only 1 jinxed character can be in play."
|
"reason": "Only one jinxed character can be in play."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -210,7 +210,7 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Heretic",
|
"id": "Heretic",
|
||||||
"reason": "The Baron might only add 1 Outsider, not 2."
|
"reason": "The Baron might only add one Outsider, not two."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -219,7 +219,7 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Lil' Monsta",
|
"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."
|
"reason": "The Marionette neighbors a Minion, not the Demon. The Marionette is not woken to choose who takes the Lil' Monsta token, and does not learn they are the Marionette if they have the Lil' Monsta token."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Poppy Grower",
|
"id": "Poppy Grower",
|
||||||
|
@ -248,7 +248,7 @@
|
||||||
"hatred": [
|
"hatred": [
|
||||||
{
|
{
|
||||||
"id": "Engineer",
|
"id": "Engineer",
|
||||||
"reason": "Riot and the Engineer can not both be in play at the start of the game. \nIf the Engineer creates Riot, the evil players become Riot."
|
"reason": "Riot and the Engineer cannot both be in play at the start of the game. If the Engineer creates Riot, the evil players become Riot."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Golem",
|
"id": "Golem",
|
||||||
|
@ -256,7 +256,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Snitch",
|
"id": "Snitch",
|
||||||
"reason": "If the Snitch is in play, each Riot player gets an extra 3 bluffs."
|
"reason": "If the Snitch is in play, each Riot player gets an extra three bluffs."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Saint",
|
"id": "Saint",
|
||||||
|
@ -264,19 +264,19 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Butler",
|
"id": "Butler",
|
||||||
"reason": "The Butler can not nominate their master."
|
"reason": "The Butler cannot nominate their master."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Pit-Hag",
|
"id": "Pit-Hag",
|
||||||
"reason": "If the Pit-Hag creates Riot, all evil players become Riot. \nIf the Pit-Hag creates Riot after day 3, the game continues for one more day."
|
"reason": "If the Pit-Hag creates Riot, all evil players become Riot. If the Pit-Hag creates Riot after day 3, the game continues for one more day."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Mayor",
|
"id": "Mayor",
|
||||||
"reason": "If the 3rd day begins with just three players alive, the players may choose (as a group) not to nominate at all. If so (and a Mayor is alive) then the Mayor's team wins."
|
"reason": "If the third day begins with just three players alive, the players may choose (as a group) not to nominate at all. If so (and a Mayor is alive) the Mayor's team wins."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Monk",
|
"id": "Monk",
|
||||||
"reason": "If a Riot player nominates and kills the Monk-protected-player, the Monk-protected-player does not die."
|
"reason": "If a Riot player nominates a Monk-protected player, the protected-player does not die."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Farmer",
|
"id": "Farmer",
|
||||||
|
@ -284,7 +284,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Innkeeper",
|
"id": "Innkeeper",
|
||||||
"reason": "If a Riot player nominates an Innkeeper-protected-player, the Innkeeper-protected-player does not die."
|
"reason": "If a Riot player nominates an Innkeeper-protected player, the protected-player does not die."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Sage",
|
"id": "Sage",
|
||||||
|
@ -300,7 +300,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Grandmother",
|
"id": "Grandmother",
|
||||||
"reason": "If a Riot player nominates and kills the Grandchild, the Grandmother dies too."
|
"reason": "If a Riot player nominates and kills the grandchild, the Grandmother dies too."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "King",
|
"id": "King",
|
||||||
|
@ -308,31 +308,31 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Exorcist",
|
"id": "Exorcist",
|
||||||
"reason": "Only 1 jinxed character can be in play."
|
"reason": "Only one jinxed character can be in play."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Minstrel",
|
"id": "Minstrel",
|
||||||
"reason": "Only 1 jinxed character can be in play."
|
"reason": "Only one jinxed character can be in play."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Flowergirl",
|
"id": "Flowergirl",
|
||||||
"reason": "Only 1 jinxed character can be in play."
|
"reason": "Only one jinxed character can be in play."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Undertaker",
|
"id": "Undertaker",
|
||||||
"reason": "Players that die by nomination register as being executed to the Undertaker."
|
"reason": "Players that die by nomination register as executed to the Undertaker."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Cannibal",
|
"id": "Cannibal",
|
||||||
"reason": "Players that die by nomination register as being executed to the Cannibal."
|
"reason": "Players that die by nomination register as executed to the Cannibal."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Pacifist",
|
"id": "Pacifist",
|
||||||
"reason": "Players that die by nomination register as being executed to the Pacifist."
|
"reason": "Players that die by nomination register as executed to the Pacifist."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Devil's Advocate",
|
"id": "Devil's Advocate",
|
||||||
"reason": "Players that die by nomination register as being executed to the Devil's Advocate."
|
"reason": "Players that die by nomination register as executed to the Devil's Advocate."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Investigator",
|
"id": "Investigator",
|
||||||
|
@ -365,7 +365,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Slayer",
|
"id": "Slayer",
|
||||||
"reason": "If the Slayer slays the Lleech's host, the host dies. "
|
"reason": "If the Slayer shoots the Lleech's host, the host dies. "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Heretic",
|
"id": "Heretic",
|
||||||
|
|
|
@ -1725,7 +1725,7 @@
|
||||||
"Day 4",
|
"Day 4",
|
||||||
"Day 5"],
|
"Day 5"],
|
||||||
"setup": false,
|
"setup": false,
|
||||||
"ability": "If more than 1 good player is executed, you win. All players know you are in play. After day 5, evil wins."
|
"ability": "If more than 1 good player is executed, evil wins. All players know you are in play. After day 5, evil wins."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "riot",
|
"id": "riot",
|
||||||
|
|
|
@ -70,7 +70,9 @@
|
||||||
"customNote": "Add a custom reminder node"
|
"customNote": "Add a custom reminder node"
|
||||||
},
|
},
|
||||||
"vote":{
|
"vote":{
|
||||||
"nominated": "nominated",
|
"nominates": "nominates",
|
||||||
|
"callexile": "calls for the exile of",
|
||||||
|
"exclam": "!",
|
||||||
"votes": "votes",
|
"votes": "votes",
|
||||||
"inFavor": "in favor",
|
"inFavor": "in favor",
|
||||||
"majorityIs": "majority is",
|
"majorityIs": "majority is",
|
||||||
|
@ -198,13 +200,19 @@
|
||||||
"firstNight": "First Night",
|
"firstNight": "First Night",
|
||||||
"otherNights": "Other Nights",
|
"otherNights": "Other Nights",
|
||||||
"minionInfo": "Minion info",
|
"minionInfo": "Minion info",
|
||||||
"minionInfoDescription": "• If more than one Minion, they all make eye contact with each other. • Show the “This is the Demon” card. Point to the Demon.",
|
"minionInfoDescription": "If more than one Minion, they all make eye contact with each other. Show the “This is the Demon” card. Point to the Demon.",
|
||||||
"demonInfo": "Demon info & bluffs",
|
"demonInfo": "Demon info & bluffs",
|
||||||
"demonInfoDescription": "• Show the “These are your minions” card. Point to each Minion. • Show the “These characters are not in play” card. Show 3 character tokens of good characters not in play."
|
"demonInfoDescription": "Show the “These are your minions” card. Point to each Minion. Show the “These characters are not in play” card. Show 3 character tokens of good characters not in play.",
|
||||||
|
"dawn": "Dawn",
|
||||||
|
"dawnDescription1": "Wake all players.",
|
||||||
|
"dawnDescription2": "Wake all players, then announce who died this night.",
|
||||||
|
"dusk": "Dusk",
|
||||||
|
"duskDescription": "End the day, and put all players to sleep."
|
||||||
},
|
},
|
||||||
"reference": {
|
"reference": {
|
||||||
"title": "Character Reference",
|
"title": "Character Reference",
|
||||||
"jinxed": "Jinxed",
|
"jinxed": "Jinxed",
|
||||||
|
"notfirstnight": "*Not the first night",
|
||||||
"teamNames": {
|
"teamNames": {
|
||||||
"townsfolk": "townfolk",
|
"townsfolk": "townfolk",
|
||||||
"outsider": "outsider",
|
"outsider": "outsider",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"team": "fabled",
|
"team": "fabled",
|
||||||
"firstNightReminder": "",
|
"firstNightReminder": "",
|
||||||
"otherNightReminder": "",
|
"otherNightReminder": "",
|
||||||
"reminders": [],
|
"reminders": ["Utilisé"],
|
||||||
"setup": false,
|
"setup": false,
|
||||||
"ability": "Si 4 joueurs ou plus sont en vie, chaque joueur vivant peut, une fois par partie, décider qu'un joueur de son propre alignement meure."
|
"ability": "Si 4 joueurs ou plus sont en vie, chaque joueur vivant peut, une fois par partie, décider qu'un joueur de son propre alignement meure."
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,7 +70,9 @@
|
||||||
"customNote": "Ajouter une note personnalisée"
|
"customNote": "Ajouter une note personnalisée"
|
||||||
},
|
},
|
||||||
"vote":{
|
"vote":{
|
||||||
"nominated": "accuse",
|
"nominates": "accuse",
|
||||||
|
"callexile": "veut exiler",
|
||||||
|
"exclam": " !",
|
||||||
"votes": "votes",
|
"votes": "votes",
|
||||||
"inFavor": "pour",
|
"inFavor": "pour",
|
||||||
"majorityIs": "majorité à",
|
"majorityIs": "majorité à",
|
||||||
|
@ -198,13 +200,19 @@
|
||||||
"firstNight": "Première Nuit",
|
"firstNight": "Première Nuit",
|
||||||
"otherNights": "Autres Nuits",
|
"otherNights": "Autres Nuits",
|
||||||
"minionInfo": "Informations Serviteurs",
|
"minionInfo": "Informations Serviteurs",
|
||||||
"minionInfoDescription": "• S'il y a plusieurs Serviteurs, ils apprennent qui sont les autres Serviteurs. • Indiquez aux Serviteurs qui est le Démon.",
|
"minionInfoDescription": "S'il y a plusieurs Serviteurs, ils apprennent qui sont les autres Serviteurs. Indiquez aux Serviteurs qui est le Démon.",
|
||||||
"demonInfo": "Info & Bluffs Démon",
|
"demonInfo": "Info & Bluffs Démon",
|
||||||
"demonInfoDescription": "• Indiquez au Démon qui sont ses serviteurs.• Indiquez les rôles de 3 personnages Bons qui ne sont pas en jeu."
|
"demonInfoDescription": "Indiquez au Démon qui sont ses serviteurs. Indiquez les rôles de 3 personnages bons qui ne sont pas en jeu.",
|
||||||
|
"dawn": "Matin",
|
||||||
|
"dawnDescription1": "Réveillez les joueurs.",
|
||||||
|
"dawnDescription2": "Réveillez les joueurs, puis annoncez qui est mort cette nuit",
|
||||||
|
"dusk": "Tombée de la nuit",
|
||||||
|
"duskDescription": "Terminez la journée, et endormez les joueurs."
|
||||||
},
|
},
|
||||||
"reference": {
|
"reference": {
|
||||||
"title": "Réference de rôles",
|
"title": "Réference de rôles",
|
||||||
"jinxed": "Jinx",
|
"jinxed": "Jinx",
|
||||||
|
"notfirstnight": "* Pas la première nuit",
|
||||||
"teamNames": {
|
"teamNames": {
|
||||||
"townsfolk": "villageois",
|
"townsfolk": "villageois",
|
||||||
"outsider": "étranger",
|
"outsider": "étranger",
|
||||||
|
|
|
@ -5,22 +5,22 @@ const NEWPLAYER = {
|
||||||
reminders: [],
|
reminders: [],
|
||||||
isVoteless: false,
|
isVoteless: false,
|
||||||
isDead: false,
|
isDead: false,
|
||||||
pronouns: ""
|
pronouns: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = () => ({
|
const state = () => ({
|
||||||
players: [],
|
players: [],
|
||||||
fabled: [],
|
fabled: [],
|
||||||
bluffs: []
|
bluffs: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
alive({ players }) {
|
alive({ players }) {
|
||||||
return players.filter(player => !player.isDead).length;
|
return players.filter((player) => !player.isDead).length;
|
||||||
},
|
},
|
||||||
nonTravelers({ players }) {
|
nonTravelers({ players }) {
|
||||||
const nonTravelers = players.filter(
|
const nonTravelers = players.filter(
|
||||||
player => player.role.team !== "traveler"
|
(player) => player.role.team !== "traveler",
|
||||||
);
|
);
|
||||||
return Math.min(nonTravelers.length, 15);
|
return Math.min(nonTravelers.length, 15);
|
||||||
},
|
},
|
||||||
|
@ -36,7 +36,7 @@ const getters = {
|
||||||
otherNight.push(role.otherNight);
|
otherNight.push(role.otherNight);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
fabled.forEach(role => {
|
fabled.forEach((role) => {
|
||||||
if (role.firstNight && !firstNight.includes(role.firstNight)) {
|
if (role.firstNight && !firstNight.includes(role.firstNight)) {
|
||||||
firstNight.push(role.firstNight);
|
firstNight.push(role.firstNight);
|
||||||
}
|
}
|
||||||
|
@ -47,32 +47,32 @@ const getters = {
|
||||||
firstNight.sort((a, b) => a - b);
|
firstNight.sort((a, b) => a - b);
|
||||||
otherNight.sort((a, b) => a - b);
|
otherNight.sort((a, b) => a - b);
|
||||||
const nightOrder = new Map();
|
const nightOrder = new Map();
|
||||||
players.forEach(player => {
|
players.forEach((player) => {
|
||||||
const first = Math.max(firstNight.indexOf(player.role.firstNight), 0);
|
const first = Math.max(firstNight.indexOf(player.role.firstNight), 0);
|
||||||
const other = Math.max(otherNight.indexOf(player.role.otherNight), 0);
|
const other = Math.max(otherNight.indexOf(player.role.otherNight), 0);
|
||||||
nightOrder.set(player, { first, other });
|
nightOrder.set(player, { first, other });
|
||||||
});
|
});
|
||||||
fabled.forEach(role => {
|
fabled.forEach((role) => {
|
||||||
const first = Math.max(firstNight.indexOf(role.firstNight), 0);
|
const first = Math.max(firstNight.indexOf(role.firstNight), 0);
|
||||||
const other = Math.max(otherNight.indexOf(role.otherNight), 0);
|
const other = Math.max(otherNight.indexOf(role.otherNight), 0);
|
||||||
nightOrder.set(role, { first, other });
|
nightOrder.set(role, { first, other });
|
||||||
});
|
});
|
||||||
return nightOrder;
|
return nightOrder;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
randomize({ state, commit }) {
|
randomize({ state, commit }) {
|
||||||
const players = state.players
|
const players = state.players
|
||||||
.map(a => [Math.random(), a])
|
.map((a) => [Math.random(), a])
|
||||||
.sort((a, b) => a[0] - b[0])
|
.sort((a, b) => a[0] - b[0])
|
||||||
.map(a => a[1]);
|
.map((a) => a[1]);
|
||||||
commit("set", players);
|
commit("set", players);
|
||||||
},
|
},
|
||||||
clearRoles({ state, commit, rootState }) {
|
clearRoles({ state, commit, rootState }) {
|
||||||
let players;
|
let players;
|
||||||
if (rootState.session.isSpectator) {
|
if (rootState.session.isSpectator) {
|
||||||
players = state.players.map(player => {
|
players = state.players.map((player) => {
|
||||||
if (player.role.team !== "traveler") {
|
if (player.role.team !== "traveler") {
|
||||||
player.role = {};
|
player.role = {};
|
||||||
}
|
}
|
||||||
|
@ -84,13 +84,12 @@ const actions = {
|
||||||
...NEWPLAYER,
|
...NEWPLAYER,
|
||||||
name,
|
name,
|
||||||
id,
|
id,
|
||||||
pronouns
|
pronouns,
|
||||||
}));
|
}));
|
||||||
commit("setFabled", { fabled: [] });
|
|
||||||
}
|
}
|
||||||
commit("set", players);
|
commit("set", players);
|
||||||
commit("setBluff");
|
commit("setBluff");
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
|
@ -119,7 +118,7 @@ const mutations = {
|
||||||
add(state, name) {
|
add(state, name) {
|
||||||
state.players.push({
|
state.players.push({
|
||||||
...NEWPLAYER,
|
...NEWPLAYER,
|
||||||
name
|
name,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
remove(state, index) {
|
remove(state, index) {
|
||||||
|
@ -128,7 +127,7 @@ const mutations = {
|
||||||
swap(state, [from, to]) {
|
swap(state, [from, to]) {
|
||||||
[state.players[from], state.players[to]] = [
|
[state.players[from], state.players[to]] = [
|
||||||
state.players[to],
|
state.players[to],
|
||||||
state.players[from]
|
state.players[from],
|
||||||
];
|
];
|
||||||
// hack: "modify" the array so that Vue notices something changed
|
// hack: "modify" the array so that Vue notices something changed
|
||||||
state.players.splice(0, 0);
|
state.players.splice(0, 0);
|
||||||
|
@ -153,7 +152,7 @@ const mutations = {
|
||||||
state.fabled = fabled;
|
state.fabled = fabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -161,5 +160,5 @@ export default {
|
||||||
state,
|
state,
|
||||||
getters,
|
getters,
|
||||||
actions,
|
actions,
|
||||||
mutations
|
mutations,
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,7 +29,7 @@ const state = () => ({
|
||||||
voteHistory: [],
|
voteHistory: [],
|
||||||
markedPlayer: -1,
|
markedPlayer: -1,
|
||||||
isVoteHistoryAllowed: true,
|
isVoteHistoryAllowed: true,
|
||||||
isRolesDistributed: false
|
isRolesDistributed: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getters = {};
|
const getters = {};
|
||||||
|
@ -37,7 +37,7 @@ const getters = {};
|
||||||
const actions = {};
|
const actions = {};
|
||||||
|
|
||||||
// mutations helper functions
|
// mutations helper functions
|
||||||
const set = key => (state, val) => {
|
const set = (key) => (state, val) => {
|
||||||
state[key] = val;
|
state[key] = val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ const mutations = {
|
||||||
},
|
},
|
||||||
nomination(
|
nomination(
|
||||||
state,
|
state,
|
||||||
{ nomination, votes, votingSpeed, lockedVote, isVoteInProgress } = {}
|
{ nomination, votes, votingSpeed, lockedVote, isVoteInProgress } = {},
|
||||||
) {
|
) {
|
||||||
state.nomination = nomination || false;
|
state.nomination = nomination || false;
|
||||||
state.votes = votes || [];
|
state.votes = votes || [];
|
||||||
|
@ -91,14 +91,14 @@ const mutations = {
|
||||||
: gameInfo.state.locale.modal.voteHistory.execution +
|
: gameInfo.state.locale.modal.voteHistory.execution +
|
||||||
(organGrinder && !state.isSpectator ? "*" : ""),
|
(organGrinder && !state.isSpectator ? "*" : ""),
|
||||||
majority: Math.ceil(
|
majority: Math.ceil(
|
||||||
players.filter(player => !player.isDead || isExile).length / 2
|
players.filter((player) => !player.isDead || isExile).length / 2,
|
||||||
),
|
),
|
||||||
votes:
|
votes:
|
||||||
organGrinder && state.isSpectator
|
organGrinder && state.isSpectator
|
||||||
? null
|
? null
|
||||||
: players
|
: players
|
||||||
.filter((player, index) => state.votes[index])
|
.filter((player, index) => state.votes[index])
|
||||||
.map(({ name }) => name)
|
.map(({ name }) => name),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clearVoteHistory(state) {
|
clearVoteHistory(state) {
|
||||||
|
@ -114,7 +114,7 @@ const mutations = {
|
||||||
voteSync: handleVote,
|
voteSync: handleVote,
|
||||||
lockVote(state, lock) {
|
lockVote(state, lock) {
|
||||||
state.lockedVote = lock !== undefined ? lock : state.lockedVote + 1;
|
state.lockedVote = lock !== undefined ? lock : state.lockedVote + 1;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -122,5 +122,5 @@ export default {
|
||||||
state,
|
state,
|
||||||
getters,
|
getters,
|
||||||
actions,
|
actions,
|
||||||
mutations
|
mutations,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports = store => {
|
module.exports = (store) => {
|
||||||
const updatePagetitle = isPublic =>
|
const updatePagetitle = (isPublic) =>
|
||||||
(document.title = `Blood on the Clocktower ${
|
(document.title = `Blood on the Clocktower ${
|
||||||
isPublic ? "Town Square" : "Grimoire"
|
isPublic ? "Town Square" : "Grimoire"
|
||||||
}`);
|
}`);
|
||||||
|
@ -39,27 +39,27 @@ module.exports = store => {
|
||||||
JSON.parse(localStorage.bluffs).forEach((role, index) => {
|
JSON.parse(localStorage.bluffs).forEach((role, index) => {
|
||||||
store.commit("players/setBluff", {
|
store.commit("players/setBluff", {
|
||||||
index,
|
index,
|
||||||
role: store.state.roles.get(role) || {}
|
role: store.state.roles.get(role) || {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (localStorage.fabled !== undefined) {
|
if (localStorage.fabled !== undefined) {
|
||||||
store.commit("players/setFabled", {
|
store.commit("players/setFabled", {
|
||||||
fabled: JSON.parse(localStorage.fabled).map(
|
fabled: JSON.parse(localStorage.fabled).map(
|
||||||
fabled => store.state.fabled.get(fabled.id) || fabled
|
(fabled) => store.state.fabled.get(fabled.id) || fabled,
|
||||||
)
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (localStorage.players) {
|
if (localStorage.players) {
|
||||||
store.commit(
|
store.commit(
|
||||||
"players/set",
|
"players/set",
|
||||||
JSON.parse(localStorage.players).map(player => ({
|
JSON.parse(localStorage.players).map((player) => ({
|
||||||
...player,
|
...player,
|
||||||
role:
|
role:
|
||||||
store.state.roles.get(player.role) ||
|
store.state.roles.get(player.role) ||
|
||||||
store.getters.rolesJSONbyId.get(player.role) ||
|
store.getters.rolesJSONbyId.get(player.role) ||
|
||||||
{}
|
{},
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/**** Session related data *****/
|
/**** Session related data *****/
|
||||||
|
@ -141,17 +141,17 @@ module.exports = store => {
|
||||||
case "players/setBluff":
|
case "players/setBluff":
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"bluffs",
|
"bluffs",
|
||||||
JSON.stringify(state.players.bluffs.map(({ id }) => id))
|
JSON.stringify(state.players.bluffs.map(({ id }) => id)),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "players/setFabled":
|
case "players/setFabled":
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"fabled",
|
"fabled",
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
state.players.fabled.map(fabled =>
|
state.players.fabled.map((fabled) =>
|
||||||
fabled.isCustom ? fabled : { id: fabled.id }
|
fabled.isCustom ? fabled : { id: fabled.id },
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "players/add":
|
case "players/add":
|
||||||
|
@ -165,12 +165,12 @@ module.exports = store => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"players",
|
"players",
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
state.players.players.map(player => ({
|
state.players.players.map((player) => ({
|
||||||
...player,
|
...player,
|
||||||
// simplify the stored data
|
// simplify the stored data
|
||||||
role: player.role.id || {}
|
role: player.role.id || {},
|
||||||
}))
|
})),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("players");
|
localStorage.removeItem("players");
|
||||||
|
@ -180,7 +180,7 @@ module.exports = store => {
|
||||||
if (payload) {
|
if (payload) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"session",
|
"session",
|
||||||
JSON.stringify([state.session.isSpectator, payload])
|
JSON.stringify([state.session.isSpectator, payload]),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("session");
|
localStorage.removeItem("session");
|
||||||
|
|
|
@ -28,11 +28,11 @@ class LiveSession {
|
||||||
this._wss +
|
this._wss +
|
||||||
channel +
|
channel +
|
||||||
"/" +
|
"/" +
|
||||||
(this._isSpectator ? this._store.state.session.playerId : "host")
|
(this._isSpectator ? this._store.state.session.playerId : "host"),
|
||||||
);
|
);
|
||||||
this._socket.addEventListener("message", this._handleMessage.bind(this));
|
this._socket.addEventListener("message", this._handleMessage.bind(this));
|
||||||
this._socket.onopen = this._onOpen.bind(this);
|
this._socket.onopen = this._onOpen.bind(this);
|
||||||
this._socket.onclose = err => {
|
this._socket.onclose = (err) => {
|
||||||
this._socket = null;
|
this._socket = null;
|
||||||
clearInterval(this._pingTimer);
|
clearInterval(this._pingTimer);
|
||||||
this._pingTimer = null;
|
this._pingTimer = null;
|
||||||
|
@ -41,7 +41,7 @@ class LiveSession {
|
||||||
this._store.commit("session/setReconnecting", true);
|
this._store.commit("session/setReconnecting", true);
|
||||||
this._reconnectTimer = setTimeout(
|
this._reconnectTimer = setTimeout(
|
||||||
() => this.connect(channel),
|
() => this.connect(channel),
|
||||||
3 * 1000
|
3 * 1000,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this._store.commit("session/setSessionId", "");
|
this._store.commit("session/setSessionId", "");
|
||||||
|
@ -87,7 +87,7 @@ class LiveSession {
|
||||||
this._sendDirect(
|
this._sendDirect(
|
||||||
"host",
|
"host",
|
||||||
"getGamestate",
|
"getGamestate",
|
||||||
this._store.state.session.playerId
|
this._store.state.session.playerId,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.sendGamestate();
|
this.sendGamestate();
|
||||||
|
@ -105,7 +105,7 @@ class LiveSession {
|
||||||
this._isSpectator
|
this._isSpectator
|
||||||
? this._store.state.session.playerId
|
? this._store.state.session.playerId
|
||||||
: Object.keys(this._players).length,
|
: Object.keys(this._players).length,
|
||||||
"latency"
|
"latency",
|
||||||
]);
|
]);
|
||||||
clearTimeout(this._pingTimer);
|
clearTimeout(this._pingTimer);
|
||||||
this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval);
|
this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval);
|
||||||
|
@ -151,7 +151,7 @@ class LiveSession {
|
||||||
// create vote history record
|
// create vote history record
|
||||||
this._store.commit(
|
this._store.commit(
|
||||||
"session/addHistory",
|
"session/addHistory",
|
||||||
this._store.state.players.players
|
this._store.state.players.players,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._store.commit("session/nomination", { nomination: params });
|
this._store.commit("session/nomination", { nomination: params });
|
||||||
|
@ -229,9 +229,7 @@ class LiveSession {
|
||||||
if (!this._store.state.session.playerId) {
|
if (!this._store.state.session.playerId) {
|
||||||
this._store.commit(
|
this._store.commit(
|
||||||
"session/setPlayerId",
|
"session/setPlayerId",
|
||||||
Math.random()
|
Math.random().toString(36).substr(2),
|
||||||
.toString(36)
|
|
||||||
.substr(2)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._pings = {};
|
this._pings = {};
|
||||||
|
@ -267,7 +265,7 @@ class LiveSession {
|
||||||
*/
|
*/
|
||||||
sendGamestate(playerId = "", isLightweight = false) {
|
sendGamestate(playerId = "", isLightweight = false) {
|
||||||
if (this._isSpectator) return;
|
if (this._isSpectator) return;
|
||||||
this._gamestate = this._store.state.players.players.map(player => ({
|
this._gamestate = this._store.state.players.players.map((player) => ({
|
||||||
name: player.name,
|
name: player.name,
|
||||||
id: player.id,
|
id: player.id,
|
||||||
isDead: player.isDead,
|
isDead: player.isDead,
|
||||||
|
@ -275,12 +273,12 @@ class LiveSession {
|
||||||
pronouns: player.pronouns,
|
pronouns: player.pronouns,
|
||||||
...(player.role && player.role.team === "traveler"
|
...(player.role && player.role.team === "traveler"
|
||||||
? { roleId: player.role.id }
|
? { roleId: player.role.id }
|
||||||
: {})
|
: {}),
|
||||||
}));
|
}));
|
||||||
if (isLightweight) {
|
if (isLightweight) {
|
||||||
this._sendDirect(playerId, "gs", {
|
this._sendDirect(playerId, "gs", {
|
||||||
gamestate: this._gamestate,
|
gamestate: this._gamestate,
|
||||||
isLightweight
|
isLightweight,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const { session, grimoire } = this._store.state;
|
const { session, grimoire } = this._store.state;
|
||||||
|
@ -298,8 +296,8 @@ class LiveSession {
|
||||||
lockedVote: session.lockedVote,
|
lockedVote: session.lockedVote,
|
||||||
isVoteInProgress: session.isVoteInProgress,
|
isVoteInProgress: session.isVoteInProgress,
|
||||||
markedPlayer: session.markedPlayer,
|
markedPlayer: session.markedPlayer,
|
||||||
fabled: fabled.map(f => (f.isCustom ? f : { id: f.id })),
|
fabled: fabled.map((f) => (f.isCustom ? f : { id: f.id })),
|
||||||
...(session.nomination ? { votes: session.votes } : {})
|
...(session.nomination ? { votes: session.votes } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,7 +323,7 @@ class LiveSession {
|
||||||
lockedVote,
|
lockedVote,
|
||||||
isVoteInProgress,
|
isVoteInProgress,
|
||||||
markedPlayer,
|
markedPlayer,
|
||||||
fabled
|
fabled,
|
||||||
} = data;
|
} = data;
|
||||||
const players = this._store.state.players.players;
|
const players = this._store.state.players.players;
|
||||||
// adjust number of players
|
// adjust number of players
|
||||||
|
@ -343,7 +341,7 @@ class LiveSession {
|
||||||
const player = players[x];
|
const player = players[x];
|
||||||
const { roleId } = state;
|
const { roleId } = state;
|
||||||
// update relevant properties
|
// update relevant properties
|
||||||
["name", "id", "isDead", "isVoteless", "pronouns"].forEach(property => {
|
["name", "id", "isDead", "isVoteless", "pronouns"].forEach((property) => {
|
||||||
const value = state[property];
|
const value = state[property];
|
||||||
if (player[property] !== value) {
|
if (player[property] !== value) {
|
||||||
this._store.commit("players/update", { player, property, value });
|
this._store.commit("players/update", { player, property, value });
|
||||||
|
@ -358,14 +356,14 @@ class LiveSession {
|
||||||
this._store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value: role
|
value: role,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (!roleId && player.role.team === "traveler") {
|
} else if (!roleId && player.role.team === "traveler") {
|
||||||
this._store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value: {}
|
value: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -380,11 +378,11 @@ class LiveSession {
|
||||||
votes,
|
votes,
|
||||||
votingSpeed,
|
votingSpeed,
|
||||||
lockedVote,
|
lockedVote,
|
||||||
isVoteInProgress
|
isVoteInProgress,
|
||||||
});
|
});
|
||||||
this._store.commit("session/setMarkedPlayer", markedPlayer);
|
this._store.commit("session/setMarkedPlayer", markedPlayer);
|
||||||
this._store.commit("players/setFabled", {
|
this._store.commit("players/setFabled", {
|
||||||
fabled: fabled.map(f => this._store.state.fabled.get(f.id) || f)
|
fabled: fabled.map((f) => this._store.state.fabled.get(f.id) || f),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -402,7 +400,7 @@ class LiveSession {
|
||||||
}
|
}
|
||||||
this._sendDirect(playerId, "edition", {
|
this._sendDirect(playerId, "edition", {
|
||||||
edition: edition.isOfficial ? { id: edition.id } : edition,
|
edition: edition.isOfficial ? { id: edition.id } : edition,
|
||||||
...(roles ? { roles } : {})
|
...(roles ? { roles } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +425,7 @@ class LiveSession {
|
||||||
alert(
|
alert(
|
||||||
`This session contains custom characters that can't be found. ` +
|
`This session contains custom characters that can't be found. ` +
|
||||||
`Please load them before joining! ` +
|
`Please load them before joining! ` +
|
||||||
`Missing roles: ${missing.join(", ")}`
|
`Missing roles: ${missing.join(", ")}`,
|
||||||
);
|
);
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
this._store.commit("toggleModal", "edition");
|
this._store.commit("toggleModal", "edition");
|
||||||
|
@ -443,7 +441,7 @@ class LiveSession {
|
||||||
const { fabled } = this._store.state.players;
|
const { fabled } = this._store.state.players;
|
||||||
this._send(
|
this._send(
|
||||||
"fabled",
|
"fabled",
|
||||||
fabled.map(f => (f.isCustom ? f : { id: f.id }))
|
fabled.map((f) => (f.isCustom ? f : { id: f.id })),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,7 +453,7 @@ class LiveSession {
|
||||||
_updateFabled(fabled) {
|
_updateFabled(fabled) {
|
||||||
if (!this._isSpectator) return;
|
if (!this._isSpectator) return;
|
||||||
this._store.commit("players/setFabled", {
|
this._store.commit("players/setFabled", {
|
||||||
fabled: fabled.map(f => this._store.state.fabled.get(f.id) || f)
|
fabled: fabled.map((f) => this._store.state.fabled.get(f.id) || f),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,7 +473,7 @@ class LiveSession {
|
||||||
this._send("player", {
|
this._send("player", {
|
||||||
index,
|
index,
|
||||||
property,
|
property,
|
||||||
value: value.id
|
value: value.id,
|
||||||
});
|
});
|
||||||
} else if (this._gamestate[index].roleId) {
|
} else if (this._gamestate[index].roleId) {
|
||||||
// player was previously a traveler
|
// player was previously a traveler
|
||||||
|
@ -505,7 +503,7 @@ class LiveSession {
|
||||||
this._store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value: {}
|
value: {},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// load role, first from session, the global, then fail gracefully
|
// load role, first from session, the global, then fail gracefully
|
||||||
|
@ -516,7 +514,7 @@ class LiveSession {
|
||||||
this._store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value: role
|
value: role,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -556,7 +554,7 @@ class LiveSession {
|
||||||
player,
|
player,
|
||||||
property: "pronouns",
|
property: "pronouns",
|
||||||
value,
|
value,
|
||||||
isFromSockets: true
|
isFromSockets: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,12 +575,12 @@ class LiveSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remove claimed seats from players that are no longer connected
|
// remove claimed seats from players that are no longer connected
|
||||||
this._store.state.players.players.forEach(player => {
|
this._store.state.players.players.forEach((player) => {
|
||||||
if (player.id && !this._players[player.id]) {
|
if (player.id && !this._players[player.id]) {
|
||||||
this._store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "id",
|
property: "id",
|
||||||
value: ""
|
value: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -596,7 +594,7 @@ class LiveSession {
|
||||||
const pings = Object.values(this._pings);
|
const pings = Object.values(this._pings);
|
||||||
this._store.commit(
|
this._store.commit(
|
||||||
"session/setPing",
|
"session/setPing",
|
||||||
Math.round(pings.reduce((a, b) => a + b, 0) / pings.length)
|
Math.round(pings.reduce((a, b) => a + b, 0) / pings.length),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -608,7 +606,7 @@ class LiveSession {
|
||||||
if (!this._isSpectator || playerIdOrCount) {
|
if (!this._isSpectator || playerIdOrCount) {
|
||||||
this._store.commit(
|
this._store.commit(
|
||||||
"session/setPlayerCount",
|
"session/setPlayerCount",
|
||||||
this._isSpectator ? playerIdOrCount : Object.keys(this._players).length
|
this._isSpectator ? playerIdOrCount : Object.keys(this._players).length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -623,7 +621,7 @@ class LiveSession {
|
||||||
delete this._players[playerId];
|
delete this._players[playerId];
|
||||||
this._store.commit(
|
this._store.commit(
|
||||||
"session/setPlayerCount",
|
"session/setPlayerCount",
|
||||||
Object.keys(this._players).length
|
Object.keys(this._players).length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,7 +654,7 @@ class LiveSession {
|
||||||
this._store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player: players[oldIndex],
|
player: players[oldIndex],
|
||||||
property,
|
property,
|
||||||
value: ""
|
value: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// add playerId to new seat
|
// add playerId to new seat
|
||||||
|
@ -680,7 +678,7 @@ class LiveSession {
|
||||||
if (player.id && player.role) {
|
if (player.id && player.role) {
|
||||||
message[player.id] = [
|
message[player.id] = [
|
||||||
"player",
|
"player",
|
||||||
{ index, property: "role", value: player.role.id }
|
{ index, property: "role", value: player.role.id },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -755,7 +753,7 @@ class LiveSession {
|
||||||
if (this._isSpectator) return;
|
if (this._isSpectator) return;
|
||||||
this._send(
|
this._send(
|
||||||
"isVoteHistoryAllowed",
|
"isVoteHistoryAllowed",
|
||||||
this._store.state.session.isVoteHistoryAllowed
|
this._store.state.session.isVoteHistoryAllowed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -802,7 +800,7 @@ class LiveSession {
|
||||||
this._send("vote", [
|
this._send("vote", [
|
||||||
index,
|
index,
|
||||||
this._store.state.session.votes[index],
|
this._store.state.session.votes[index],
|
||||||
!this._isSpectator
|
!this._isSpectator,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -881,7 +879,7 @@ class LiveSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default store => {
|
export default (store) => {
|
||||||
// setup
|
// setup
|
||||||
const session = new LiveSession(store);
|
const session = new LiveSession(store);
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,4 @@ $outsider: #46d5ff;
|
||||||
$minion: #ff6900;
|
$minion: #ff6900;
|
||||||
$demon: #ce0100;
|
$demon: #ce0100;
|
||||||
$traveler: #cc04ff;
|
$traveler: #cc04ff;
|
||||||
|
$default: #4E4E4E;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// if the app is supposed to run on Github Pages in a subfolder, use the following config:
|
// if the app is supposed to run on Github Pages in a subfolder, use the following config:
|
||||||
// publicPath: process.env.NODE_ENV === "production" ? "/townsquare/" : "/"
|
// publicPath: process.env.NODE_ENV === "production" ? "/townsquare/" : "/"
|
||||||
publicPath: process.env.NODE_ENV === "production" ? "/" : "/"
|
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue