mirror of
https://github.com/bra1n/townsquare.git
synced 2025-04-04 14:14:38 +00:00
storyteller tools
This commit is contained in:
parent
77c70c5538
commit
d59cef24d3
8 changed files with 259 additions and 30 deletions
64
src/components/Countdown.vue
Normal file
64
src/components/Countdown.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div :data-text="timerName" :style="style" class="countdown"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
timerName: String,
|
||||
timerDuration: Number
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return `--timer: ${this.timerDuration}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
div {
|
||||
width: 100%;
|
||||
height: 1.6em;
|
||||
border: 2px solid black;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
div::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(255, 0, 0, 0.6);
|
||||
z-index: 1;
|
||||
animation: forwards countdown calc(var(--timer) * 1s) linear;
|
||||
}
|
||||
|
||||
div::after{
|
||||
position:absolute;
|
||||
inset: 0;
|
||||
text-align:center;
|
||||
content: attr(data-text);
|
||||
z-index: 2;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0) 5%,
|
||||
rgba(255, 255, 255, 0.5) 15%,
|
||||
rgba(255, 255, 255, 0) 35%,
|
||||
rgba(0, 0, 0, 0) 60%,
|
||||
rgba(0, 0, 0, 0.7) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@keyframes countdown {
|
||||
0% {
|
||||
width: 100%;
|
||||
}
|
||||
100% {
|
||||
width:0%;
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -63,20 +63,27 @@
|
|||
:icon="teams.traveler > 1 ? 'user-friends' : 'user'"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="grimoire.isNight">
|
||||
Night phase
|
||||
<font-awesome-icon :icon="['fas', 'cloud-moon']" />
|
||||
</span>
|
||||
<span v-if="grimoire.isRinging">
|
||||
<audio
|
||||
:autoplay="!grimoire.isMuted"
|
||||
src="../assets/sounds/countdown.mp3"
|
||||
:muted="grimoire.isMuted"
|
||||
></audio>
|
||||
<font-awesome-icon :icon="['fas', 'music']" />
|
||||
<font-awesome-icon :icon="['fas', 'bell']" />
|
||||
<font-awesome-icon :icon="['fas', 'music']" />
|
||||
</span>
|
||||
</li>
|
||||
<li v-if="grimoire.isNight">
|
||||
<font-awesome-icon :icon="['fas', 'cloud-moon']" />
|
||||
{{ locale.towninfo.nightPhase }}
|
||||
</li>
|
||||
<li v-if="grimoire.isRinging">
|
||||
<audio
|
||||
:autoplay="!grimoire.isMuted"
|
||||
src="../assets/sounds/countdown.mp3"
|
||||
:muted="grimoire.isMuted"
|
||||
></audio>
|
||||
<font-awesome-icon :icon="['fas', 'music']" />
|
||||
<font-awesome-icon :icon="['fas', 'bell']" />
|
||||
<font-awesome-icon :icon="['fas', 'music']" />
|
||||
</li>
|
||||
<li>
|
||||
<Countdown
|
||||
v-if="grimoire.timer.duration"
|
||||
:timerName="grimoire.timer.name"
|
||||
:timerDuration="grimoire.timer.duration"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
@ -84,8 +91,12 @@
|
|||
<script>
|
||||
import gameJSON from "./../game";
|
||||
import { mapState } from "vuex";
|
||||
import Countdown from "./Countdown";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Countdown
|
||||
},
|
||||
computed: {
|
||||
teams: function() {
|
||||
const { players } = this.$store.state.players;
|
||||
|
@ -102,7 +113,10 @@ export default {
|
|||
).length
|
||||
};
|
||||
},
|
||||
...mapState(["edition", "grimoire"]),
|
||||
countdownStyle: function() {
|
||||
return `--timer: ${this.$store.state.grimoire.timer.duration}`;
|
||||
},
|
||||
...mapState(["edition", "grimoire", "locale"]),
|
||||
...mapState("players", ["players"])
|
||||
}
|
||||
};
|
||||
|
@ -184,7 +198,7 @@ export default {
|
|||
background-repeat: no-repeat;
|
||||
background-size: 100% auto;
|
||||
position: absolute;
|
||||
top: -25%;
|
||||
top: -50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -46,6 +46,52 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="storytelling"
|
||||
v-if="!session.isSpectator"
|
||||
ref="storytelling"
|
||||
:class="{ closed: !isTimeControlsOpen }"
|
||||
>
|
||||
<h3>
|
||||
<span>{{ locale.townsquare.storytellerTools }}</span>
|
||||
<font-awesome-icon
|
||||
icon="times-circle"
|
||||
@click.stop="toggleTimeControls"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
icon="plus-circle"
|
||||
@click.stop="toggleTimeControls"
|
||||
/>
|
||||
</h3>
|
||||
<div class="button-group">
|
||||
<div @click="setTimer()" class="button">🕑 {{ timerDuration }} min</div>
|
||||
<div @click="renameTimer()" class="button">🗏 {{ timerName }}</div>
|
||||
<div
|
||||
class="button demon"
|
||||
@click="stopTimer()"
|
||||
:class="{ disabled: !timerOn }"
|
||||
>
|
||||
■
|
||||
</div>
|
||||
<div
|
||||
class="button townfolk"
|
||||
@click="startTimer()"
|
||||
:class="{ disabled: timerOn }"
|
||||
>
|
||||
⏵
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div @click="toggleNight()" class="button" :class="{disabled: grimoire.isNight}">☀</div>
|
||||
<div @click="toggleNight()" class="button" :class="{disabled: !grimoire.isNight}">☽</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div @click="toggleRinging()" class="button">
|
||||
<font-awesome-icon :icon="['fas', 'bell']" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fabled" :class="{ closed: !isFabledOpen }" v-if="fabled.length">
|
||||
<h3>
|
||||
<span>{{ locale.townsquare.fabled }}</span>
|
||||
|
@ -113,7 +159,12 @@ export default {
|
|||
move: -1,
|
||||
nominate: -1,
|
||||
isBluffsOpen: true,
|
||||
isFabledOpen: true
|
||||
isFabledOpen: true,
|
||||
isTimeControlsOpen: false,
|
||||
timerName: "Timer",
|
||||
timerDuration: 1,
|
||||
timerOn: false,
|
||||
timerEnder: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -123,10 +174,23 @@ export default {
|
|||
toggleFabled() {
|
||||
this.isFabledOpen = !this.isFabledOpen;
|
||||
},
|
||||
toggleTimeControls() {
|
||||
this.isTimeControlsOpen = !this.isTimeControlsOpen;
|
||||
},
|
||||
removeFabled(index) {
|
||||
if (this.session.isSpectator) return;
|
||||
this.$store.commit("players/setFabled", { index });
|
||||
},
|
||||
toggleNight() {
|
||||
this.$store.commit("toggleNight");
|
||||
if (this.grimoire.isNight) {
|
||||
this.$store.commit("session/setMarkedPlayer", -1);
|
||||
}
|
||||
},
|
||||
toggleRinging() {
|
||||
this.$store.commit("toggleRinging", true);
|
||||
setTimeout(this.$store.commit, 4000, "toggleRinging", false);
|
||||
},
|
||||
handleTrigger(playerIndex, [method, params]) {
|
||||
if (typeof this[method] === "function") {
|
||||
this[method](playerIndex, params);
|
||||
|
@ -251,6 +315,33 @@ export default {
|
|||
this.move = -1;
|
||||
this.swap = -1;
|
||||
this.nominate = -1;
|
||||
},
|
||||
renameTimer() {
|
||||
let newName = prompt("Timer Name", "");
|
||||
if (newName === "") {
|
||||
return;
|
||||
}
|
||||
this.timerName = newName.trim();
|
||||
},
|
||||
setTimer() {
|
||||
let newDuration = prompt("Timer Duration in minutes");
|
||||
if (isNaN(newDuration)) {
|
||||
return alert("Incorrect number");
|
||||
}
|
||||
if (newDuration > 0) {
|
||||
this.timerDuration = newDuration;
|
||||
}
|
||||
},
|
||||
startTimer() {
|
||||
let timer = { name: this.timerName, duration: this.timerDuration * 60 };
|
||||
this.$store.commit("setTimer", timer);
|
||||
this.timerOn = true;
|
||||
this.timerEnder = setTimeout(this.stopTimer, timer.duration * 1000);
|
||||
},
|
||||
stopTimer() {
|
||||
this.$store.commit("setTimer", {});
|
||||
this.timerOn = false;
|
||||
clearTimeout(this.timerEnder);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -396,15 +487,22 @@ export default {
|
|||
|
||||
/***** Demon bluffs / Fabled *******/
|
||||
#townsquare > .bluffs,
|
||||
#townsquare > .fabled {
|
||||
#townsquare > .fabled,
|
||||
#townsquare > .storytelling {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
&.bluffs {
|
||||
bottom: 10px;
|
||||
}
|
||||
&.fabled {
|
||||
top: 10px;
|
||||
}
|
||||
left: 10px;
|
||||
&.storytelling{
|
||||
bottom: 20vmin;
|
||||
left: auto;
|
||||
right: 10px;
|
||||
width: min-content;
|
||||
}
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 10px;
|
||||
border: 3px solid black;
|
||||
|
@ -412,7 +510,7 @@ export default {
|
|||
transform-origin: bottom left;
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
transition: all 200ms ease-in-out;
|
||||
transition: all 250ms ease-in-out;
|
||||
z-index: 50;
|
||||
|
||||
> svg {
|
||||
|
@ -465,6 +563,15 @@ export default {
|
|||
transition: all 250ms;
|
||||
}
|
||||
}
|
||||
.button-group {
|
||||
transition: all 250ms;
|
||||
input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
&.closed {
|
||||
svg.fa-times-circle {
|
||||
display: none;
|
||||
|
@ -473,6 +580,7 @@ export default {
|
|||
display: block;
|
||||
}
|
||||
ul li {
|
||||
scale: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
.night-order {
|
||||
|
@ -482,6 +590,12 @@ export default {
|
|||
border-width: 0;
|
||||
}
|
||||
}
|
||||
.button-group,
|
||||
.button-group * {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
scale: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
<em v-if="nominee.role.team !== 'traveler'">
|
||||
({{ locale.vote.majorityIs }} {{ Math.ceil(alive / 2) }})
|
||||
</em>
|
||||
<em v-else>({{ locale.vote.majorityIs }} {{ Math.ceil(players.length / 2) }})</em>
|
||||
<em v-else>
|
||||
({{ locale.vote.majorityIs }} {{ Math.ceil(players.length / 2) }})
|
||||
</em>
|
||||
|
||||
<template v-if="!session.isSpectator">
|
||||
<div v-if="!session.isVoteInProgress && session.lockedVote < 1">
|
||||
|
@ -94,6 +96,11 @@
|
|||
<div v-else-if="!player">
|
||||
{{ locale.vote.seatToVote }}
|
||||
</div>
|
||||
<Countdown
|
||||
v-if="grimoire.timer.duration"
|
||||
:timerName="grimoire.timer.name"
|
||||
:timerDuration="grimoire.timer.duration"
|
||||
/>
|
||||
</div>
|
||||
<transition name="blur">
|
||||
<div
|
||||
|
@ -116,8 +123,12 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters, mapState } from "vuex";
|
||||
import Countdown from "./Countdown";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Countdown
|
||||
},
|
||||
computed: {
|
||||
...mapState("players", ["players"]),
|
||||
...mapState(["session", "grimoire", "locale"]),
|
||||
|
|
|
@ -111,7 +111,11 @@ export default new Vuex.Store({
|
|||
isMuted: false,
|
||||
isImageOptIn: false,
|
||||
zoom: 0,
|
||||
background: ""
|
||||
background: "",
|
||||
timer: {
|
||||
name: "",
|
||||
duration: 0
|
||||
}
|
||||
},
|
||||
modals: {
|
||||
edition: false,
|
||||
|
@ -179,6 +183,9 @@ export default new Vuex.Store({
|
|||
toggleRinging: toggle("isRinging"),
|
||||
toggleGrimoire: toggle("isPublic"),
|
||||
toggleImageOptIn: toggle("isImageOptIn"),
|
||||
setTimer(state, timer) {
|
||||
state.grimoire.timer = timer;
|
||||
},
|
||||
toggleModal({ modals }, name) {
|
||||
if (name) {
|
||||
modals[name] = !modals[name];
|
||||
|
|
|
@ -91,10 +91,12 @@
|
|||
"townsquare":{
|
||||
"others": "Other characters",
|
||||
"bluffs": "Demon bluffs",
|
||||
"fabled": "Fabled"
|
||||
"fabled": "Fabled",
|
||||
"storytellerTools": "Storytelling"
|
||||
},
|
||||
"towninfo":{
|
||||
"addPlayers":"Please add more players!"
|
||||
"addPlayers":"Please add more players!",
|
||||
"nightPhase":"Night Phase"
|
||||
},
|
||||
"player":{
|
||||
"handUp": "Hand UP",
|
||||
|
|
|
@ -91,10 +91,12 @@
|
|||
"townsquare":{
|
||||
"others": "Autres Rôles",
|
||||
"bluffs": "Bluffs de Démon",
|
||||
"fabled": "Fabuleux"
|
||||
"fabled": "Fabuleux",
|
||||
"storytellerTools": "Narration"
|
||||
},
|
||||
"towninfo":{
|
||||
"addPlayers":"Appuyez sur [A] pour ajouter plus de joueurs !"
|
||||
"addPlayers": "Appuyez sur [A] pour ajouter plus de joueurs !",
|
||||
"nightPhase": "C'est la nuit"
|
||||
},
|
||||
"player":{
|
||||
"handUp": "Main levée",
|
||||
|
@ -118,7 +120,7 @@
|
|||
"intro":{
|
||||
"header": "Bienvenue sur le Centre-ville Virtuel (non-officiel) pour Blood on the Clocktower! Veuillez ajouter des Joueurs via le",
|
||||
"menu": "Menu",
|
||||
"body": "en haut à droite ou en appuyant sur [A] pour commencer. Vous pouvez aussi rejoindre une ssession en appuyant sur [J].",
|
||||
"body": "en haut à droite ou en appuyant sur [A] pour commencer. Vous pouvez aussi rejoindre une session en appuyant sur [J].",
|
||||
"footerStart": "Ce programme est libre et ses sources peuvent être trouvées sur",
|
||||
"footerEnd": ". Ce site n'est pas affilié à The Pandemonium Institute. \"Blood on the Clocktower\" est une marque déposée de Steven Medway & The Pandemonium Institute."
|
||||
},
|
||||
|
|
|
@ -179,9 +179,10 @@ class LiveSession {
|
|||
case "isRinging":
|
||||
if (!this._isSpectator) return;
|
||||
this._store.commit("toggleRinging", params);
|
||||
// if (params){
|
||||
// setTimeout(this._store.commit, 4000, "toggleRinging", false);
|
||||
// }
|
||||
break;
|
||||
case "setTimer":
|
||||
if (!this._isSpectator) return;
|
||||
this._store.commit("setTimer", params);
|
||||
break;
|
||||
case "isVoteHistoryAllowed":
|
||||
if (!this._isSpectator) return;
|
||||
|
@ -285,6 +286,7 @@ class LiveSession {
|
|||
gamestate: this._gamestate,
|
||||
isNight: grimoire.isNight,
|
||||
isRinging: grimoire.isRinging,
|
||||
timer: grimoire.timer,
|
||||
isVoteHistoryAllowed: session.isVoteHistoryAllowed,
|
||||
nomination: session.nomination,
|
||||
votingSpeed: session.votingSpeed,
|
||||
|
@ -310,6 +312,7 @@ class LiveSession {
|
|||
isNight,
|
||||
isVoteHistoryAllowed,
|
||||
isRinging,
|
||||
timer,
|
||||
nomination,
|
||||
votingSpeed,
|
||||
votes,
|
||||
|
@ -361,6 +364,7 @@ class LiveSession {
|
|||
}
|
||||
});
|
||||
if (!isLightweight) {
|
||||
this._store.commit("timer", timer);
|
||||
this._store.commit("toggleRinging", !!isRinging);
|
||||
this._store.commit("toggleNight", !!isNight);
|
||||
this._store.commit("session/setVoteHistoryAllowed", isVoteHistoryAllowed);
|
||||
|
@ -721,6 +725,14 @@ class LiveSession {
|
|||
this._send("isRinging", this._store.state.grimoire.isRinging);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start or stop a timer
|
||||
*/
|
||||
setTimer() {
|
||||
if (this._isSpectator) return;
|
||||
this._send("setTimer", this._store.state.grimoire.timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the isVoteHistoryAllowed state. ST only
|
||||
*/
|
||||
|
@ -905,6 +917,9 @@ export default store => {
|
|||
case "toggleRinging":
|
||||
session.setIsRinging();
|
||||
break;
|
||||
case "setTimer":
|
||||
session.setTimer();
|
||||
break;
|
||||
case "setEdition":
|
||||
session.sendEdition();
|
||||
break;
|
||||
|
|
Loading…
Add table
Reference in a new issue