mirror of https://github.com/bra1n/townsquare.git
Merge pull request #115 from bra1n/multiple_character_selec
allow selecting characters multiple times
This commit is contained in:
commit
f3dec23233
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,5 +1,16 @@
|
||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
## Version 2.7.0
|
||||||
|
- added support for assigning duplicate characters to more than one player (like Legion)
|
||||||
|
- further live session bandwidth optimizations
|
||||||
|
- sessions can now be joined by pasting the whole link into the popup (thanks @davotronic5000)
|
||||||
|
- fabled night order bug fixed
|
||||||
|
- added Legion to list of available characters (thanks @eddgabriel)
|
||||||
|
- added support for mp4/webm video backgrounds
|
||||||
|
- added tooltips to night order popup
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Version 2.6.0
|
## Version 2.6.0
|
||||||
- night mode can be toggeled with [S] now (thanks @davotronic5000)
|
- night mode can be toggeled with [S] now (thanks @davotronic5000)
|
||||||
- night order shows which players are dead
|
- night order shows which players are dead
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "townsquare",
|
"name": "townsquare",
|
||||||
"version": "2.6.0",
|
"version": "2.7.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "townsquare",
|
"name": "townsquare",
|
||||||
"version": "2.6.0",
|
"version": "2.7.0",
|
||||||
"description": "Blood on the Clocktower Town Square",
|
"description": "Blood on the Clocktower Town Square",
|
||||||
"author": "Steffen Baumgart",
|
"author": "Steffen Baumgart",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -7,22 +7,38 @@
|
||||||
<h3>Select the characters for {{ nonTravelers }} players:</h3>
|
<h3>Select the characters for {{ nonTravelers }} players:</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.filter(role => role.selected).length }} /
|
{{ teamRoles.reduce((a, { selected }) => a + selected, 0) }} /
|
||||||
{{ game[nonTravelers - 5][team] }}
|
{{ game[nonTravelers - 5][team] }}
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-for="role in teamRoles"
|
v-for="role in teamRoles"
|
||||||
:class="[role.team, role.selected ? 'selected' : '']"
|
:class="[role.team, role.selected ? 'selected' : '']"
|
||||||
:key="role.id"
|
:key="role.id"
|
||||||
@click="role.selected = !role.selected"
|
@click="role.selected = role.selected ? 0 : 1"
|
||||||
>
|
>
|
||||||
<Token :role="role" />
|
<Token :role="role" />
|
||||||
|
<div class="buttons" v-if="allowMultiple">
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="minus-circle"
|
||||||
|
@click.stop="role.selected--"
|
||||||
|
/>
|
||||||
|
<span>{{ role.selected > 1 ? "x" + role.selected : "" }}</span>
|
||||||
|
<font-awesome-icon icon="plus-circle" @click.stop="role.selected++" />
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="warning" v-if="hasSelectedSetupRoles">
|
<div class="warning" v-if="hasSelectedSetupRoles">
|
||||||
|
<font-awesome-icon icon="exclamation-triangle" />
|
||||||
|
<span>
|
||||||
Warning: there are characters selected that modify the game setup! The
|
Warning: there are characters selected that modify the game setup! The
|
||||||
randomizer does not account for these characters.
|
randomizer does not account for these characters.
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="multiple" :class="{ checked: allowMultiple }">
|
||||||
|
<font-awesome-icon :icon="allowMultiple ? 'check-square' : 'square'" />
|
||||||
|
<input type="checkbox" name="allow-multiple" v-model="allowMultiple" />
|
||||||
|
Allow duplicate characters
|
||||||
|
</label>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<div
|
<div
|
||||||
class="button"
|
class="button"
|
||||||
|
@ -58,13 +74,14 @@ export default {
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
roleSelection: {},
|
roleSelection: {},
|
||||||
game: gameJSON
|
game: gameJSON,
|
||||||
|
allowMultiple: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
selectedRoles: function() {
|
selectedRoles: function() {
|
||||||
return Object.values(this.roleSelection)
|
return Object.values(this.roleSelection)
|
||||||
.map(roles => roles.filter(role => role.selected).length)
|
.map(roles => roles.reduce((a, { selected }) => a + selected, 0))
|
||||||
.reduce((a, b) => a + b, 0);
|
.reduce((a, b) => a + b, 0);
|
||||||
},
|
},
|
||||||
hasSelectedSetupRoles: function() {
|
hasSelectedSetupRoles: function() {
|
||||||
|
@ -84,7 +101,7 @@ export default {
|
||||||
this.$set(this.roleSelection, role.team, []);
|
this.$set(this.roleSelection, role.team, []);
|
||||||
}
|
}
|
||||||
this.roleSelection[role.team].push(role);
|
this.roleSelection[role.team].push(role);
|
||||||
this.$set(role, "selected", false);
|
this.$set(role, "selected", 0);
|
||||||
});
|
});
|
||||||
delete this.roleSelection["traveler"];
|
delete this.roleSelection["traveler"];
|
||||||
const playerCount = Math.max(5, this.nonTravelers);
|
const playerCount = Math.max(5, this.nonTravelers);
|
||||||
|
@ -93,10 +110,10 @@ export default {
|
||||||
for (let x = 0; x < composition[team]; x++) {
|
for (let x = 0; x < composition[team]; x++) {
|
||||||
if (this.roleSelection[team]) {
|
if (this.roleSelection[team]) {
|
||||||
const available = this.roleSelection[team].filter(
|
const available = this.roleSelection[team].filter(
|
||||||
role => role.selected !== true
|
role => !role.selected
|
||||||
);
|
);
|
||||||
if (available.length) {
|
if (available.length) {
|
||||||
randomElement(available).selected = true;
|
randomElement(available).selected = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +123,12 @@ export default {
|
||||||
if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
|
if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
|
||||||
// generate list of selected roles and randomize it
|
// generate list of selected roles and randomize it
|
||||||
const roles = Object.values(this.roleSelection)
|
const roles = Object.values(this.roleSelection)
|
||||||
.map(roles => roles.filter(role => role.selected))
|
.map(roles =>
|
||||||
|
roles
|
||||||
|
// duplicate roles selected more than once and filter unselected
|
||||||
|
.reduce((a, r) => [...a, ...Array(r.selected).fill(r)], [])
|
||||||
|
)
|
||||||
|
// flatten into a single array
|
||||||
.reduce((a, b) => [...a, ...b], [])
|
.reduce((a, b) => [...a, ...b], [])
|
||||||
.map(a => [Math.random(), a])
|
.map(a => [Math.random(), a])
|
||||||
.sort((a, b) => a[0] - b[0])
|
.sort((a, b) => a[0] - b[0])
|
||||||
|
@ -152,6 +174,9 @@ ul.tokens {
|
||||||
transition: all 250ms;
|
transition: all 250ms;
|
||||||
&.selected {
|
&.selected {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.townsfolk {
|
&.townsfolk {
|
||||||
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff;
|
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff;
|
||||||
|
@ -172,6 +197,27 @@ ul.tokens {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
.buttons {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 95%;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 30;
|
||||||
|
font-weight: bold;
|
||||||
|
filter: drop-shadow(0 0 5px rgba(0, 0, 0, 1));
|
||||||
|
span {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
opacity: 0.25;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.count {
|
.count {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
@ -203,9 +249,51 @@ ul.tokens {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.roles .modal .warning {
|
.roles .modal {
|
||||||
color: red;
|
.multiple {
|
||||||
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: auto;
|
cursor: pointer;
|
||||||
|
&.checked,
|
||||||
|
&:hover {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
&.checked {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: red;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 10;
|
||||||
|
svg {
|
||||||
|
font-size: 150%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
right: -20px;
|
||||||
|
bottom: 30px;
|
||||||
|
width: 420px;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid black;
|
||||||
|
}
|
||||||
|
&:hover span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -18,6 +18,7 @@ const faIcons = [
|
||||||
"Dice",
|
"Dice",
|
||||||
"Dragon",
|
"Dragon",
|
||||||
"ExchangeAlt",
|
"ExchangeAlt",
|
||||||
|
"ExclamationTriangle",
|
||||||
"FileCode",
|
"FileCode",
|
||||||
"FileUpload",
|
"FileUpload",
|
||||||
"HandPaper",
|
"HandPaper",
|
||||||
|
|
Loading…
Reference in New Issue