mirror of https://github.com/bra1n/townsquare.git
commit
ef4241308e
|
@ -18,6 +18,9 @@ on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'gh-pages'
|
- 'gh-pages'
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Set the Job #
|
# Set the Job #
|
||||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -1,10 +1,57 @@
|
||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
## Version 2.6.0
|
||||||
|
- night mode can be toggeled with [S] now (thanks @davotronic5000)
|
||||||
|
- night order shows which players are dead
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version 2.5.0
|
||||||
|
- all travelers from the base editions are now optionally available (thanks @davotronic5000)
|
||||||
|
- night order shows player names near roles now
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version 2.4.0
|
||||||
|
- added spoiler role (Pixie!)
|
||||||
|
- fixed bug with ST sending out roles that are not part of the current edition / script (ie. travelers or base set roles)
|
||||||
|
- better Lycanthrope icon (thanks @AWConant)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version 2.3.1
|
||||||
|
- better vote history design and added timestamps
|
||||||
|
- adjusted player menu styling on smaller screens
|
||||||
|
- improved CONTRIBUTING.md description of hosting your own copy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version 2.3.0
|
||||||
|
- added spoiler role (Lycanthrope!)
|
||||||
|
- fixed copy to clipboard in Firefox
|
||||||
|
- fixed non-countdown votes still playing countdown sound for a split second
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version 2.2.1
|
||||||
|
- clearing players / roles now also clears Fabled
|
||||||
|
- fix list of locked votes showing unlocked votes sometimes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version 2.2.0
|
||||||
|
- added [V] hotkey to open nomination history (thanks @lilserf)
|
||||||
|
- updated roles according to official Wiki changes
|
||||||
|
- adjusted roles night order
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Version 2.1.1
|
## Version 2.1.1
|
||||||
- show vote results at the end of a vote
|
- show vote results at the end of a vote
|
||||||
- fixed global reminders not showing up anymore when the associated role is assigned to a player
|
- fixed global reminders not showing up anymore when the associated role is assigned to a player
|
||||||
- adjusted backend metrics
|
- adjusted backend metrics
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Version 2.1.0
|
## Version 2.1.0
|
||||||
|
|
|
@ -43,6 +43,20 @@ $ npm install
|
||||||
|
|
||||||
The development server can be started with `npm run serve`.
|
The development server can be started with `npm run serve`.
|
||||||
|
|
||||||
|
### Deploying to GitHub pages or with a non-root path
|
||||||
|
|
||||||
|
Deploying a forked version to GitHub Pages or running your local
|
||||||
|
development copy in a sub-path (instead of the web root) will require you to modify
|
||||||
|
the `vue.config.js` and configure the path at which the website will be served.
|
||||||
|
|
||||||
|
For example, deploying your forked `townsquare` project to GitHub pages would need the following
|
||||||
|
`vue.config.js` changes:
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
publicPath: process.env.NODE_ENV === "production" ? "/townsquare/" : "/"
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### Committing Changes
|
### Committing Changes
|
||||||
|
|
||||||
Commit messages should be verbose enough to allow someone else to follow your changes and should include references to issues that are being worked on.
|
Commit messages should be verbose enough to allow someone else to follow your changes and should include references to issues that are being worked on.
|
||||||
|
@ -64,6 +78,10 @@ $ npm run lint
|
||||||
|
|
||||||
- **`dist`**: contains built files for distribution.
|
- **`dist`**: contains built files for distribution.
|
||||||
|
|
||||||
|
- **`public`**: static assets and templates that don't need to be built dynamically.
|
||||||
|
|
||||||
|
- **`server`**: NodeJS code for the live session backend server.
|
||||||
|
|
||||||
- **`src`**: contains the source code. The codebase is written in ES2015.
|
- **`src`**: contains the source code. The codebase is written in ES2015.
|
||||||
|
|
||||||
- **`assets`**: contains all graphical assets like images, fonts, icons, etc.
|
- **`assets`**: contains all graphical assets like images, fonts, icons, etc.
|
||||||
|
@ -73,9 +91,13 @@ $ npm run lint
|
||||||
- **`fonts`**: webfonts used on the page
|
- **`fonts`**: webfonts used on the page
|
||||||
|
|
||||||
- **`icons`**: character token icons
|
- **`icons`**: character token icons
|
||||||
|
|
||||||
|
- **`sounds`**: sound effects used on the page
|
||||||
|
|
||||||
- **`components`**: the internal components used in the project
|
- **`components`**: the internal components used in the project
|
||||||
|
|
||||||
- **`modals`**: the modals have a separate subfolder
|
- **`modals`**: the modals have a separate subfolder
|
||||||
|
|
||||||
- **`store`**: the VueX data store and modules
|
- **`store`**: the VueX data store and modules
|
||||||
|
|
||||||
|
- **`modules`**: VueX modules that live in their own namespace
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "townsquare",
|
"name": "townsquare",
|
||||||
"version": "2.1.1",
|
"version": "2.6.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "townsquare",
|
"name": "townsquare",
|
||||||
"version": "2.1.1",
|
"version": "2.6.0",
|
||||||
"description": "Blood on the Clocktower Town Square",
|
"description": "Blood on the Clocktower Town Square",
|
||||||
"author": "Steffen Baumgart",
|
"author": "Steffen Baumgart",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -102,6 +102,15 @@ export default {
|
||||||
if (this.session.isSpectator) return;
|
if (this.session.isSpectator) return;
|
||||||
this.$store.commit("toggleModal", "roles");
|
this.$store.commit("toggleModal", "roles");
|
||||||
break;
|
break;
|
||||||
|
case "v":
|
||||||
|
if (this.session.voteHistory.length) {
|
||||||
|
this.$store.commit("toggleModal", "voteHistory");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "s":
|
||||||
|
if (this.session.isSpectator) return;
|
||||||
|
this.$store.commit("toggleNight");
|
||||||
|
break;
|
||||||
case "escape":
|
case "escape":
|
||||||
this.$store.commit("toggleModal");
|
this.$store.commit("toggleModal");
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 95 KiB |
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
After Width: | Height: | Size: 151 KiB |
|
@ -43,10 +43,7 @@
|
||||||
<li @click="toggleNight" v-if="!session.isSpectator">
|
<li @click="toggleNight" v-if="!session.isSpectator">
|
||||||
<template v-if="!grimoire.isNight">Switch to Night</template>
|
<template v-if="!grimoire.isNight">Switch to Night</template>
|
||||||
<template v-if="grimoire.isNight">Switch to Day</template>
|
<template v-if="grimoire.isNight">Switch to Day</template>
|
||||||
<em
|
<em>[S]</em>
|
||||||
><font-awesome-icon
|
|
||||||
:icon="['fas', grimoire.isNight ? 'sun' : 'cloud-moon']"
|
|
||||||
/></em>
|
|
||||||
</li>
|
</li>
|
||||||
<li @click="toggleNightOrder" v-if="players.length">
|
<li @click="toggleNightOrder" v-if="players.length">
|
||||||
Night order
|
Night order
|
||||||
|
@ -114,8 +111,7 @@
|
||||||
v-if="session.voteHistory.length"
|
v-if="session.voteHistory.length"
|
||||||
@click="toggleModal('voteHistory')"
|
@click="toggleModal('voteHistory')"
|
||||||
>
|
>
|
||||||
Nomination history
|
Nomination history<em>[V]</em>
|
||||||
<em><font-awesome-icon icon="hand-paper"/></em>
|
|
||||||
</li>
|
</li>
|
||||||
<li @click="leaveSession" v-if="session.sessionId">
|
<li @click="leaveSession" v-if="session.sessionId">
|
||||||
Leave Session
|
Leave Session
|
||||||
|
@ -239,16 +235,9 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
copySessionUrl() {
|
copySessionUrl() {
|
||||||
// check for clipboard permissions
|
const url = window.location.href.split("#")[0];
|
||||||
navigator.permissions
|
const link = url + "#" + this.session.sessionId;
|
||||||
.query({ name: "clipboard-write" })
|
navigator.clipboard.writeText(link);
|
||||||
.then(({ state }) => {
|
|
||||||
if (state === "granted" || state === "prompt") {
|
|
||||||
const url = window.location.href.split("#")[0];
|
|
||||||
const link = url + "#" + this.session.sessionId;
|
|
||||||
navigator.clipboard.writeText(link);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
distributeRoles() {
|
distributeRoles() {
|
||||||
if (this.session.isSpectator) return;
|
if (this.session.isSpectator) return;
|
||||||
|
|
|
@ -675,7 +675,7 @@ li.move:not(.from) .player .overlay svg.move {
|
||||||
border: 10px solid transparent;
|
border: 10px solid transparent;
|
||||||
border-right-color: black;
|
border-right-color: black;
|
||||||
right: 100%;
|
right: 100%;
|
||||||
bottom: 7px;
|
bottom: 5px;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,11 +161,13 @@ export default {
|
||||||
},
|
},
|
||||||
voters: function() {
|
voters: function() {
|
||||||
const nomination = this.session.nomination[1];
|
const nomination = this.session.nomination[1];
|
||||||
const voters = this.session.votes.map((vote, index) =>
|
const voters = Array(this.players.length)
|
||||||
vote ? this.players[index].name : ""
|
.fill("")
|
||||||
);
|
.map((x, index) =>
|
||||||
|
this.session.votes[index] ? this.players[index].name : ""
|
||||||
|
);
|
||||||
const reorder = [
|
const reorder = [
|
||||||
...voters.slice(nomination + 1, this.players.length),
|
...voters.slice(nomination + 1),
|
||||||
...voters.slice(0, nomination + 1)
|
...voters.slice(0, nomination + 1)
|
||||||
];
|
];
|
||||||
return reorder.slice(0, this.session.lockedVote - 1).filter(n => !!n);
|
return reorder.slice(0, this.session.lockedVote - 1).filter(n => !!n);
|
||||||
|
@ -178,15 +180,15 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
countdown() {
|
countdown() {
|
||||||
this.$store.commit("session/setVoteInProgress", true);
|
|
||||||
this.$store.commit("session/lockVote", 0);
|
this.$store.commit("session/lockVote", 0);
|
||||||
|
this.$store.commit("session/setVoteInProgress", true);
|
||||||
this.voteTimer = setInterval(() => {
|
this.voteTimer = setInterval(() => {
|
||||||
this.start();
|
this.start();
|
||||||
}, 4000);
|
}, 4000);
|
||||||
},
|
},
|
||||||
start() {
|
start() {
|
||||||
this.$store.commit("session/setVoteInProgress", true);
|
|
||||||
this.$store.commit("session/lockVote", 1);
|
this.$store.commit("session/lockVote", 1);
|
||||||
|
this.$store.commit("session/setVoteInProgress", true);
|
||||||
clearInterval(this.voteTimer);
|
clearInterval(this.voteTimer);
|
||||||
this.voteTimer = setInterval(() => {
|
this.voteTimer = setInterval(() => {
|
||||||
this.$store.commit("session/lockVote");
|
this.$store.commit("session/lockVote");
|
||||||
|
|
|
@ -54,14 +54,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copy: function() {
|
copy: function() {
|
||||||
// check for clipboard permissions
|
navigator.clipboard.writeText(this.input || this.gamestate);
|
||||||
navigator.permissions
|
|
||||||
.query({ name: "clipboard-write" })
|
|
||||||
.then(({ state }) => {
|
|
||||||
if (state === "granted" || state === "prompt") {
|
|
||||||
navigator.clipboard.writeText(this.input || this.gamestate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
load: function() {
|
load: function() {
|
||||||
if (this.session.isSpectator) return;
|
if (this.session.isSpectator) return;
|
||||||
|
|
|
@ -25,6 +25,17 @@
|
||||||
>
|
>
|
||||||
<span class="name">
|
<span class="name">
|
||||||
{{ role.name }}
|
{{ role.name }}
|
||||||
|
<template v-if="role.players.length">
|
||||||
|
<br />
|
||||||
|
<small
|
||||||
|
v-for="(player, index) in role.players"
|
||||||
|
:class="{ dead: player.isDead }"
|
||||||
|
:key="index"
|
||||||
|
>{{
|
||||||
|
player.name + (role.players.length > index + 1 ? "," : "")
|
||||||
|
}}</small
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
|
@ -53,6 +64,17 @@
|
||||||
></span>
|
></span>
|
||||||
<span class="name">
|
<span class="name">
|
||||||
{{ role.name }}
|
{{ role.name }}
|
||||||
|
<template v-if="role.players.length">
|
||||||
|
<br />
|
||||||
|
<small
|
||||||
|
v-for="(player, index) in role.players"
|
||||||
|
:class="{ dead: player.isDead }"
|
||||||
|
:key="index"
|
||||||
|
>{{
|
||||||
|
player.name + (role.players.length > index + 1 ? "," : "")
|
||||||
|
}}</small
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -68,11 +90,6 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal
|
||||||
},
|
},
|
||||||
data: function() {
|
|
||||||
return {
|
|
||||||
roleSelection: {}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
rolesFirstNight: function() {
|
rolesFirstNight: function() {
|
||||||
const rolesFirstNight = [];
|
const rolesFirstNight = [];
|
||||||
|
@ -83,23 +100,22 @@ export default {
|
||||||
id: "evil",
|
id: "evil",
|
||||||
name: "Minion info",
|
name: "Minion info",
|
||||||
firstNight: 2,
|
firstNight: 2,
|
||||||
team: "minion"
|
team: "minion",
|
||||||
|
players: this.players.filter(p => p.role.team === "minion")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "evil",
|
id: "evil",
|
||||||
name: "Demon info & bluffs",
|
name: "Demon info & bluffs",
|
||||||
firstNight: 4,
|
firstNight: 4,
|
||||||
team: "demon"
|
team: "demon",
|
||||||
|
players: this.players.filter(p => p.role.team === "demon")
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach(role => {
|
||||||
if (
|
const players = this.players.filter(p => p.role.id === role.id);
|
||||||
role.firstNight &&
|
if (role.firstNight && (role.team !== "traveler" || players.length)) {
|
||||||
(role.team !== "traveler" ||
|
rolesFirstNight.push(Object.assign({ players }, role));
|
||||||
this.players.some(p => p.role.id === role.id))
|
|
||||||
) {
|
|
||||||
rolesFirstNight.push(role);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.fabled
|
this.fabled
|
||||||
|
@ -113,12 +129,9 @@ export default {
|
||||||
rolesOtherNight: function() {
|
rolesOtherNight: function() {
|
||||||
const rolesOtherNight = [];
|
const rolesOtherNight = [];
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach(role => {
|
||||||
if (
|
const players = this.players.filter(p => p.role.id === role.id);
|
||||||
role.otherNight &&
|
if (role.otherNight && (role.team !== "traveler" || players.length)) {
|
||||||
(role.team !== "traveler" ||
|
rolesOtherNight.push(Object.assign({ players }, role));
|
||||||
this.players.some(p => p.role.id === role.id))
|
|
||||||
) {
|
|
||||||
rolesOtherNight.push(role);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.fabled
|
this.fabled
|
||||||
|
@ -179,57 +192,42 @@ h4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.fabled {
|
.fabled {
|
||||||
.name,
|
.name {
|
||||||
.player,
|
background: linear-gradient(90deg, $fabled, transparent 35%);
|
||||||
h4 {
|
.night .other & {
|
||||||
color: $fabled;
|
background: linear-gradient(-90deg, $fabled, transparent 35%);
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
background-color: $fabled;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.townsfolk {
|
.townsfolk {
|
||||||
.name,
|
.name {
|
||||||
.player,
|
background: linear-gradient(90deg, $townsfolk, transparent 35%);
|
||||||
h4 {
|
.night .other & {
|
||||||
color: $townsfolk;
|
background: linear-gradient(-90deg, $townsfolk, transparent 35%);
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
background-color: $townsfolk;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.outsider {
|
.outsider {
|
||||||
.name,
|
.name {
|
||||||
.player,
|
background: linear-gradient(90deg, $outsider, transparent 35%);
|
||||||
h4 {
|
.night .other & {
|
||||||
color: $outsider;
|
background: linear-gradient(-90deg, $outsider, transparent 35%);
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
background-color: $outsider;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.minion {
|
.minion {
|
||||||
.name,
|
.name {
|
||||||
.player,
|
background: linear-gradient(90deg, $minion, transparent 35%);
|
||||||
h4 {
|
.night .other & {
|
||||||
color: $minion;
|
background: linear-gradient(-90deg, $minion, transparent 35%);
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
background-color: $minion;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.demon {
|
.demon {
|
||||||
.name,
|
.name {
|
||||||
.player,
|
background: linear-gradient(90deg, $demon, transparent 35%);
|
||||||
h4 {
|
.night .other & {
|
||||||
color: $demon;
|
background: linear-gradient(-90deg, $demon, transparent 35%);
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
background-color: $demon;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,20 +235,15 @@ ul {
|
||||||
li {
|
li {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
margin-bottom: 3px;
|
||||||
align-content: center;
|
|
||||||
/*background: linear-gradient(0deg, #ffffff0f, transparent);*/
|
|
||||||
border-radius: 10px;
|
|
||||||
.icon {
|
.icon {
|
||||||
width: 6vh;
|
width: 6vh;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: 0 -5px;
|
background-position: 0 0;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin: 0 10px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-left: 1px solid #ffffff1f;
|
margin: 0 2px;
|
||||||
border-right: 1px solid #ffffff1f;
|
|
||||||
&:after {
|
&:after {
|
||||||
content: " ";
|
content: " ";
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -261,19 +254,18 @@ ul {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 15%;
|
width: 15%;
|
||||||
font-weight: bold;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-family: "Papyrus", sans-serif;
|
|
||||||
font-size: 110%;
|
font-size: 110%;
|
||||||
}
|
padding: 5px;
|
||||||
.player {
|
border-left: 1px solid rgba(255, 255, 255, 0.4);
|
||||||
flex-grow: 0;
|
border-right: 1px solid rgba(255, 255, 255, 0.4);
|
||||||
flex-shrink: 1;
|
small {
|
||||||
text-align: right;
|
color: #888;
|
||||||
margin: 0 10px;
|
margin-right: 5px;
|
||||||
}
|
&.dead {
|
||||||
.ability {
|
text-decoration: line-through;
|
||||||
flex-grow: 1;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.legend {
|
&.legend {
|
||||||
|
@ -307,28 +299,23 @@ ul {
|
||||||
.headline {
|
.headline {
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-bottom: 1px solid white;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.4);
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.icon {
|
|
||||||
border-color: white;
|
|
||||||
}
|
|
||||||
.name {
|
.name {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
.first {
|
.first {
|
||||||
.icon {
|
.name {
|
||||||
border-right: 0;
|
border-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.other {
|
.other {
|
||||||
li .name {
|
li .name {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
border-right: 0;
|
||||||
.icon {
|
|
||||||
border-left: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,11 +56,6 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal
|
||||||
},
|
},
|
||||||
data: function() {
|
|
||||||
return {
|
|
||||||
roleSelection: {}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
rolesGrouped: function() {
|
rolesGrouped: function() {
|
||||||
const rolesGrouped = {};
|
const rolesGrouped = {};
|
||||||
|
@ -136,7 +131,6 @@ h4 {
|
||||||
|
|
||||||
.townsfolk {
|
.townsfolk {
|
||||||
.name,
|
.name,
|
||||||
.player,
|
|
||||||
h4 {
|
h4 {
|
||||||
color: $townsfolk;
|
color: $townsfolk;
|
||||||
&:before,
|
&:before,
|
||||||
|
@ -147,7 +141,6 @@ h4 {
|
||||||
}
|
}
|
||||||
.outsider {
|
.outsider {
|
||||||
.name,
|
.name,
|
||||||
.player,
|
|
||||||
h4 {
|
h4 {
|
||||||
color: $outsider;
|
color: $outsider;
|
||||||
&:before,
|
&:before,
|
||||||
|
@ -158,7 +151,6 @@ h4 {
|
||||||
}
|
}
|
||||||
.minion {
|
.minion {
|
||||||
.name,
|
.name,
|
||||||
.player,
|
|
||||||
h4 {
|
h4 {
|
||||||
color: $minion;
|
color: $minion;
|
||||||
&:before,
|
&:before,
|
||||||
|
@ -169,7 +161,6 @@ h4 {
|
||||||
}
|
}
|
||||||
.demon {
|
.demon {
|
||||||
.name,
|
.name,
|
||||||
.player,
|
|
||||||
h4 {
|
h4 {
|
||||||
color: $demon;
|
color: $demon;
|
||||||
&:before,
|
&:before,
|
||||||
|
@ -208,7 +199,6 @@ ul {
|
||||||
width: 15%;
|
width: 15%;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-family: "Papyrus", sans-serif;
|
|
||||||
font-size: 110%;
|
font-size: 110%;
|
||||||
}
|
}
|
||||||
.player {
|
.player {
|
||||||
|
@ -216,6 +206,8 @@ ul {
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
|
color: #888;
|
||||||
|
font-size: smaller;
|
||||||
}
|
}
|
||||||
.ability {
|
.ability {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@ -230,6 +222,7 @@ ul {
|
||||||
height: auto;
|
height: auto;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
.icon:after {
|
.icon:after {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|
|
@ -82,6 +82,21 @@ export default {
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// add out of script traveler reminders
|
||||||
|
this.$store.state.otherTravelers.forEach(role => {
|
||||||
|
if (players.some(p => p.role.id === role.id)) {
|
||||||
|
reminders = [
|
||||||
|
...reminders,
|
||||||
|
...role.reminders.map(name => ({
|
||||||
|
role: role.id,
|
||||||
|
image: role.image,
|
||||||
|
name
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
reminders.push({ role: "good", name: "Good" });
|
reminders.push({ role: "good", name: "Good" });
|
||||||
reminders.push({ role: "evil", name: "Evil" });
|
reminders.push({ role: "evil", name: "Evil" });
|
||||||
reminders.push({ role: "custom", name: "Custom note" });
|
reminders.push({ role: "custom", name: "Custom note" });
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<Modal
|
<Modal v-if="modals.role && availableRoles.length" @close="close">
|
||||||
v-if="modals.role && availableRoles.length"
|
|
||||||
@close="toggleModal('role')"
|
|
||||||
>
|
|
||||||
<h3>
|
<h3>
|
||||||
Choose a new character for
|
Choose a new character for
|
||||||
{{
|
{{
|
||||||
|
@ -11,7 +8,7 @@
|
||||||
: "bluffing"
|
: "bluffing"
|
||||||
}}
|
}}
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="tokens">
|
<ul class="tokens" v-if="tab === 'editionRoles' || !otherTravelers.size">
|
||||||
<li
|
<li
|
||||||
v-for="role in availableRoles"
|
v-for="role in availableRoles"
|
||||||
:class="[role.team]"
|
:class="[role.team]"
|
||||||
|
@ -21,6 +18,33 @@
|
||||||
<Token :role="role" />
|
<Token :role="role" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ul class="tokens" v-if="tab === 'otherTravelers' && otherTravelers.size">
|
||||||
|
<li
|
||||||
|
v-for="role in otherTravelers.values()"
|
||||||
|
:class="[role.team]"
|
||||||
|
:key="role.id"
|
||||||
|
@click="setRole(role)"
|
||||||
|
>
|
||||||
|
<Token :role="role" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div
|
||||||
|
class="button-group"
|
||||||
|
v-if="playerIndex >= 0 && otherTravelers.size && !session.isSpectator"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="button"
|
||||||
|
:class="{ townsfolk: tab === 'editionRoles' }"
|
||||||
|
@click="tab = 'editionRoles'"
|
||||||
|
>Edtition Roles</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="button"
|
||||||
|
:class="{ townsfolk: tab === 'otherTravelers' }"
|
||||||
|
@click="tab = 'otherTravelers'"
|
||||||
|
>Other Travelers</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -50,7 +74,13 @@ export default {
|
||||||
return availableRoles;
|
return availableRoles;
|
||||||
},
|
},
|
||||||
...mapState(["modals", "roles", "session"]),
|
...mapState(["modals", "roles", "session"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
|
...mapState(["otherTravelers"])
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tab: "editionRoles"
|
||||||
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setRole(role) {
|
setRole(role) {
|
||||||
|
@ -72,6 +102,10 @@ export default {
|
||||||
}
|
}
|
||||||
this.$store.commit("toggleModal", "role");
|
this.$store.commit("toggleModal", "role");
|
||||||
},
|
},
|
||||||
|
close() {
|
||||||
|
this.tab = "editionRoles";
|
||||||
|
this.toggleModal("role");
|
||||||
|
},
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,22 +15,50 @@
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>Time</td>
|
||||||
<td>Nominator</td>
|
<td>Nominator</td>
|
||||||
<td>Nominee</td>
|
<td>Nominee</td>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
|
<td>Votes</td>
|
||||||
<td>Majority</td>
|
<td>Majority</td>
|
||||||
<td><font-awesome-icon icon="hand-paper" /> Hand up</td>
|
<td>
|
||||||
|
<font-awesome-icon icon="user-friends" />
|
||||||
|
Voters
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(vote, index) in session.voteHistory" :key="index">
|
<tr v-for="(vote, index) in session.voteHistory" :key="index">
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
vote.timestamp
|
||||||
|
.getHours()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")
|
||||||
|
}}:{{
|
||||||
|
vote.timestamp
|
||||||
|
.getMinutes()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
<td>{{ vote.nominator }}</td>
|
<td>{{ vote.nominator }}</td>
|
||||||
<td>{{ vote.nominee }}</td>
|
<td>{{ vote.nominee }}</td>
|
||||||
<td>{{ vote.type }}</td>
|
<td>{{ vote.type }}</td>
|
||||||
<td>{{ vote.majority }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
{{ vote.votes.length }}
|
{{ vote.votes.length }}
|
||||||
<font-awesome-icon icon="user-friends" />
|
<font-awesome-icon icon="hand-paper" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ vote.majority }}
|
||||||
|
<font-awesome-icon
|
||||||
|
:icon="[
|
||||||
|
'fas',
|
||||||
|
vote.votes.length >= vote.majority ? 'check-square' : 'square'
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
{{ vote.votes.join(", ") }}
|
{{ vote.votes.join(", ") }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -89,13 +117,16 @@ thead td {
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody {
|
tbody {
|
||||||
td:nth-child(1) {
|
td:nth-child(2) {
|
||||||
color: $townsfolk;
|
color: $townsfolk;
|
||||||
}
|
}
|
||||||
td:nth-child(2) {
|
td:nth-child(3) {
|
||||||
color: $demon;
|
color: $demon;
|
||||||
}
|
}
|
||||||
td:nth-child(4) {
|
td:nth-child(5) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
td:nth-child(6) {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ const faIcons = [
|
||||||
"SearchMinus",
|
"SearchMinus",
|
||||||
"SearchPlus",
|
"SearchPlus",
|
||||||
"Square",
|
"Square",
|
||||||
"Sun",
|
|
||||||
"TheaterMasks",
|
"TheaterMasks",
|
||||||
"Times",
|
"Times",
|
||||||
"TimesCircle",
|
"TimesCircle",
|
||||||
|
|
|
@ -44,6 +44,12 @@
|
||||||
.player > .name {
|
.player > .name {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
.player > .menu {
|
||||||
|
bottom: 0;
|
||||||
|
&:before {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old phones
|
// Old phones
|
||||||
|
|
1578
src/roles.json
1578
src/roles.json
File diff suppressed because it is too large
Load Diff
|
@ -25,6 +25,19 @@ const getRolesByEdition = (edition = editionJSON[0]) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTravelersNotInEdition = (edition = editionJSON[0]) => {
|
||||||
|
return new Map(
|
||||||
|
rolesJSON
|
||||||
|
.filter(
|
||||||
|
r =>
|
||||||
|
r.team === "traveler" &&
|
||||||
|
r.edition !== edition.id &&
|
||||||
|
!edition.roles.includes(r.id)
|
||||||
|
)
|
||||||
|
.map(role => [role.id, role])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// base definition for custom roles
|
// base definition for custom roles
|
||||||
const imageBase =
|
const imageBase =
|
||||||
"https://raw.githubusercontent.com/bra1n/townsquare/main/src/assets/icons/";
|
"https://raw.githubusercontent.com/bra1n/townsquare/main/src/assets/icons/";
|
||||||
|
@ -70,6 +83,7 @@ export default new Vuex.Store({
|
||||||
},
|
},
|
||||||
edition: editionJSONbyId.get("tb"),
|
edition: editionJSONbyId.get("tb"),
|
||||||
roles: getRolesByEdition(),
|
roles: getRolesByEdition(),
|
||||||
|
otherTravelers: getTravelersNotInEdition(),
|
||||||
fabled
|
fabled
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -180,11 +194,18 @@ export default new Vuex.Store({
|
||||||
// convert to Map
|
// convert to Map
|
||||||
.map(role => [role.id, role])
|
.map(role => [role.id, role])
|
||||||
);
|
);
|
||||||
|
// update extraTravelers map to only show travelers not in this script
|
||||||
|
state.otherTravelers = new Map(
|
||||||
|
rolesJSON
|
||||||
|
.filter(r => r.team === "traveler" && !roles.some(i => i.id === r.id))
|
||||||
|
.map(role => [role.id, role])
|
||||||
|
);
|
||||||
},
|
},
|
||||||
setEdition(state, edition) {
|
setEdition(state, edition) {
|
||||||
if (editionJSONbyId.has(edition.id)) {
|
if (editionJSONbyId.has(edition.id)) {
|
||||||
state.edition = editionJSONbyId.get(edition.id);
|
state.edition = editionJSONbyId.get(edition.id);
|
||||||
state.roles = getRolesByEdition(state.edition);
|
state.roles = getRolesByEdition(state.edition);
|
||||||
|
state.otherTravelers = getTravelersNotInEdition(state.edition);
|
||||||
} else {
|
} else {
|
||||||
state.edition = edition;
|
state.edition = edition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@ const actions = {
|
||||||
name,
|
name,
|
||||||
id
|
id
|
||||||
}));
|
}));
|
||||||
|
commit("setFabled", { fabled: [] });
|
||||||
}
|
}
|
||||||
commit("set", players);
|
commit("set", players);
|
||||||
commit("setBluff");
|
commit("setBluff");
|
||||||
|
@ -94,6 +95,7 @@ const mutations = {
|
||||||
clear(state) {
|
clear(state) {
|
||||||
state.players = [];
|
state.players = [];
|
||||||
state.bluffs = [];
|
state.bluffs = [];
|
||||||
|
state.fabled = [];
|
||||||
},
|
},
|
||||||
set(state, players = []) {
|
set(state, players = []) {
|
||||||
state.players = players;
|
state.players = players;
|
||||||
|
|
|
@ -72,8 +72,8 @@ const mutations = {
|
||||||
addHistory(state, players) {
|
addHistory(state, players) {
|
||||||
if (!state.nomination || state.lockedVote <= players.length) return;
|
if (!state.nomination || state.lockedVote <= players.length) return;
|
||||||
const isBanishment = players[state.nomination[1]].role.team === "traveler";
|
const isBanishment = players[state.nomination[1]].role.team === "traveler";
|
||||||
console.log(isBanishment);
|
|
||||||
state.voteHistory.push({
|
state.voteHistory.push({
|
||||||
|
timestamp: new Date(),
|
||||||
nominator: players[state.nomination[0]].name,
|
nominator: players[state.nomination[0]].name,
|
||||||
nominee: players[state.nomination[1]].name,
|
nominee: players[state.nomination[1]].name,
|
||||||
type: isBanishment ? "Banishment" : "Execution",
|
type: isBanishment ? "Banishment" : "Execution",
|
||||||
|
|
|
@ -443,8 +443,11 @@ class LiveSession {
|
||||||
value: {}
|
value: {}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// load role
|
// load role, first from session, the global, then fail gracefully
|
||||||
const role = this._store.state.roles.get(value);
|
const role =
|
||||||
|
this._store.state.roles.get(value) ||
|
||||||
|
this._store.getters.rolesJSONbyId.get(value) ||
|
||||||
|
{};
|
||||||
this._store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
|
|
Loading…
Reference in New Issue