1
0
Fork 0
mirror of https://github.com/bra1n/townsquare.git synced 2025-04-04 22:24:36 +00:00

Merge branch 'develop'

This commit is contained in:
Yizhe Zhang 2024-12-03 09:22:44 +01:00
commit 6312dd4a45
11 changed files with 169 additions and 138 deletions

Binary file not shown.

After

(image error) Size: 23 KiB

Binary file not shown.

After

(image error) Size: 24 KiB

View file

@ -87,7 +87,7 @@
Background image Background image
<em><font-awesome-icon icon="image"/></em> <em><font-awesome-icon icon="image"/></em>
</li> </li>
<li v-if="!edition.isOfficial" @click="imageOptIn"> <!-- <li v-if="!edition.isOfficial" @click="imageOptIn">
<small>Show Custom Images</small> <small>Show Custom Images</small>
<em <em
><font-awesome-icon ><font-awesome-icon
@ -96,7 +96,7 @@
grimoire.isImageOptIn ? 'check-square' : 'square' grimoire.isImageOptIn ? 'check-square' : 'square'
]" ]"
/></em> /></em>
</li> </li> -->
<li @click="toggleStatic"> <li @click="toggleStatic">
Disable Animations Disable Animations
<em <em
@ -134,10 +134,6 @@
Copy player link Copy player link
<em><font-awesome-icon icon="copy"/></em> <em><font-awesome-icon icon="copy"/></em>
</li> </li>
<li v-if="!session.isSpectator" @click="distributeRoles">
Send Characters
<em><font-awesome-icon icon="theater-masks"/></em>
</li>
<li <li
v-if="session.voteHistory.length || !session.isSpectator" v-if="session.voteHistory.length || !session.isSpectator"
@click="toggleModal('voteHistory')" @click="toggleModal('voteHistory')"
@ -145,7 +141,7 @@
Vote history<em>[V]</em> Vote history<em>[V]</em>
</li> </li>
<li @click="leaveSession"> <li @click="leaveSession">
Leave Session 退出房间
<em>{{ session.sessionId }}</em> <em>{{ session.sessionId }}</em>
</li> </li>
</template> </template>
@ -154,13 +150,13 @@
<template v-if="tab === 'players' && !session.isSpectator"> <template v-if="tab === 'players' && !session.isSpectator">
<!-- Users --> <!-- Users -->
<li class="headline">Players</li> <li class="headline">Players</li>
<li @click="addPlayer" v-if="players.length < 20">Add<em>[A]</em></li> <li @click="addPlayer" v-if="players.length < 20">添加座位<em>[A]</em></li>
<li @click="randomizeSeatings" v-if="players.length > 2"> <li @click="randomizeSeatings" v-if="players.length > 2">
Randomize 随机座位
<em><font-awesome-icon icon="dice"/></em> <em><font-awesome-icon icon="dice"/></em>
</li> </li>
<li @click="clearPlayers" v-if="players.length"> <li @click="clearPlayers" v-if="players.length">
Remove all 移除全部座位
<em><font-awesome-icon icon="trash-alt"/></em> <em><font-awesome-icon icon="trash-alt"/></em>
</li> </li>
</template> </template>
@ -169,22 +165,26 @@
<!-- Characters --> <!-- Characters -->
<li class="headline">Characters</li> <li class="headline">Characters</li>
<li v-if="!session.isSpectator" @click="toggleModal('edition')"> <li v-if="!session.isSpectator" @click="toggleModal('edition')">
Select Edition 选择剧本
<em>[E]</em> <em>[E]</em>
</li> </li>
<li <li
@click="toggleModal('roles')" @click="toggleModal('roles')"
v-if="!session.isSpectator && players.length > 4" v-if="!session.isSpectator && players.length > 4"
> >
Choose & Assign 分配角色
<em>[C]</em> <em>[C]</em>
</li> </li>
<li v-if="!session.isSpectator" @click="distributeRoles">
发送角色
<em><font-awesome-icon icon="theater-masks"/></em>
</li>
<li v-if="!session.isSpectator" @click="toggleModal('fabled')"> <li v-if="!session.isSpectator" @click="toggleModal('fabled')">
Add Fabled 传奇角色
<em><font-awesome-icon icon="dragon"/></em> <em><font-awesome-icon icon="dragon"/></em>
</li> </li>
<li @click="clearRoles" v-if="players.length"> <li @click="clearRoles" v-if="players.length">
Remove all 移除全部角色
<em><font-awesome-icon icon="trash-alt"/></em> <em><font-awesome-icon icon="trash-alt"/></em>
</li> </li>
</template> </template>
@ -193,11 +193,11 @@
<!-- Help --> <!-- Help -->
<li class="headline">Help</li> <li class="headline">Help</li>
<li @click="toggleModal('reference')"> <li @click="toggleModal('reference')">
Reference Sheet 角色能力表
<em>[R]</em> <em>[R]</em>
</li> </li>
<li @click="toggleModal('nightOrder')"> <li @click="toggleModal('nightOrder')">
Night Order Sheet 夜晚顺序表
<em>[N]</em> <em>[N]</em>
</li> </li>
<li @click="toggleModal('gameState')"> <li @click="toggleModal('gameState')">
@ -253,7 +253,7 @@ export default {
hostSession() { hostSession() {
if (this.session.sessionId) return; if (this.session.sessionId) return;
const sessionId = prompt( const sessionId = prompt(
"Enter a channel number / name for your session", "输入你想要创建的房间的名称或号码",
Math.round(Math.random() * 10000) Math.round(Math.random() * 10000)
); );
if (sessionId) { if (sessionId) {
@ -271,7 +271,7 @@ export default {
distributeRoles() { distributeRoles() {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
const popup = const popup =
"Do you want to distribute assigned characters to all SEATED players?"; "你确定要向所有已入座的玩家发送角色吗?";
if (confirm(popup)) { if (confirm(popup)) {
this.$store.commit("session/distributeRoles", true); this.$store.commit("session/distributeRoles", true);
setTimeout( setTimeout(
@ -292,7 +292,7 @@ export default {
joinSession() { joinSession() {
if (this.session.sessionId) return this.leaveSession(); if (this.session.sessionId) return this.leaveSession();
let sessionId = prompt( let sessionId = prompt(
"Enter the channel number / name of the session you want to join" "输入你想要加入的房间的名称或号码"
); );
if (sessionId.match(/^https?:\/\//i)) { if (sessionId.match(/^https?:\/\//i)) {
sessionId = sessionId.split("#").pop(); sessionId = sessionId.split("#").pop();
@ -305,7 +305,7 @@ export default {
} }
}, },
leaveSession() { leaveSession() {
if (confirm("Are you sure you want to leave the active live game?")) { if (confirm("你确定要离开房间吗?")) {
this.$store.commit("session/setSpectator", false); this.$store.commit("session/setSpectator", false);
this.$store.commit("session/setSessionId", ""); this.$store.commit("session/setSessionId", "");
} }
@ -320,13 +320,13 @@ export default {
}, },
randomizeSeatings() { randomizeSeatings() {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
if (confirm("Are you sure you want to randomize seatings?")) { if (confirm("你确定要打乱座位吗?")) {
this.$store.dispatch("players/randomize"); this.$store.dispatch("players/randomize");
} }
}, },
clearPlayers() { clearPlayers() {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
if (confirm("Are you sure you want to remove all players?")) { if (confirm("你确定要移除所有座位吗?")) {
// abort vote if in progress // abort vote if in progress
if (this.session.nomination) { if (this.session.nomination) {
this.$store.commit("session/nomination"); this.$store.commit("session/nomination");
@ -335,7 +335,7 @@ export default {
} }
}, },
clearRoles() { clearRoles() {
if (confirm("Are you sure you want to remove all player roles?")) { if (confirm("你确定要移除所有玩家的角色吗?")) {
this.$store.dispatch("players/clearRoles"); this.$store.dispatch("players/clearRoles");
} }
}, },

View file

@ -10,9 +10,9 @@
'no-vote': player.isVoteless, 'no-vote': player.isVoteless,
you: session.sessionId && player.id && player.id === session.playerId, you: session.sessionId && player.id && player.id === session.playerId,
'vote-yes': session.votes[index], 'vote-yes': session.votes[index],
'vote-lock': voteLocked 'vote-lock': voteLocked,
}, },
player.role.team player.role.team,
]" ]"
> >
<div class="shroud" @click="toggleStatus()"></div> <div class="shroud" @click="toggleStatus()"></div>
@ -121,7 +121,7 @@
@click="changePronouns" @click="changePronouns"
v-if=" v-if="
!session.isSpectator || !session.isSpectator ||
(session.isSpectator && player.id === session.playerId) (session.isSpectator && player.id === session.playerId)
" "
> >
<font-awesome-icon icon="venus-mars" />Change Pronouns <font-awesome-icon icon="venus-mars" />Change Pronouns
@ -162,9 +162,7 @@
:class="{ disabled: player.id && player.id !== session.playerId }" :class="{ disabled: player.id && player.id !== session.playerId }"
> >
<font-awesome-icon icon="chair" /> <font-awesome-icon icon="chair" />
<template v-if="!player.id"> <template v-if="!player.id"> Claim seat </template>
Claim seat
</template>
<template v-else-if="player.id === session.playerId"> <template v-else-if="player.id === session.playerId">
Vacate seat Vacate seat
</template> </template>
@ -188,10 +186,12 @@
backgroundImage: `url(${ backgroundImage: `url(${
reminder.image && grimoire.isImageOptIn reminder.image && grimoire.isImageOptIn
? reminder.image ? reminder.image
: require('../assets/icons/' + : require(
(reminder.imageAlt || reminder.role) + '../assets/icons/' +
'.png') (reminder.imageAlt || reminder.role) +
})` '.png',
)
})`,
}" }"
></span> ></span>
<span class="text">{{ reminder.name }}</span> <span class="text">{{ reminder.name }}</span>
@ -210,22 +210,22 @@ import { mapGetters, mapState } from "vuex";
export default { export default {
components: { components: {
Token Token,
}, },
props: { props: {
player: { player: {
type: Object, type: Object,
required: true required: true,
} },
}, },
computed: { computed: {
...mapState("players", ["players"]), ...mapState("players", ["players"]),
...mapState(["grimoire", "session"]), ...mapState(["grimoire", "session"]),
...mapGetters({ nightOrder: "players/nightOrder" }), ...mapGetters({ nightOrder: "players/nightOrder" }),
index: function() { index: function () {
return this.players.indexOf(this.player); return this.players.indexOf(this.player);
}, },
voteLocked: function() { voteLocked: function () {
const session = this.session; const session = this.session;
const players = this.players.length; const players = this.players.length;
if (!session.nomination) return false; if (!session.nomination) return false;
@ -233,7 +233,7 @@ export default {
(this.index - 1 + players - session.nomination[1]) % players; (this.index - 1 + players - session.nomination[1]) % players;
return indexAdjusted < session.lockedVote - 1; return indexAdjusted < session.lockedVote - 1;
}, },
zoom: function() { zoom: function () {
const unit = window.innerWidth > window.innerHeight ? "vh" : "vw"; const unit = window.innerWidth > window.innerHeight ? "vh" : "vw";
if (this.players.length < 7) { if (this.players.length < 7) {
return { width: 18 + this.grimoire.zoom + unit }; return { width: 18 + this.grimoire.zoom + unit };
@ -244,12 +244,12 @@ export default {
} else { } else {
return { width: 12 + this.grimoire.zoom + unit }; return { width: 12 + this.grimoire.zoom + unit };
} }
} },
}, },
data() { data() {
return { return {
isMenuOpen: false, isMenuOpen: false,
isSwap: false isSwap: false,
}; };
}, },
methods: { methods: {
@ -305,7 +305,7 @@ export default {
this.$store.commit("players/update", { this.$store.commit("players/update", {
player: this.player, player: this.player,
property, property,
value value,
}); });
if (closeMenu) { if (closeMenu) {
this.isMenuOpen = false; this.isMenuOpen = false;
@ -342,10 +342,10 @@ export default {
if (!this.voteLocked) return; if (!this.voteLocked) return;
this.$store.commit("session/voteSync", [ this.$store.commit("session/voteSync", [
this.index, this.index,
!this.session.votes[this.index] !this.session.votes[this.index],
]); ]);
} },
} },
}; };
</script> </script>
@ -632,11 +632,11 @@ li.move:not(.from) .player .overlay svg.move {
} }
} }
@include glow("townsfolk", $townsfolk); @include glow("镇民", $townsfolk);
@include glow("outsider", $outsider); @include glow("外来者", $outsider);
@include glow("demon", $demon); @include glow("恶魔", $demon);
@include glow("minion", $minion); @include glow("爪牙", $minion);
@include glow("traveler", $traveler); @include glow("旅行者", $traveler);
.player.you .token { .player.you .token {
animation: townsfolk-glow 5s ease-in-out infinite; animation: townsfolk-glow 5s ease-in-out infinite;
@ -869,7 +869,10 @@ li.move:not(.from) .player .overlay svg.move {
width: 100%; width: 100%;
position: absolute; position: absolute;
top: 15%; top: 15%;
text-shadow: 0 1px 1px #f6dfbd, 0 -1px 1px #f6dfbd, 1px 0 1px #f6dfbd, text-shadow:
0 1px 1px #f6dfbd,
0 -1px 1px #f6dfbd,
1px 0 1px #f6dfbd,
-1px 0 1px #f6dfbd; -1px 0 1px #f6dfbd;
} }

View file

@ -5,7 +5,7 @@
:class="{ :class="{
public: grimoire.isPublic, public: grimoire.isPublic,
spectator: session.isSpectator, spectator: session.isSpectator,
vote: session.nomination vote: session.nomination,
}" }"
> >
<ul class="circle" :class="['size-' + players.length]"> <ul class="circle" :class="['size-' + players.length]">
@ -18,7 +18,7 @@
from: Math.max(swap, move, nominate) === index, from: Math.max(swap, move, nominate) === index,
swap: swap > -1, swap: swap > -1,
move: move > -1, move: move > -1,
nominate: nominate > -1 nominate: nominate > -1,
}" }"
></Player> ></Player>
</ul> </ul>
@ -29,12 +29,20 @@
ref="bluffs" ref="bluffs"
:class="{ closed: !isBluffsOpen }" :class="{ closed: !isBluffsOpen }"
> >
<h3> <h5>
<span v-if="session.isSpectator">Other characters</span> <span v-if="session.isSpectator">Other characters</span>
<span v-else>Demon bluffs</span> <span v-else>恶魔的伪装角色</span>
<font-awesome-icon icon="times-circle" @click.stop="toggleBluffs" /> <font-awesome-icon
<font-awesome-icon icon="plus-circle" @click.stop="toggleBluffs" /> icon="times-circle"
</h3> v-if="isBluffsOpen"
@click.stop="toggleBluffs"
/>
<font-awesome-icon
icon="plus-circle"
v-if="!isBluffsOpen"
@click.stop="toggleBluffs"
/>
</h5>
<ul> <ul>
<li <li
v-for="index in bluffSize" v-for="index in bluffSize"
@ -47,11 +55,19 @@
</div> </div>
<div class="fabled" :class="{ closed: !isFabledOpen }" v-if="fabled.length"> <div class="fabled" :class="{ closed: !isFabledOpen }" v-if="fabled.length">
<h3> <h5>
<span>Fabled</span> <span>传奇角色</span>
<font-awesome-icon icon="times-circle" @click.stop="toggleFabled" /> <font-awesome-icon
<font-awesome-icon icon="plus-circle" @click.stop="toggleFabled" /> icon="times-circle"
</h3> v-if="isFabledOpen"
@click.stop="toggleFabled"
/>
<font-awesome-icon
icon="plus-circle"
v-if="!isFabledOpen"
@click.stop="toggleFabled"
/>
</h5>
<ul> <ul>
<li <li
v-for="(role, index) in fabled" v-for="(role, index) in fabled"
@ -98,12 +114,12 @@ export default {
Player, Player,
Token, Token,
RoleModal, RoleModal,
ReminderModal ReminderModal,
}, },
computed: { computed: {
...mapGetters({ nightOrder: "players/nightOrder" }), ...mapGetters({ nightOrder: "players/nightOrder" }),
...mapState(["grimoire", "roles", "session"]), ...mapState(["grimoire", "roles", "session"]),
...mapState("players", ["players", "bluffs", "fabled"]) ...mapState("players", ["players", "bluffs", "fabled"]),
}, },
data() { data() {
return { return {
@ -113,7 +129,7 @@ export default {
move: -1, move: -1,
nominate: -1, nominate: -1,
isBluffsOpen: true, isBluffsOpen: true,
isFabledOpen: true isFabledOpen: true,
}; };
}, },
methods: { methods: {
@ -155,7 +171,7 @@ export default {
if (this.session.isSpectator || this.session.lockedVote) return; if (this.session.isSpectator || this.session.lockedVote) return;
if ( if (
confirm( confirm(
`Do you really want to remove ${this.players[playerIndex].name}?` `Do you really want to remove ${this.players[playerIndex].name}?`,
) )
) { ) {
const { nomination } = this.session; const { nomination } = this.session;
@ -170,7 +186,7 @@ export default {
// update nomination array if removed player has lower index // update nomination array if removed player has lower index
this.$store.commit("session/setNomination", [ this.$store.commit("session/setNomination", [
nomination[0] > playerIndex ? nomination[0] - 1 : nomination[0], nomination[0] > playerIndex ? nomination[0] - 1 : nomination[0],
nomination[1] > playerIndex ? nomination[1] - 1 : nomination[1] nomination[1] > playerIndex ? nomination[1] - 1 : nomination[1],
]); ]);
} }
} }
@ -186,7 +202,7 @@ export default {
if (this.session.nomination) { if (this.session.nomination) {
// update nomination if one of the involved players is swapped // update nomination if one of the involved players is swapped
const swapTo = this.players.indexOf(to); const swapTo = this.players.indexOf(to);
const updatedNomination = this.session.nomination.map(nom => { const updatedNomination = this.session.nomination.map((nom) => {
if (nom === this.swap) return swapTo; if (nom === this.swap) return swapTo;
if (nom === swapTo) return this.swap; if (nom === swapTo) return this.swap;
return nom; return nom;
@ -200,7 +216,7 @@ export default {
} }
this.$store.commit("players/swap", [ this.$store.commit("players/swap", [
this.swap, this.swap,
this.players.indexOf(to) this.players.indexOf(to),
]); ]);
this.cancel(); this.cancel();
} }
@ -214,7 +230,7 @@ export default {
if (this.session.nomination) { if (this.session.nomination) {
// update nomination if it is affected by the move // update nomination if it is affected by the move
const moveTo = this.players.indexOf(to); const moveTo = this.players.indexOf(to);
const updatedNomination = this.session.nomination.map(nom => { const updatedNomination = this.session.nomination.map((nom) => {
if (nom === this.move) return moveTo; if (nom === this.move) return moveTo;
if (nom > this.move && nom <= moveTo) return nom - 1; if (nom > this.move && nom <= moveTo) return nom - 1;
if (nom < this.move && nom >= moveTo) return nom + 1; if (nom < this.move && nom >= moveTo) return nom + 1;
@ -229,7 +245,7 @@ export default {
} }
this.$store.commit("players/move", [ this.$store.commit("players/move", [
this.move, this.move,
this.players.indexOf(to) this.players.indexOf(to),
]); ]);
this.cancel(); this.cancel();
} }
@ -251,8 +267,8 @@ export default {
this.move = -1; this.move = -1;
this.swap = -1; this.swap = -1;
this.nominate = -1; this.nominate = -1;
} },
} },
}; };
</script> </script>
@ -272,8 +288,8 @@ export default {
.circle { .circle {
padding: 0; padding: 0;
width: 100%; width: 100vw;
height: 100%; height: min(100vw, 100vh);
list-style: none; list-style: none;
margin: 0; margin: 0;
@ -459,8 +475,8 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
li { li {
width: 14vh; width: max(min(8vh, 8vw), 50px);
height: 14vh; height: max(min(8vh, 8vw), 50px);
margin: 0 0.5%; margin: 0 0.5%;
display: inline-block; display: inline-block;
transition: all 250ms; transition: all 250ms;

View file

@ -31,16 +31,16 @@
<script> <script>
export default { export default {
data: function() { data: function () {
return { return {
isMaximized: false isMaximized: false,
}; };
}, },
methods: { methods: {
close() { close() {
this.$emit("close"); this.$emit("close");
} },
} },
}; };
</script> </script>

View file

@ -113,25 +113,24 @@ export default {
if (this.players.length > 6) { if (this.players.length > 6) {
rolesFirstNight.push( rolesFirstNight.push(
{ {
id: "evil", id: "minion_info",
name: "Minion info", name: "爪牙信息",
firstNight: 5, firstNight: 5,
team: "minion", team: "minion",
players: this.players.filter(p => p.role.team === "minion"), players: this.players.filter(p => p.role.team === "minion"),
firstNightReminder: firstNightReminder:
"• If more than one Minion, they all make eye contact with each other. " + "• 如果有七名或以上玩家:爪牙睁眼相互认识。" +
"• Show the “This is the Demon” card. Point to the Demon." "• 告诉他们恶魔是哪名玩家。"
}, },
{ {
id: "evil", id: "demon_info",
name: "Demon info & bluffs", name: "恶魔信息",
firstNight: 8, firstNight: 8,
team: "demon", team: "demon",
players: this.players.filter(p => p.role.team === "demon"), players: this.players.filter(p => p.role.team === "demon"),
firstNightReminder: firstNightReminder:
"• Show the “These are your minions” card. Point to each Minion. " + "• 如果有七名或以上玩家:告诉恶魔,这些玩家是你的爪牙。 " +
"• Show the “These characters are not in play” card. Show 3 character tokens of good " + "• 告诉恶魔3个不在场的伪装角色。"
"characters not in play."
} }
); );
} }

View file

@ -18,10 +18,12 @@
backgroundImage: `url(${ backgroundImage: `url(${
reminder.image && grimoire.isImageOptIn reminder.image && grimoire.isImageOptIn
? reminder.image ? reminder.image
: require('../../assets/icons/' + : require(
(reminder.imageAlt || reminder.role) + '../../assets/icons/' +
'.png') (reminder.imageAlt || reminder.role) +
})` '.png',
)
})`,
}" }"
></span> ></span>
<span class="text">{{ reminder.name }}</span> <span class="text">{{ reminder.name }}</span>
@ -39,12 +41,14 @@ import { mapMutations, mapState } from "vuex";
* @param role The role for which the reminder should be generated * @param role The role for which the reminder should be generated
* @return {function(*): {image: string|string[]|string|*, role: *, name: *, imageAlt: string|*}} * @return {function(*): {image: string|string[]|string|*, role: *, name: *, imageAlt: string|*}}
*/ */
const mapReminder = ({ id, image, imageAlt }) => name => ({ const mapReminder =
role: id, ({ id, image, imageAlt }) =>
image, (name) => ({
imageAlt, role: id,
name image,
}); imageAlt,
name,
});
export default { export default {
components: { Modal }, components: { Modal },
@ -53,31 +57,31 @@ export default {
availableReminders() { availableReminders() {
let reminders = []; let reminders = [];
const { players, bluffs } = this.$store.state.players; const { players, bluffs } = this.$store.state.players;
this.$store.state.roles.forEach(role => { this.$store.state.roles.forEach((role) => {
// add reminders from player roles // add reminders from player roles
if (players.some(p => p.role.id === role.id)) { if (players.some((p) => p.role.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))]; reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
} }
// add reminders from bluff/other roles // add reminders from bluff/other roles
else if (bluffs.some(bluff => bluff.id === role.id)) { else if (bluffs.some((bluff) => bluff.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))]; reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
} }
// add global reminders // add global reminders
if (role.remindersGlobal && role.remindersGlobal.length) { if (role.remindersGlobal && role.remindersGlobal.length) {
reminders = [ reminders = [
...reminders, ...reminders,
...role.remindersGlobal.map(mapReminder(role)) ...role.remindersGlobal.map(mapReminder(role)),
]; ];
} }
}); });
// add fabled reminders // add fabled reminders
this.$store.state.players.fabled.forEach(role => { this.$store.state.players.fabled.forEach((role) => {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))]; reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
}); });
// add out of script traveler reminders // add out of script traveler reminders
this.$store.state.otherTravelers.forEach(role => { this.$store.state.otherTravelers.forEach((role) => {
if (players.some(p => p.role.id === role.id)) { if (players.some((p) => p.role.id === role.id)) {
reminders = [...reminders, ...role.reminders.map(mapReminder(role))]; reminders = [...reminders, ...role.reminders.map(mapReminder(role))];
} }
}); });
@ -88,7 +92,7 @@ export default {
return reminders; return reminders;
}, },
...mapState(["modals", "grimoire"]), ...mapState(["modals", "grimoire"]),
...mapState("players", ["players"]) ...mapState("players", ["players"]),
}, },
methods: { methods: {
addReminder(reminder) { addReminder(reminder) {
@ -104,12 +108,12 @@ export default {
this.$store.commit("players/update", { this.$store.commit("players/update", {
player, player,
property: "reminders", property: "reminders",
value value,
}); });
this.$store.commit("toggleModal", "reminder"); this.$store.commit("toggleModal", "reminder");
}, },
...mapMutations(["toggleModal"]) ...mapMutations(["toggleModal"]),
} },
}; };
</script> </script>
@ -117,8 +121,8 @@ export default {
ul.reminders .reminder { ul.reminders .reminder {
background: url("../../assets/reminder.png") center center; background: url("../../assets/reminder.png") center center;
background-size: 100%; background-size: 100%;
width: 14vh; width: max(min(14vh, 14vw), 50px);
height: 14vh; height: max(min(14vh, 14vw), 50px);
max-width: 100px; max-width: 100px;
max-height: 100px; max-height: 100px;
display: flex; display: flex;
@ -145,12 +149,12 @@ ul.reminders .reminder {
.text { .text {
color: black; color: black;
font-size: 65%; font-size: 0.9em;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
top: 28%; top: 28%;
width: 80%; width: 80%;
line-height: 1; line-height: 0.9em;
} }
&:hover { &:hover {

View file

@ -60,12 +60,12 @@ export default {
availableRoles() { availableRoles() {
const availableRoles = []; const availableRoles = [];
const players = this.$store.state.players.players; const players = this.$store.state.players.players;
this.$store.state.roles.forEach(role => { this.$store.state.roles.forEach((role) => {
// don't show bluff roles that are already assigned to players // don't show bluff roles that are already assigned to players
if ( if (
this.playerIndex >= 0 || this.playerIndex >= 0 ||
(this.playerIndex < 0 && (this.playerIndex < 0 &&
!players.some(player => player.role.id === role.id)) !players.some((player) => player.role.id === role.id))
) { ) {
availableRoles.push(role); availableRoles.push(role);
} }
@ -75,11 +75,11 @@ export default {
}, },
...mapState(["modals", "roles", "session"]), ...mapState(["modals", "roles", "session"]),
...mapState("players", ["players"]), ...mapState("players", ["players"]),
...mapState(["otherTravelers"]) ...mapState(["otherTravelers"]),
}, },
data() { data() {
return { return {
tab: "editionRoles" tab: "editionRoles",
}; };
}, },
methods: { methods: {
@ -88,7 +88,7 @@ export default {
// assign to bluff slot (index < 0) // assign to bluff slot (index < 0)
this.$store.commit("players/setBluff", { this.$store.commit("players/setBluff", {
index: this.playerIndex * -1 - 1, index: this.playerIndex * -1 - 1,
role role,
}); });
} else { } else {
if (this.session.isSpectator && role.team === "traveler") return; if (this.session.isSpectator && role.team === "traveler") return;
@ -97,7 +97,7 @@ export default {
this.$store.commit("players/update", { this.$store.commit("players/update", {
player, player,
property: "role", property: "role",
value: role value: role,
}); });
} }
this.tab = "editionRoles"; this.tab = "editionRoles";
@ -107,8 +107,8 @@ export default {
this.tab = "editionRoles"; this.tab = "editionRoles";
this.toggleModal("role"); this.toggleModal("role");
}, },
...mapMutations(["toggleModal"]) ...mapMutations(["toggleModal"]),
} },
}; };
</script> </script>
@ -117,24 +117,34 @@ export default {
ul.tokens li { ul.tokens li {
border-radius: 50%; border-radius: 50%;
width: 6vw; width: max(6vw, 60px);
margin: 1%; margin: 1%;
transition: transform 500ms ease; transition: transform 500ms ease;
&.townsfolk { &.townsfolk {
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff; box-shadow:
0 0 10px $townsfolk,
0 0 10px #004cff;
} }
&.outsider { &.outsider {
box-shadow: 0 0 10px $outsider, 0 0 10px $outsider; box-shadow:
0 0 10px $outsider,
0 0 10px $outsider;
} }
&.minion { &.minion {
box-shadow: 0 0 10px $minion, 0 0 10px $minion; box-shadow:
0 0 10px $minion,
0 0 10px $minion;
} }
&.demon { &.demon {
box-shadow: 0 0 10px $demon, 0 0 10px $demon; box-shadow:
0 0 10px $demon,
0 0 10px $demon;
} }
&.traveler { &.traveler {
box-shadow: 0 0 10px $traveler, 0 0 10px $traveler; box-shadow:
0 0 10px $traveler,
0 0 10px $traveler;
} }
&:hover { &:hover {
transform: scale(1.2); transform: scale(1.2);

View file

@ -4,7 +4,7 @@
v-if="modals.roles && nonTravelers >= 5" v-if="modals.roles && nonTravelers >= 5"
@close="toggleModal('roles')" @close="toggleModal('roles')"
> >
<h3>Select the characters for {{ nonTravelers }} players:</h3> <h3>{{ nonTravelers }} 个玩家选择角色:</h3>
<ul class="tokens" v-for="(teamRoles, team) in roleSelection" :key="team"> <ul class="tokens" v-for="(teamRoles, team) in roleSelection" :key="team">
<li class="count" :class="[team]"> <li class="count" :class="[team]">
{{ teamRoles.reduce((a, { selected }) => a + selected, 0) }} / {{ teamRoles.reduce((a, { selected }) => a + selected, 0) }} /
@ -31,14 +31,13 @@
<div class="warning" v-if="hasSelectedSetupRoles"> <div class="warning" v-if="hasSelectedSetupRoles">
<font-awesome-icon icon="exclamation-triangle" /> <font-awesome-icon icon="exclamation-triangle" />
<span> <span>
Warning: there are characters selected that modify the game setup! The 注意目前选择的角色会影响配板需要说书人手动调整
randomizer does not account for these characters.
</span> </span>
</div> </div>
<label class="multiple" :class="{ checked: allowMultiple }"> <label class="multiple" :class="{ checked: allowMultiple }">
<font-awesome-icon :icon="allowMultiple ? 'check-square' : 'square'" /> <font-awesome-icon :icon="allowMultiple ? 'check-square' : 'square'" />
<input type="checkbox" name="allow-multiple" v-model="allowMultiple" /> <input type="checkbox" name="allow-multiple" v-model="allowMultiple" />
Allow duplicate characters 允许重复角色
</label> </label>
<div class="button-group"> <div class="button-group">
<div <div
@ -49,11 +48,11 @@
}" }"
> >
<font-awesome-icon icon="people-arrows" /> <font-awesome-icon icon="people-arrows" />
Assign {{ selectedRoles }} characters randomly 随机分配 {{ selectedRoles }} 个角色
</div> </div>
<div class="button" @click="selectRandomRoles"> <div class="button" @click="selectRandomRoles">
<font-awesome-icon icon="random" /> <font-awesome-icon icon="random" />
Shuffle characters 随机选取角色
</div> </div>
</div> </div>
</Modal> </Modal>

View file

@ -104,7 +104,7 @@ export default new Vuex.Store({
isMenuOpen: false, isMenuOpen: false,
isStatic: false, isStatic: false,
isMuted: false, isMuted: false,
isImageOptIn: false, isImageOptIn: true,
zoom: 0, zoom: 0,
background: "" background: ""
}, },
@ -170,7 +170,7 @@ export default new Vuex.Store({
toggleStatic: toggle("isStatic"), toggleStatic: toggle("isStatic"),
toggleNight: toggle("isNight"), toggleNight: toggle("isNight"),
toggleGrimoire: toggle("isPublic"), toggleGrimoire: toggle("isPublic"),
toggleImageOptIn: toggle("isImageOptIn"), // toggleImageOptIn: toggle("isImageOptIn"),
toggleModal({ modals }, name) { toggleModal({ modals }, name) {
if (name) { if (name) {
modals[name] = !modals[name]; modals[name] = !modals[name];