mirror of
				https://github.com/bra1n/townsquare.git
				synced 2025-10-21 16:55:12 +00:00 
			
		
		
		
	Merge branch 'main' into custom-images
# Conflicts: # CHANGELOG.md
This commit is contained in:
		
						commit
						8bd3c9b450
					
				
					 5 changed files with 111 additions and 17 deletions
				
			
		| 
						 | 
					@ -1,8 +1,13 @@
 | 
				
			||||||
# Release Notes
 | 
					# Release Notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Version 2.7.0
 | 
					### Version 2.8.0
 | 
				
			||||||
- added hands-off live session support for homebrew / custom characters again!
 | 
					- 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
 | 
					- 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
 | 
					- further live session bandwidth optimizations
 | 
				
			||||||
- sessions can now be joined by pasting the whole link into the popup (thanks @davotronic5000)
 | 
					- sessions can now be joined by pasting the whole link into the popup (thanks @davotronic5000)
 | 
				
			||||||
- fabled night order bug fixed
 | 
					- fabled night order bug fixed
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -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…
	
	Add table
		
		Reference in a new issue