Merge branch 'main' into custom-images

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Steffen 2021-02-08 13:58:27 +01:00
commit 8bd3c9b450
5 changed files with 111 additions and 17 deletions

View File

@ -1,8 +1,13 @@
# Release Notes
## Version 2.7.0
### Version 2.8.0
- added hands-off live session support for homebrew / custom characters again!
- added custom image opt-in that will prevent any (potentially malicious / harmful) images from loading until a player manually allows them to
---
## 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

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "townsquare",
"version": "2.6.0",
"version": "2.7.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "townsquare",
"version": "2.6.0",
"version": "2.7.0",
"description": "Blood on the Clocktower Town Square",
"author": "Steffen Baumgart",
"scripts": {

View File

@ -7,22 +7,38 @@
<h3>Select the characters for {{ nonTravelers }} players:</h3>
<ul class="tokens" v-for="(teamRoles, team) in roleSelection" :key="team">
<li class="count" :class="[team]">
{{ teamRoles.filter(role => role.selected).length }} /
{{ teamRoles.reduce((a, { selected }) => a + selected, 0) }} /
{{ game[nonTravelers - 5][team] }}
</li>
<li
v-for="role in teamRoles"
:class="[role.team, role.selected ? 'selected' : '']"
:key="role.id"
@click="role.selected = !role.selected"
@click="role.selected = role.selected ? 0 : 1"
>
<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>
</ul>
<div class="warning" v-if="hasSelectedSetupRoles">
Warning: there are characters selected that modify the game setup! The
randomizer does not account for these characters.
<font-awesome-icon icon="exclamation-triangle" />
<span>
Warning: there are characters selected that modify the game setup! The
randomizer does not account for these characters.
</span>
</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"
@ -58,13 +74,14 @@ export default {
data: function() {
return {
roleSelection: {},
game: gameJSON
game: gameJSON,
allowMultiple: false
};
},
computed: {
selectedRoles: function() {
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);
},
hasSelectedSetupRoles: function() {
@ -84,7 +101,7 @@ export default {
this.$set(this.roleSelection, role.team, []);
}
this.roleSelection[role.team].push(role);
this.$set(role, "selected", false);
this.$set(role, "selected", 0);
});
delete this.roleSelection["traveler"];
const playerCount = Math.max(5, this.nonTravelers);
@ -93,10 +110,10 @@ export default {
for (let x = 0; x < composition[team]; x++) {
if (this.roleSelection[team]) {
const available = this.roleSelection[team].filter(
role => role.selected !== true
role => !role.selected
);
if (available.length) {
randomElement(available).selected = true;
randomElement(available).selected = 1;
}
}
}
@ -106,7 +123,12 @@ export default {
if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
// generate list of selected roles and randomize it
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], [])
.map(a => [Math.random(), a])
.sort((a, b) => a[0] - b[0])
@ -152,6 +174,9 @@ ul.tokens {
transition: all 250ms;
&.selected {
opacity: 1;
.buttons {
display: flex;
}
}
&.townsfolk {
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff;
@ -172,6 +197,27 @@ ul.tokens {
transform: scale(1.2);
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 {
opacity: 1;
@ -203,9 +249,51 @@ ul.tokens {
}
}
.roles .modal .warning {
color: red;
text-align: center;
margin: auto;
.roles .modal {
.multiple {
display: block;
text-align: center;
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>

View File

@ -18,6 +18,7 @@ const faIcons = [
"Dice",
"Dragon",
"ExchangeAlt",
"ExclamationTriangle",
"FileCode",
"FileUpload",
"HandPaper",