mirror of https://github.com/bra1n/townsquare.git
commit
8f09315659
23
src/App.vue
23
src/App.vue
|
@ -10,13 +10,17 @@
|
||||||
: ''
|
: ''
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<transition name="zoom">
|
||||||
<Intro v-if="!players.length"></Intro>
|
<Intro v-if="!players.length"></Intro>
|
||||||
<TownInfo v-if="players.length"></TownInfo>
|
<TownInfo v-if="players.length && !session.nomination"></TownInfo>
|
||||||
|
<Vote v-if="session.nomination"></Vote>
|
||||||
|
</transition>
|
||||||
<TownSquare @screenshot="takeScreenshot"></TownSquare>
|
<TownSquare @screenshot="takeScreenshot"></TownSquare>
|
||||||
<Menu ref="menu"></Menu>
|
<Menu ref="menu"></Menu>
|
||||||
<EditionModal />
|
<EditionModal />
|
||||||
<RolesModal />
|
<RolesModal />
|
||||||
<ReferenceModal />
|
<ReferenceModal />
|
||||||
|
<Gradients />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -29,16 +33,20 @@ import RolesModal from "./components/modals/RolesModal";
|
||||||
import EditionModal from "./components/modals/EditionModal";
|
import EditionModal from "./components/modals/EditionModal";
|
||||||
import Intro from "./components/Intro";
|
import Intro from "./components/Intro";
|
||||||
import ReferenceModal from "./components/modals/ReferenceModal";
|
import ReferenceModal from "./components/modals/ReferenceModal";
|
||||||
|
import Vote from "./components/Vote";
|
||||||
|
import Gradients from "./components/Gradients";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Vote,
|
||||||
ReferenceModal,
|
ReferenceModal,
|
||||||
Intro,
|
Intro,
|
||||||
TownInfo,
|
TownInfo,
|
||||||
TownSquare,
|
TownSquare,
|
||||||
Menu,
|
Menu,
|
||||||
EditionModal,
|
EditionModal,
|
||||||
RolesModal
|
RolesModal,
|
||||||
|
Gradients
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["grimoire", "session"]),
|
...mapState(["grimoire", "session"]),
|
||||||
|
@ -157,6 +165,17 @@ ul {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zoom-enter-active,
|
||||||
|
.zoom-leave-active {
|
||||||
|
transition: all 250ms;
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
|
.zoom-enter,
|
||||||
|
.zoom-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
filter: blur(20px)
|
||||||
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
.button-group {
|
.button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,43 @@
|
||||||
|
<template>
|
||||||
|
<!-- SVG Gradients -->
|
||||||
|
<div id="gradients">
|
||||||
|
<svg
|
||||||
|
width="0"
|
||||||
|
height="0"
|
||||||
|
v-for="(gradient, index) in gradients"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<linearGradient :id="gradient[0]" x1="50%" y1="100%" x2="50%" y2="0%">
|
||||||
|
<stop
|
||||||
|
offset="0%"
|
||||||
|
:style="{ 'stop-color': gradient[2], 'stop-opacity': 1 }"
|
||||||
|
></stop>
|
||||||
|
<stop
|
||||||
|
offset="100%"
|
||||||
|
:style="{ 'stop-color': gradient[1], 'stop-opacity': 1 }"
|
||||||
|
></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
gradients: [
|
||||||
|
["demon", "#ce0100", "#000"],
|
||||||
|
["townsfolk", "#1f65ff", "#000"],
|
||||||
|
["default", "#4E4E4E", "#000"]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -32,11 +32,7 @@
|
||||||
v-if="!session.isSpectator"
|
v-if="!session.isSpectator"
|
||||||
@click="tab = 'players'"
|
@click="tab = 'players'"
|
||||||
/>
|
/>
|
||||||
<font-awesome-icon
|
<font-awesome-icon icon="theater-masks" @click="tab = 'characters'" />
|
||||||
icon="theater-masks"
|
|
||||||
v-if="!session.isSpectator"
|
|
||||||
@click="tab = 'characters'"
|
|
||||||
/>
|
|
||||||
<font-awesome-icon icon="question" @click="tab = 'help'" />
|
<font-awesome-icon icon="question" @click="tab = 'help'" />
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -114,14 +110,17 @@
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="tab === 'characters' && !session.isSpectator">
|
<template v-if="tab === 'characters'">
|
||||||
<!-- Characters -->
|
<!-- Characters -->
|
||||||
<li class="headline">Characters</li>
|
<li class="headline">Characters</li>
|
||||||
<li @click="toggleModal('edition')">
|
<li v-if="!session.isSpectator" @click="toggleModal('edition')">
|
||||||
<em>[E]</em>
|
<em>[E]</em>
|
||||||
Select Edition
|
Select Edition
|
||||||
</li>
|
</li>
|
||||||
<li @click="toggleModal('roles')" v-if="players.length > 4">
|
<li
|
||||||
|
@click="toggleModal('roles')"
|
||||||
|
v-if="!session.isSpectator && players.length > 4"
|
||||||
|
>
|
||||||
<em>[C]</em>
|
<em>[C]</em>
|
||||||
Choose & Assign
|
Choose & Assign
|
||||||
</li>
|
</li>
|
||||||
|
@ -190,9 +189,9 @@ export default {
|
||||||
Math.round(Math.random() * 10000)
|
Math.round(Math.random() * 10000)
|
||||||
);
|
);
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
this.$store.commit("setSpectator", false);
|
this.$store.commit("session/setSpectator", false);
|
||||||
this.$store.commit(
|
this.$store.commit(
|
||||||
"setSessionId",
|
"session/setSessionId",
|
||||||
sessionId.replace(/[^0-9a-z]/g, "").substr(0, 5)
|
sessionId.replace(/[^0-9a-z]/g, "").substr(0, 5)
|
||||||
);
|
);
|
||||||
this.copySessionUrl();
|
this.copySessionUrl();
|
||||||
|
@ -215,17 +214,17 @@ export default {
|
||||||
"Enter the channel number / name of the session you want to join"
|
"Enter the channel number / name of the session you want to join"
|
||||||
);
|
);
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
this.$store.commit("setSpectator", true);
|
this.$store.commit("session/setSpectator", true);
|
||||||
this.$store.commit(
|
this.$store.commit(
|
||||||
"setSessionId",
|
"session/setSessionId",
|
||||||
sessionId.replace(/[^0-9a-z]/g, "").substr(0, 5)
|
sessionId.replace(/[^0-9a-z]/g, "").substr(0, 5)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
leaveSession() {
|
leaveSession() {
|
||||||
if (confirm("Are you sure you want to leave the active live game?")) {
|
if (confirm("Are you sure you want to leave the active live game?")) {
|
||||||
this.$store.commit("setSpectator", false);
|
this.$store.commit("session/setSpectator", false);
|
||||||
this.$store.commit("setSessionId", "");
|
this.$store.commit("session/setSessionId", "");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addPlayer() {
|
addPlayer() {
|
||||||
|
@ -249,7 +248,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearRoles() {
|
clearRoles() {
|
||||||
if (this.session.isSpectator) return;
|
|
||||||
if (confirm("Are you sure you want to remove all player roles?")) {
|
if (confirm("Are you sure you want to remove all player roles?")) {
|
||||||
this.$store.dispatch("players/clearRoles");
|
this.$store.dispatch("players/clearRoles");
|
||||||
this.$store.commit("setBluff");
|
this.$store.commit("setBluff");
|
||||||
|
|
|
@ -3,11 +3,16 @@
|
||||||
<div
|
<div
|
||||||
ref="player"
|
ref="player"
|
||||||
class="player"
|
class="player"
|
||||||
:class="{
|
:class="[
|
||||||
|
{
|
||||||
dead: player.isDead,
|
dead: player.isDead,
|
||||||
'no-vote': player.isVoteless,
|
'no-vote': player.isVoteless,
|
||||||
traveler: player.role && player.role.team === 'traveler'
|
you: player.id === session.playerId,
|
||||||
}"
|
'vote-yes': session.votes[index],
|
||||||
|
'vote-lock': voteLocked
|
||||||
|
},
|
||||||
|
player.role.team
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<div class="shroud" @click="toggleStatus()"></div>
|
<div class="shroud" @click="toggleStatus()"></div>
|
||||||
<div class="life" @click="toggleStatus()"></div>
|
<div class="life" @click="toggleStatus()"></div>
|
||||||
|
@ -31,8 +36,24 @@
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Token :role="player.role" @set-role="$emit('set-role')" />
|
<Token
|
||||||
|
:role="player.role"
|
||||||
|
@set-role="$emit('trigger', ['openRoleModal'])"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Overlay icons -->
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="skull"
|
||||||
|
class="vote"
|
||||||
|
title="Voted YES"
|
||||||
|
@click="vote()"
|
||||||
|
/>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="times"
|
||||||
|
class="vote"
|
||||||
|
title="Voted NO"
|
||||||
|
@click="vote()"
|
||||||
|
/>
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
icon="times-circle"
|
icon="times-circle"
|
||||||
class="cancel"
|
class="cancel"
|
||||||
|
@ -51,10 +72,20 @@
|
||||||
@click="movePlayer(player)"
|
@click="movePlayer(player)"
|
||||||
title="Move player to this seat"
|
title="Move player to this seat"
|
||||||
/>
|
/>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="hand-point-right"
|
||||||
|
class="nominate"
|
||||||
|
@click="nominatePlayer(player)"
|
||||||
|
title="Nominate this player"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Claimed seat icon -->
|
||||||
|
<font-awesome-icon icon="chair" v-if="player.id" class="seat" />
|
||||||
|
|
||||||
|
<!-- Ghost vote icon -->
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
icon="vote-yea"
|
icon="vote-yea"
|
||||||
class="vote"
|
class="has-vote"
|
||||||
v-if="player.isDead && !player.isVoteless"
|
v-if="player.isDead && !player.isVoteless"
|
||||||
@click="updatePlayer('isVoteless', true)"
|
@click="updatePlayer('isVoteless', true)"
|
||||||
title="Ghost vote"
|
title="Ghost vote"
|
||||||
|
@ -69,14 +100,15 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="fold">
|
<transition name="fold">
|
||||||
<ul class="menu" v-if="isMenuOpen && !session.isSpectator">
|
<ul class="menu" v-if="isMenuOpen">
|
||||||
|
<template v-if="!session.isSpectator">
|
||||||
<li @click="changeName">
|
<li @click="changeName">
|
||||||
<font-awesome-icon icon="user-edit" />Rename
|
<font-awesome-icon icon="user-edit" />Rename
|
||||||
</li>
|
</li>
|
||||||
<!--<li @click="nomination">
|
<li v-if="!session.nomination" @click="nominatePlayer()">
|
||||||
<font-awesome-icon icon="hand-point-right" />
|
<font-awesome-icon icon="hand-point-right" />
|
||||||
Nomination
|
Nomination
|
||||||
</li>-->
|
</li>
|
||||||
<li @click="movePlayer()">
|
<li @click="movePlayer()">
|
||||||
<font-awesome-icon icon="redo-alt" />
|
<font-awesome-icon icon="redo-alt" />
|
||||||
Move player
|
Move player
|
||||||
|
@ -89,10 +121,18 @@
|
||||||
<font-awesome-icon icon="camera" />
|
<font-awesome-icon icon="camera" />
|
||||||
Screenshot
|
Screenshot
|
||||||
</li>
|
</li>
|
||||||
<li @click="$emit('remove-player')">
|
<li @click="$emit('trigger', ['removePlayer'])">
|
||||||
<font-awesome-icon icon="times-circle" />
|
<font-awesome-icon icon="times-circle" />
|
||||||
Remove
|
Remove
|
||||||
</li>
|
</li>
|
||||||
|
</template>
|
||||||
|
<li @click="claimSeat" v-if="session.isSpectator">
|
||||||
|
<font-awesome-icon icon="chair" />
|
||||||
|
<template v-if="player.id !== session.playerId">
|
||||||
|
Claim seat
|
||||||
|
</template>
|
||||||
|
<template v-else> Vacate seat </template>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
|
@ -116,7 +156,7 @@
|
||||||
{{ reminder.name }}
|
{{ reminder.name }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="reminder add" @click="$emit('add-reminder')">
|
<div class="reminder add" @click="$emit('trigger', ['openReminderModal'])">
|
||||||
<span class="icon"></span>
|
<span class="icon"></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -137,8 +177,20 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState("players", ["players"]),
|
||||||
...mapState(["grimoire", "session"]),
|
...mapState(["grimoire", "session"]),
|
||||||
...mapGetters({ nightOrder: "players/nightOrder" })
|
...mapGetters({ nightOrder: "players/nightOrder" }),
|
||||||
|
index: function() {
|
||||||
|
return this.players.indexOf(this.player);
|
||||||
|
},
|
||||||
|
voteLocked: function() {
|
||||||
|
const session = this.session;
|
||||||
|
const players = this.players.length;
|
||||||
|
if (!session.nomination) return false;
|
||||||
|
const indexAdjusted =
|
||||||
|
(this.index - 1 + players - session.nomination[1]) % players;
|
||||||
|
return indexAdjusted < session.lockedVote - 1;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -193,14 +245,26 @@ export default {
|
||||||
},
|
},
|
||||||
swapPlayer(player) {
|
swapPlayer(player) {
|
||||||
this.isMenuOpen = false;
|
this.isMenuOpen = false;
|
||||||
this.$emit("swap-player", player);
|
this.$emit("trigger", ["swapPlayer", player]);
|
||||||
},
|
},
|
||||||
movePlayer(player) {
|
movePlayer(player) {
|
||||||
this.isMenuOpen = false;
|
this.isMenuOpen = false;
|
||||||
this.$emit("move-player", player);
|
this.$emit("trigger", ["movePlayer", player]);
|
||||||
|
},
|
||||||
|
nominatePlayer(player) {
|
||||||
|
this.isMenuOpen = false;
|
||||||
|
this.$emit("trigger", ["nominatePlayer", player]);
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
this.$emit("cancel");
|
this.$emit("trigger", ["cancel"]);
|
||||||
|
},
|
||||||
|
claimSeat() {
|
||||||
|
this.isMenuOpen = false;
|
||||||
|
this.$emit("trigger", ["claimSeat"]);
|
||||||
|
},
|
||||||
|
vote() {
|
||||||
|
if (this.player.id !== this.session.playerId) return;
|
||||||
|
this.$store.commit("session/vote", [this.index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -367,28 +431,52 @@ export default {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&.swap,
|
&.swap,
|
||||||
&.move,
|
&.move,
|
||||||
|
&.nominate,
|
||||||
|
&.vote,
|
||||||
&.cancel {
|
&.cancel {
|
||||||
top: 9%;
|
top: 9%;
|
||||||
left: 20%;
|
left: 25%;
|
||||||
width: 60%;
|
width: 50%;
|
||||||
height: 60%;
|
height: 60%;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: all 250ms;
|
transition: all 250ms;
|
||||||
transform: scale(0.2);
|
transform: scale(0.2);
|
||||||
&:hover {
|
* {
|
||||||
color: red;
|
stroke-width: 10px;
|
||||||
|
stroke: white;
|
||||||
|
fill: url(#default);
|
||||||
|
}
|
||||||
|
&:hover *,
|
||||||
|
&.fa-skull * {
|
||||||
|
fill: url(#demon);
|
||||||
|
}
|
||||||
|
&.fa-times * {
|
||||||
|
fill: url(#townsfolk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li.from .player > svg.cancel {
|
#townsquare.vote .player.vote-yes > svg.vote.fa-skull {
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#townsquare.vote .player.you.vote-yes > svg.vote.fa-skull,
|
||||||
|
#townsquare.vote .player.vote-lock.vote-yes > svg.vote.fa-skull,
|
||||||
|
#townsquare.vote .player.vote-lock:not(.vote-yes) > svg.vote.fa-times {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.from:not(.nominate) .player > svg.cancel {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.swap:not(.from) .player > svg.swap,
|
li.swap:not(.from) .player > svg.swap,
|
||||||
|
li.nominate .player > svg.nominate,
|
||||||
li.move:not(.from) .player > svg.move {
|
li.move:not(.from) .player > svg.move {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
@ -396,13 +484,12 @@ li.move:not(.from) .player > svg.move {
|
||||||
}
|
}
|
||||||
|
|
||||||
/****** Vote icon ********/
|
/****** Vote icon ********/
|
||||||
.player .vote {
|
.player .has-vote {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
bottom: 45px;
|
bottom: 45px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
filter: drop-shadow(0 0 3px black);
|
filter: drop-shadow(0 0 3px black);
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 250ms;
|
transition: opacity 250ms;
|
||||||
|
|
||||||
#townsquare.public & {
|
#townsquare.public & {
|
||||||
|
@ -411,6 +498,50 @@ li.move:not(.from) .player > svg.move {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin glow($name, $color) {
|
||||||
|
@keyframes #{$name}-glow {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 rgba($color, 1);
|
||||||
|
border-color: $color;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 20px 16px transparent;
|
||||||
|
border-color: $color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.player.you.#{$name} .token {
|
||||||
|
animation: #{$name}-glow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include glow("townsfolk", $townsfolk);
|
||||||
|
@include glow("outsider", $outsider);
|
||||||
|
@include glow("demon", $demon);
|
||||||
|
@include glow("minion", $minion);
|
||||||
|
@include glow("traveler", $traveler);
|
||||||
|
|
||||||
|
.player.you .token {
|
||||||
|
animation: townsfolk-glow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****** Seat icon ********/
|
||||||
|
.player .seat {
|
||||||
|
position: absolute;
|
||||||
|
left: 2px;
|
||||||
|
bottom: 45px;
|
||||||
|
color: #fff;
|
||||||
|
filter: drop-shadow(0 0 3px black);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player.you .seat {
|
||||||
|
color: $townsfolk;
|
||||||
|
}
|
||||||
|
|
||||||
/***** Player name *****/
|
/***** Player name *****/
|
||||||
.player > .name {
|
.player > .name {
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
|
|
|
@ -59,15 +59,13 @@ export default {
|
||||||
).length
|
).length
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
...mapState({
|
...mapState(["edition"]),
|
||||||
edition: state => state.edition,
|
...mapState("players", ["players"])
|
||||||
players: state => state.players.players
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
@import "../vars.scss";
|
@import "../vars.scss";
|
||||||
|
|
||||||
// Editions
|
// Editions
|
||||||
|
@ -94,12 +92,8 @@ export default {
|
||||||
.info {
|
.info {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
width: 20%;
|
width: 20%;
|
||||||
height: 20%;
|
height: 20%;
|
||||||
margin-left: -10%;
|
|
||||||
margin-top: -5%;
|
|
||||||
padding: 50px 0 0;
|
padding: 50px 0 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
class="square"
|
class="square"
|
||||||
v-bind:class="{
|
v-bind:class="{
|
||||||
public: grimoire.isPublic,
|
public: grimoire.isPublic,
|
||||||
spectator: session.isSpectator
|
spectator: session.isSpectator,
|
||||||
|
vote: session.nomination
|
||||||
}"
|
}"
|
||||||
v-bind:style="{ zoom: grimoire.zoom }"
|
v-bind:style="{ zoom: grimoire.zoom }"
|
||||||
>
|
>
|
||||||
|
@ -13,13 +14,8 @@
|
||||||
v-for="(player, index) in players"
|
v-for="(player, index) in players"
|
||||||
:key="index"
|
:key="index"
|
||||||
:player="player"
|
:player="player"
|
||||||
@add-reminder="openReminderModal(index)"
|
|
||||||
@set-role="openRoleModal(index)"
|
|
||||||
@remove-player="removePlayer(index)"
|
|
||||||
@cancel="cancel(index)"
|
|
||||||
@swap-player="swapPlayer(index, $event)"
|
|
||||||
@move-player="movePlayer(index, $event)"
|
|
||||||
@screenshot="$emit('screenshot', $event)"
|
@screenshot="$emit('screenshot', $event)"
|
||||||
|
@trigger="handleTrigger(index, $event)"
|
||||||
v-bind:class="{
|
v-bind:class="{
|
||||||
from: Math.max(swap, move, nominate) === index,
|
from: Math.max(swap, move, nominate) === index,
|
||||||
swap: swap > -1,
|
swap: swap > -1,
|
||||||
|
@ -80,6 +76,19 @@ export default {
|
||||||
const { width, height, x, y } = this.$refs.bluffs.getBoundingClientRect();
|
const { width, height, x, y } = this.$refs.bluffs.getBoundingClientRect();
|
||||||
this.$emit("screenshot", { width, height, x, y });
|
this.$emit("screenshot", { width, height, x, y });
|
||||||
},
|
},
|
||||||
|
handleTrigger(playerIndex, [method, params]) {
|
||||||
|
if (typeof this[method] === "function") {
|
||||||
|
this[method](playerIndex, params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimSeat(playerIndex) {
|
||||||
|
if (!this.session.isSpectator) return;
|
||||||
|
if (this.session.playerId === this.players[playerIndex].id) {
|
||||||
|
this.$store.commit("session/claimSeat", -1);
|
||||||
|
} else {
|
||||||
|
this.$store.commit("session/claimSeat", playerIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
openReminderModal(playerIndex) {
|
openReminderModal(playerIndex) {
|
||||||
this.selectedPlayer = playerIndex;
|
this.selectedPlayer = playerIndex;
|
||||||
this.$store.commit("toggleModal", "reminder");
|
this.$store.commit("toggleModal", "reminder");
|
||||||
|
@ -125,6 +134,20 @@ export default {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
nominatePlayer(from, to) {
|
||||||
|
if (to === undefined && from !== this.nominate) {
|
||||||
|
this.cancel();
|
||||||
|
if (from !== this.nominate) {
|
||||||
|
this.nominate = from;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$store.commit("session/nomination", [
|
||||||
|
this.nominate,
|
||||||
|
this.players.indexOf(to)
|
||||||
|
]);
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
this.move = -1;
|
this.move = -1;
|
||||||
this.swap = -1;
|
this.swap = -1;
|
||||||
|
@ -221,6 +244,10 @@ export default {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***** Demon bluffs *******/
|
/***** Demon bluffs *******/
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
<template>
|
||||||
|
<div id="vote">
|
||||||
|
<div class="arrows">
|
||||||
|
<span class="nominee" :style="nomineeStyle"></span>
|
||||||
|
<span class="nominator" :style="nominatorStyle"></span>
|
||||||
|
</div>
|
||||||
|
<div class="overlay">
|
||||||
|
<em class="blue">{{ nominator.name }}</em> nominated
|
||||||
|
<em>{{ nominee.name }}</em
|
||||||
|
>!
|
||||||
|
<br />
|
||||||
|
<template v-if="nominee.role.team !== 'traveler'">
|
||||||
|
<em class="blue">{{ Math.ceil(alive / 2) }} votes</em> required to
|
||||||
|
<em>execute</em>.
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<em>{{ Math.ceil(players.length / 2) }} votes</em> required to
|
||||||
|
<em>exile</em>.
|
||||||
|
</template>
|
||||||
|
<div class="button-group" v-if="!session.isSpectator">
|
||||||
|
<div class="button" v-if="!session.lockedVote" @click="start">
|
||||||
|
Start Vote
|
||||||
|
</div>
|
||||||
|
<div class="button" v-else @click="stop">
|
||||||
|
Reset Vote
|
||||||
|
</div>
|
||||||
|
<div class="button" @click="finish">Finish</div>
|
||||||
|
</div>
|
||||||
|
<div class="button-group" v-else-if="canVote">
|
||||||
|
<div class="button vote-no" @click="vote(false)">Vote NO</div>
|
||||||
|
<div class="button vote-yes" @click="vote(true)">Vote YES</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!player">
|
||||||
|
Please claim a seat to vote.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters, mapState } from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapState("players", ["players"]),
|
||||||
|
...mapState(["session"]),
|
||||||
|
...mapGetters({ alive: "players/alive" }),
|
||||||
|
nominator: function() {
|
||||||
|
return this.players[this.session.nomination[0]];
|
||||||
|
},
|
||||||
|
nominatorStyle: function() {
|
||||||
|
const players = this.players.length;
|
||||||
|
const nomination = this.session.nomination[0];
|
||||||
|
return {
|
||||||
|
transform: `rotate(${Math.round((nomination / players) * 360)}deg)`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
nominee: function() {
|
||||||
|
return this.players[this.session.nomination[1]];
|
||||||
|
},
|
||||||
|
nomineeStyle: function() {
|
||||||
|
const players = this.players.length;
|
||||||
|
const nomination = this.session.nomination[1];
|
||||||
|
const lock = this.session.lockedVote;
|
||||||
|
const rotation = (360 * (nomination + Math.min(lock, players))) / players;
|
||||||
|
return {
|
||||||
|
transform: `rotate(${Math.round(rotation)}deg)`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
player: function() {
|
||||||
|
return this.players.find(p => p.id === this.session.playerId);
|
||||||
|
},
|
||||||
|
canVote: function() {
|
||||||
|
if (!this.player) return false;
|
||||||
|
if (this.player.isVoteless && this.nominee.role.team !== "traveler")
|
||||||
|
return false;
|
||||||
|
const session = this.session;
|
||||||
|
const players = this.players.length;
|
||||||
|
const index = this.players.indexOf(this.player);
|
||||||
|
const indexAdjusted =
|
||||||
|
(index - 1 + players - session.nomination[1]) % players;
|
||||||
|
return indexAdjusted >= session.lockedVote - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
start() {
|
||||||
|
this.$store.commit("session/lockVote");
|
||||||
|
this.voteTimer = setInterval(() => {
|
||||||
|
this.$store.commit("session/lockVote");
|
||||||
|
if (this.session.lockedVote > this.players.length) {
|
||||||
|
clearInterval(this.voteTimer);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
this.$store.commit("session/lockVote", 0);
|
||||||
|
clearInterval(this.voteTimer);
|
||||||
|
},
|
||||||
|
finish() {
|
||||||
|
this.$store.commit("session/nomination", false);
|
||||||
|
},
|
||||||
|
vote(vote) {
|
||||||
|
if (!this.canVote) return false;
|
||||||
|
const index = this.players.findIndex(p => p.id === this.session.playerId);
|
||||||
|
if (index >= 0 && !!this.session.votes[index] !== vote) {
|
||||||
|
this.$store.commit("session/vote", [index, vote]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../vars.scss";
|
||||||
|
|
||||||
|
#vote {
|
||||||
|
position: absolute;
|
||||||
|
width: 20%;
|
||||||
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: url("../assets/demon-head.png") center center no-repeat;
|
||||||
|
background-size: auto 75%;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 1px 2px #000000, 0 -1px 2px #000000, 1px 0 2px #000000,
|
||||||
|
-1px 0 2px #000000;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: " ";
|
||||||
|
padding-bottom: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
color: $demon;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
&.blue {
|
||||||
|
color: $townsfolk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes arrow-cw {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes arrow-ccw {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrows {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
height: 150%;
|
||||||
|
width: 20%;
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: transform 2.9s ease-in-out;
|
||||||
|
}
|
||||||
|
span:before {
|
||||||
|
content: " ";
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
background-size: auto 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
position: absolute;
|
||||||
|
filter: drop-shadow(0px 0px 3px #000);
|
||||||
|
}
|
||||||
|
.nominator:before {
|
||||||
|
background-image: url("../assets/clock-small.png");
|
||||||
|
animation: arrow-ccw 1s ease-out;
|
||||||
|
}
|
||||||
|
.nominee:before {
|
||||||
|
background-image: url("../assets/clock-big.png");
|
||||||
|
animation: arrow-cw 1s ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.vote-no {
|
||||||
|
background: radial-gradient(
|
||||||
|
at 0 -15%,
|
||||||
|
rgba(255, 255, 255, 0.07) 70%,
|
||||||
|
rgba(255, 255, 255, 0) 71%
|
||||||
|
)
|
||||||
|
0 0/80% 90% no-repeat content-box,
|
||||||
|
linear-gradient(#0031ad, rgba(5, 0, 0, 0.22)) content-box,
|
||||||
|
linear-gradient(#292929, #001142) border-box;
|
||||||
|
box-shadow: inset 0 1px 1px #002c9c, 0 0 10px #000;
|
||||||
|
&:hover {
|
||||||
|
color: #008cf7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.button.vote-yes {
|
||||||
|
background: radial-gradient(
|
||||||
|
at 0 -15%,
|
||||||
|
rgba(255, 255, 255, 0.07) 70%,
|
||||||
|
rgba(255, 255, 255, 0) 71%
|
||||||
|
)
|
||||||
|
0 0/80% 90% no-repeat content-box,
|
||||||
|
linear-gradient(#ad0000, rgba(5, 0, 0, 0.22)) content-box,
|
||||||
|
linear-gradient(#292929, #420000) border-box;
|
||||||
|
box-shadow: inset 0 1px 1px #9c0000, 0 0 10px #000;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -10,6 +10,7 @@ const faIcons = [
|
||||||
"BookOpen",
|
"BookOpen",
|
||||||
"BroadcastTower",
|
"BroadcastTower",
|
||||||
"Camera",
|
"Camera",
|
||||||
|
"Chair",
|
||||||
"CheckSquare",
|
"CheckSquare",
|
||||||
"Cog",
|
"Cog",
|
||||||
"Copy",
|
"Copy",
|
||||||
|
@ -26,8 +27,10 @@ const faIcons = [
|
||||||
"RedoAlt",
|
"RedoAlt",
|
||||||
"SearchMinus",
|
"SearchMinus",
|
||||||
"SearchPlus",
|
"SearchPlus",
|
||||||
|
"Skull",
|
||||||
"Square",
|
"Square",
|
||||||
"TheaterMasks",
|
"TheaterMasks",
|
||||||
|
"Times",
|
||||||
"TimesCircle",
|
"TimesCircle",
|
||||||
"TrashAlt",
|
"TrashAlt",
|
||||||
"Undo",
|
"Undo",
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import Vuex from "vuex";
|
import Vuex from "vuex";
|
||||||
import persistence from "./persistence";
|
import persistence from "./persistence";
|
||||||
import session from "./session";
|
import socket from "./socket";
|
||||||
import players from "./modules/players";
|
import players from "./modules/players";
|
||||||
|
import session from "./modules/session";
|
||||||
import editionJSON from "../editions.json";
|
import editionJSON from "../editions.json";
|
||||||
import rolesJSON from "../roles.json";
|
import rolesJSON from "../roles.json";
|
||||||
|
|
||||||
|
@ -23,7 +24,8 @@ const getRolesByEdition = (edition = "tb") => {
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
players
|
players,
|
||||||
|
session
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
grimoire: {
|
grimoire: {
|
||||||
|
@ -36,12 +38,6 @@ export default new Vuex.Store({
|
||||||
background: "",
|
background: "",
|
||||||
bluffs: []
|
bluffs: []
|
||||||
},
|
},
|
||||||
session: {
|
|
||||||
sessionId: "",
|
|
||||||
isSpectator: false,
|
|
||||||
playerCount: 0,
|
|
||||||
playerId: ""
|
|
||||||
},
|
|
||||||
modals: {
|
modals: {
|
||||||
reference: false,
|
reference: false,
|
||||||
edition: false,
|
edition: false,
|
||||||
|
@ -75,18 +71,6 @@ export default new Vuex.Store({
|
||||||
setBackground({ grimoire }, background) {
|
setBackground({ grimoire }, background) {
|
||||||
grimoire.background = background;
|
grimoire.background = background;
|
||||||
},
|
},
|
||||||
setSessionId({ session }, sessionId) {
|
|
||||||
session.sessionId = sessionId;
|
|
||||||
},
|
|
||||||
setPlayerId({ session }, playerId) {
|
|
||||||
session.playerId = playerId;
|
|
||||||
},
|
|
||||||
setSpectator({ session }, spectator) {
|
|
||||||
session.isSpectator = spectator;
|
|
||||||
},
|
|
||||||
setPlayerCount({ session }, playerCount) {
|
|
||||||
session.playerCount = playerCount;
|
|
||||||
},
|
|
||||||
setBluff({ grimoire }, { index, role } = {}) {
|
setBluff({ grimoire }, { index, role } = {}) {
|
||||||
if (index !== undefined) {
|
if (index !== undefined) {
|
||||||
grimoire.bluffs.splice(index, 1, role);
|
grimoire.bluffs.splice(index, 1, role);
|
||||||
|
@ -128,5 +112,5 @@ export default new Vuex.Store({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [persistence, session]
|
plugins: [persistence, socket]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
const NEWPLAYER = {
|
const NEWPLAYER = {
|
||||||
|
name: "",
|
||||||
|
id: "",
|
||||||
role: {},
|
role: {},
|
||||||
reminders: [],
|
reminders: [],
|
||||||
isVoteless: false,
|
isVoteless: false,
|
||||||
|
@ -10,6 +12,9 @@ const state = () => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
|
alive({ players }) {
|
||||||
|
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"
|
||||||
|
@ -48,11 +53,23 @@ const actions = {
|
||||||
.map(a => a[1]);
|
.map(a => a[1]);
|
||||||
commit("set", players);
|
commit("set", players);
|
||||||
},
|
},
|
||||||
clearRoles({ state, commit }) {
|
clearRoles({ state, commit, rootState }) {
|
||||||
const players = state.players.map(({ name }) => ({
|
let players;
|
||||||
|
if (rootState.session.isSpectator) {
|
||||||
|
players = state.players.map(player => {
|
||||||
|
if (player.role.team !== "traveler") {
|
||||||
|
player.role = {};
|
||||||
|
}
|
||||||
|
player.reminders = [];
|
||||||
|
return player;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
players = state.players.map(({ name, id }) => ({
|
||||||
|
...NEWPLAYER,
|
||||||
name,
|
name,
|
||||||
...NEWPLAYER
|
id
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
commit("set", players);
|
commit("set", players);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -72,8 +89,8 @@ const mutations = {
|
||||||
},
|
},
|
||||||
add(state, name) {
|
add(state, name) {
|
||||||
state.players.push({
|
state.players.push({
|
||||||
name,
|
...NEWPLAYER,
|
||||||
...NEWPLAYER
|
name
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
remove(state, index) {
|
remove(state, index) {
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
const state = () => ({
|
||||||
|
sessionId: "",
|
||||||
|
isSpectator: false,
|
||||||
|
playerCount: 0,
|
||||||
|
playerId: "",
|
||||||
|
claimedSeat: -1,
|
||||||
|
nomination: false,
|
||||||
|
votes: [],
|
||||||
|
lockedVote: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const getters = {};
|
||||||
|
|
||||||
|
const actions = {};
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
setSessionId(state, sessionId) {
|
||||||
|
state.sessionId = sessionId;
|
||||||
|
},
|
||||||
|
setPlayerId(state, playerId) {
|
||||||
|
state.playerId = playerId;
|
||||||
|
},
|
||||||
|
setSpectator(state, spectator) {
|
||||||
|
state.isSpectator = spectator;
|
||||||
|
},
|
||||||
|
setPlayerCount(state, playerCount) {
|
||||||
|
state.playerCount = playerCount;
|
||||||
|
},
|
||||||
|
claimSeat(state, claimedSeat) {
|
||||||
|
state.claimedSeat = claimedSeat;
|
||||||
|
},
|
||||||
|
nomination(state, nomination) {
|
||||||
|
state.nomination = nomination;
|
||||||
|
state.votes = [];
|
||||||
|
state.lockedVote = 0;
|
||||||
|
},
|
||||||
|
vote(state, [index, vote]) {
|
||||||
|
if (!state.nomination) return;
|
||||||
|
state.votes = [...state.votes];
|
||||||
|
state.votes[index] = vote === undefined ? !state.votes[index] : vote;
|
||||||
|
},
|
||||||
|
lockVote(state, lock) {
|
||||||
|
state.lockedVote = lock !== undefined ? lock : state.lockedVote + 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
};
|
|
@ -32,12 +32,12 @@ module.exports = store => {
|
||||||
}
|
}
|
||||||
/**** Session related data *****/
|
/**** Session related data *****/
|
||||||
if (localStorage.getItem("playerId")) {
|
if (localStorage.getItem("playerId")) {
|
||||||
store.commit("setPlayerId", localStorage.getItem("playerId"));
|
store.commit("session/setPlayerId", localStorage.getItem("playerId"));
|
||||||
}
|
}
|
||||||
if (localStorage.getItem("session")) {
|
if (localStorage.getItem("session")) {
|
||||||
const [spectator, sessionId] = JSON.parse(localStorage.getItem("session"));
|
const [spectator, sessionId] = JSON.parse(localStorage.getItem("session"));
|
||||||
store.commit("setSpectator", spectator);
|
store.commit("session/setSpectator", spectator);
|
||||||
store.commit("setSessionId", sessionId);
|
store.commit("session/setSessionId", sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen to mutations
|
// listen to mutations
|
||||||
|
@ -99,7 +99,7 @@ module.exports = store => {
|
||||||
localStorage.removeItem("players");
|
localStorage.removeItem("players");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "setSessionId":
|
case "session/setSessionId":
|
||||||
if (payload) {
|
if (payload) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"session",
|
"session",
|
||||||
|
@ -109,11 +109,11 @@ module.exports = store => {
|
||||||
localStorage.removeItem("session");
|
localStorage.removeItem("session");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "setPlayerId":
|
case "session/setPlayerId":
|
||||||
if (payload) {
|
if (payload) {
|
||||||
localStorage.setItem("playerId", payload);
|
localStorage.setItem("playerId", payload);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("setPlayerId");
|
localStorage.removeItem("playerId");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class LiveSession {
|
||||||
this._socket.onopen = this._onOpen.bind(this);
|
this._socket.onopen = this._onOpen.bind(this);
|
||||||
this._socket.onclose = () => {
|
this._socket.onclose = () => {
|
||||||
this._socket = null;
|
this._socket = null;
|
||||||
this._store.commit("setSessionId", "");
|
this._store.commit("session/setSessionId", "");
|
||||||
clearInterval(this._pingTimer);
|
clearInterval(this._pingTimer);
|
||||||
this._pingTimer = null;
|
this._pingTimer = null;
|
||||||
};
|
};
|
||||||
|
@ -93,9 +93,21 @@ class LiveSession {
|
||||||
case "player":
|
case "player":
|
||||||
this._updatePlayer(params);
|
this._updatePlayer(params);
|
||||||
break;
|
break;
|
||||||
|
case "claim":
|
||||||
|
this._updateSeat(params);
|
||||||
|
break;
|
||||||
case "ping":
|
case "ping":
|
||||||
this._handlePing(params);
|
this._handlePing(params);
|
||||||
break;
|
break;
|
||||||
|
case "nomination":
|
||||||
|
this._store.commit("session/nomination", params);
|
||||||
|
break;
|
||||||
|
case "vote":
|
||||||
|
this._store.commit("session/vote", params);
|
||||||
|
break;
|
||||||
|
case "lock":
|
||||||
|
this._store.commit("session/lockVote", params);
|
||||||
|
break;
|
||||||
case "bye":
|
case "bye":
|
||||||
this._handleBye(params);
|
this._handleBye(params);
|
||||||
break;
|
break;
|
||||||
|
@ -110,13 +122,13 @@ class LiveSession {
|
||||||
connect(channel) {
|
connect(channel) {
|
||||||
if (!this._store.state.session.playerId) {
|
if (!this._store.state.session.playerId) {
|
||||||
this._store.commit(
|
this._store.commit(
|
||||||
"setPlayerId",
|
"session/setPlayerId",
|
||||||
Math.random()
|
Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
.substr(2)
|
.substr(2)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._store.commit("setPlayerCount", 0);
|
this._store.commit("session/setPlayerCount", 0);
|
||||||
this._isSpectator = this._store.state.session.isSpectator;
|
this._isSpectator = this._store.state.session.isSpectator;
|
||||||
this._open(channel);
|
this._open(channel);
|
||||||
}
|
}
|
||||||
|
@ -125,7 +137,7 @@ class LiveSession {
|
||||||
* Close the current session, if any.
|
* Close the current session, if any.
|
||||||
*/
|
*/
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this._store.commit("setPlayerCount", 0);
|
this._store.commit("session/setPlayerCount", 0);
|
||||||
if (this._socket) {
|
if (this._socket) {
|
||||||
this._send("bye", this._store.state.session.playerId);
|
this._send("bye", this._store.state.session.playerId);
|
||||||
this._socket.close();
|
this._socket.close();
|
||||||
|
@ -140,6 +152,7 @@ class LiveSession {
|
||||||
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,
|
||||||
isDead: player.isDead,
|
isDead: player.isDead,
|
||||||
isVoteless: player.isVoteless,
|
isVoteless: player.isVoteless,
|
||||||
...(player.role && player.role.team === "traveler"
|
...(player.role && player.role.team === "traveler"
|
||||||
|
@ -154,7 +167,8 @@ class LiveSession {
|
||||||
}));
|
}));
|
||||||
this._send("gs", {
|
this._send("gs", {
|
||||||
gamestate: this._gamestate,
|
gamestate: this._gamestate,
|
||||||
edition: this._store.state.edition
|
edition: this._store.state.edition,
|
||||||
|
nomination: this._store.state.session.nomination
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,9 +178,10 @@ class LiveSession {
|
||||||
* @param edition
|
* @param edition
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_updateGamestate({ gamestate, edition }) {
|
_updateGamestate({ gamestate, edition, nomination }) {
|
||||||
if (!this._isSpectator) return;
|
if (!this._isSpectator) return;
|
||||||
this._store.commit("setEdition", edition);
|
this._store.commit("setEdition", edition);
|
||||||
|
this._store.commit("session/nomination", nomination);
|
||||||
const players = this._store.state.players.players;
|
const players = this._store.state.players.players;
|
||||||
// adjust number of players
|
// adjust number of players
|
||||||
if (players.length < gamestate.length) {
|
if (players.length < gamestate.length) {
|
||||||
|
@ -181,28 +196,15 @@ class LiveSession {
|
||||||
// update status for each player
|
// update status for each player
|
||||||
gamestate.forEach((state, x) => {
|
gamestate.forEach((state, x) => {
|
||||||
const player = players[x];
|
const player = players[x];
|
||||||
const { name, isDead, isVoteless, role } = state;
|
const { role } = state;
|
||||||
if (player.name !== name) {
|
// update relevant properties
|
||||||
this._store.commit("players/update", {
|
["name", "id", "isDead", "isVoteless"].forEach(property => {
|
||||||
player,
|
const value = state[property];
|
||||||
property: "name",
|
if (player[property] !== value) {
|
||||||
value: name
|
this._store.commit("players/update", { player, property, value });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (player.isDead !== isDead) {
|
|
||||||
this._store.commit("players/update", {
|
|
||||||
player,
|
|
||||||
property: "isDead",
|
|
||||||
value: isDead
|
|
||||||
});
|
});
|
||||||
}
|
// roles are special, because of travelers
|
||||||
if (player.isVoteless !== isVoteless) {
|
|
||||||
this._store.commit("players/update", {
|
|
||||||
player,
|
|
||||||
property: "isVoteless",
|
|
||||||
value: isVoteless
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (role && player.role.id !== role.id) {
|
if (role && player.role.id !== role.id) {
|
||||||
this._store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
|
@ -298,7 +300,20 @@ class LiveSession {
|
||||||
delete this._players[player];
|
delete this._players[player];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._store.commit("setPlayerCount", Object.keys(this._players).length);
|
// remove claimed seats from players that are no longer connected
|
||||||
|
this._store.state.players.players.forEach(player => {
|
||||||
|
if (!this._isSpectator && player.id && !this._players[player.id]) {
|
||||||
|
this._store.commit("players/update", {
|
||||||
|
player,
|
||||||
|
property: "id",
|
||||||
|
value: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._store.commit(
|
||||||
|
"session/setPlayerCount",
|
||||||
|
Object.keys(this._players).length
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -308,7 +323,82 @@ class LiveSession {
|
||||||
*/
|
*/
|
||||||
_handleBye(playerId) {
|
_handleBye(playerId) {
|
||||||
delete this._players[playerId];
|
delete this._players[playerId];
|
||||||
this._store.commit("setPlayerCount", Object.keys(this._players).length);
|
this._store.commit(
|
||||||
|
"session/setPlayerCount",
|
||||||
|
Object.keys(this._players).length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claim a seat, needs to be confirmed by the Storyteller.
|
||||||
|
* @param seat either -1 or the index of the seat claimed
|
||||||
|
*/
|
||||||
|
claimSeat(seat) {
|
||||||
|
if (!this._isSpectator) return;
|
||||||
|
if (this._store.state.players.players.length > seat) {
|
||||||
|
this._send("claim", [seat, this._store.state.session.playerId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a player id associated with that seat.
|
||||||
|
* @param index seat index or -1
|
||||||
|
* @param value playerId to add / remove
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_updateSeat([index, value]) {
|
||||||
|
if (this._isSpectator) return;
|
||||||
|
const property = "id";
|
||||||
|
const players = this._store.state.players.players;
|
||||||
|
// remove previous seat
|
||||||
|
const oldIndex = players.findIndex(({ id }) => id === value);
|
||||||
|
if (oldIndex >= 0 && oldIndex !== index) {
|
||||||
|
this._store.commit("players/update", {
|
||||||
|
player: players[oldIndex],
|
||||||
|
property,
|
||||||
|
value: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// add playerId to new seat
|
||||||
|
if (index >= 0) {
|
||||||
|
const player = players[index];
|
||||||
|
if (!player) return;
|
||||||
|
this._store.commit("players/update", { player, property, value });
|
||||||
|
}
|
||||||
|
// update player session list as if this was a ping
|
||||||
|
this._handlePing([true, value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player nomination. ST only
|
||||||
|
* @param nomination [nominator, nominee]
|
||||||
|
*/
|
||||||
|
nomination(nomination) {
|
||||||
|
if (this._isSpectator) return;
|
||||||
|
const players = this._store.state.players.players;
|
||||||
|
if (
|
||||||
|
!nomination ||
|
||||||
|
(players.length > nomination[0] && players.length > nomination[1])
|
||||||
|
) {
|
||||||
|
this._send("nomination", nomination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a vote. Player only
|
||||||
|
* @param index
|
||||||
|
*/
|
||||||
|
vote([index]) {
|
||||||
|
if (!this._isSpectator) return;
|
||||||
|
this._send("vote", [index, this._store.state.session.votes[index]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock a vote. ST only
|
||||||
|
*/
|
||||||
|
lockVote() {
|
||||||
|
if (this._isSpectator) return;
|
||||||
|
this._send("lock", this._store.state.session.lockedVote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +409,7 @@ module.exports = store => {
|
||||||
// listen to mutations
|
// listen to mutations
|
||||||
store.subscribe(({ type, payload }) => {
|
store.subscribe(({ type, payload }) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "setSessionId":
|
case "session/setSessionId":
|
||||||
if (payload) {
|
if (payload) {
|
||||||
session.connect(payload);
|
session.connect(payload);
|
||||||
} else {
|
} else {
|
||||||
|
@ -327,6 +417,18 @@ module.exports = store => {
|
||||||
session.disconnect();
|
session.disconnect();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "session/claimSeat":
|
||||||
|
session.claimSeat(payload);
|
||||||
|
break;
|
||||||
|
case "session/nomination":
|
||||||
|
session.nomination(payload);
|
||||||
|
break;
|
||||||
|
case "session/vote":
|
||||||
|
session.vote(payload);
|
||||||
|
break;
|
||||||
|
case "session/lockVote":
|
||||||
|
session.lockVote();
|
||||||
|
break;
|
||||||
case "players/set":
|
case "players/set":
|
||||||
case "players/swap":
|
case "players/swap":
|
||||||
case "players/move":
|
case "players/move":
|
||||||
|
@ -345,7 +447,7 @@ module.exports = store => {
|
||||||
// check for session Id in hash
|
// check for session Id in hash
|
||||||
const [command, param] = window.location.hash.substr(1).split("/");
|
const [command, param] = window.location.hash.substr(1).split("/");
|
||||||
if (command === "play") {
|
if (command === "play") {
|
||||||
store.commit("setSpectator", true);
|
store.commit("session/setSpectator", true);
|
||||||
store.commit("setSessionId", param);
|
store.commit("session/setSessionId", param);
|
||||||
}
|
}
|
||||||
};
|
};
|
Loading…
Reference in New Issue