mirror of
				https://github.com/bra1n/townsquare.git
				synced 2025-10-21 16:55:12 +00:00 
			
		
		
		
	
						commit
						9eb9f44e32
					
				
					 8 changed files with 78 additions and 60 deletions
				
			
		| 
						 | 
				
			
			@ -1,5 +1,10 @@
 | 
			
		|||
# Release Notes
 | 
			
		||||
 | 
			
		||||
### Version 2.13.0
 | 
			
		||||
- add support for custom Fabled characters
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Version 2.12.0
 | 
			
		||||
- tweak reference sheet to better fit screen in single column layout
 | 
			
		||||
- add warning icon overlay for setup roles on character assignment modal
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -96,7 +96,8 @@ For base game characters, it is sufficient to only provide the ID, similar to wh
 | 
			
		|||
- **remindersGlobal**: global reminder tokens that will always be available, no matter if the character is assigned to a player or not
 | 
			
		||||
- **setup**: whether this token affects setup (orange leaf), like the Drunk or Baron
 | 
			
		||||
- **name**: the displayed name of this character
 | 
			
		||||
- **team**: the team of the character, has to be one of `townsfolk`, `outsider`, `minion`, `demon` or `traveler`
 | 
			
		||||
- **team**: the team of the character, has to be one of `townsfolk`, `outsider`, `minion`, `demon`, `traveler` or `fabled`<br>
 | 
			
		||||
  _Note_: if you create a custom Fabled character, it will be automatically added to the game when the custom script is loaded
 | 
			
		||||
- **ability**: the displayed ability text of the character
 | 
			
		||||
 | 
			
		||||
## [Code of Conduct](CODE_OF_CONDUCT.md)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										16
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -1,11 +1,11 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "townsquare",
 | 
			
		||||
  "version": "2.12.0",
 | 
			
		||||
  "version": "2.13.0",
 | 
			
		||||
  "lockfileVersion": 2,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "version": "2.12.0",
 | 
			
		||||
      "version": "2.13.0",
 | 
			
		||||
      "license": "GPL-3.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@fortawesome/fontawesome-svg-core": "^1.2.32",
 | 
			
		||||
| 
						 | 
				
			
			@ -1628,9 +1628,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cacache/node_modules/ssri": {
 | 
			
		||||
      "version": "6.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
 | 
			
		||||
      "version": "6.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "figgy-pudding": "^3.5.1"
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -13477,9 +13477,9 @@
 | 
			
		|||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "ssri": {
 | 
			
		||||
          "version": "6.0.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
 | 
			
		||||
          "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
 | 
			
		||||
          "version": "6.0.2",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
 | 
			
		||||
          "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "figgy-pudding": "^3.5.1"
 | 
			
		||||
          }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "townsquare",
 | 
			
		||||
  "version": "2.12.0",
 | 
			
		||||
  "version": "2.13.0",
 | 
			
		||||
  "description": "Blood on the Clocktower Town Square",
 | 
			
		||||
  "author": "Steffen Baumgart",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								src/assets/icons/fabled.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/icons/fabled.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 136 KiB  | 
| 
						 | 
				
			
			@ -162,54 +162,62 @@ export default new Vuex.Store({
 | 
			
		|||
     * @param roles Array of role IDs or full role definitions
 | 
			
		||||
     */
 | 
			
		||||
    setCustomRoles(state, roles) {
 | 
			
		||||
      state.roles = new Map(
 | 
			
		||||
        roles
 | 
			
		||||
          // replace numerical role object keys with matching key names
 | 
			
		||||
          .map(role => {
 | 
			
		||||
            if (role[0]) {
 | 
			
		||||
              const customKeys = Object.keys(customRole);
 | 
			
		||||
              const mappedRole = {};
 | 
			
		||||
              for (let prop in role) {
 | 
			
		||||
                if (customKeys[prop]) {
 | 
			
		||||
                  mappedRole[customKeys[prop]] = role[prop];
 | 
			
		||||
                }
 | 
			
		||||
      const processedRoles = roles
 | 
			
		||||
        // replace numerical role object keys with matching key names
 | 
			
		||||
        .map(role => {
 | 
			
		||||
          if (role[0]) {
 | 
			
		||||
            const customKeys = Object.keys(customRole);
 | 
			
		||||
            const mappedRole = {};
 | 
			
		||||
            for (let prop in role) {
 | 
			
		||||
              if (customKeys[prop]) {
 | 
			
		||||
                mappedRole[customKeys[prop]] = role[prop];
 | 
			
		||||
              }
 | 
			
		||||
              return mappedRole;
 | 
			
		||||
            } else {
 | 
			
		||||
              return role;
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          // clean up role.id
 | 
			
		||||
          .map(role => {
 | 
			
		||||
            role.id = role.id.toLocaleLowerCase().replace(/[^a-z0-9]/g, "");
 | 
			
		||||
            return mappedRole;
 | 
			
		||||
          } else {
 | 
			
		||||
            return role;
 | 
			
		||||
          })
 | 
			
		||||
          // map existing roles to base definition or pre-populate custom roles to ensure all properties
 | 
			
		||||
          .map(
 | 
			
		||||
            role =>
 | 
			
		||||
              rolesJSONbyId.get(role.id) ||
 | 
			
		||||
              state.roles.get(role.id) ||
 | 
			
		||||
              Object.assign({}, customRole, role)
 | 
			
		||||
          )
 | 
			
		||||
          // default empty icons and placeholders
 | 
			
		||||
          .map(role => {
 | 
			
		||||
            if (rolesJSONbyId.get(role.id)) return role;
 | 
			
		||||
            role.imageAlt = // map team to generic icon
 | 
			
		||||
              {
 | 
			
		||||
                townsfolk: "good",
 | 
			
		||||
                outsider: "outsider",
 | 
			
		||||
                minion: "minion",
 | 
			
		||||
                demon: "evil"
 | 
			
		||||
              }[role.team] || "custom";
 | 
			
		||||
            return role;
 | 
			
		||||
          })
 | 
			
		||||
          // filter out roles that don't match an existing role and also don't have name/ability/team
 | 
			
		||||
          .filter(role => role.name && role.ability && role.team)
 | 
			
		||||
          // sort by team
 | 
			
		||||
          .sort((a, b) => b.team.localeCompare(a.team))
 | 
			
		||||
          // convert to Map
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        // clean up role.id
 | 
			
		||||
        .map(role => {
 | 
			
		||||
          role.id = role.id.toLocaleLowerCase().replace(/[^a-z0-9]/g, "");
 | 
			
		||||
          return role;
 | 
			
		||||
        })
 | 
			
		||||
        // map existing roles to base definition or pre-populate custom roles to ensure all properties
 | 
			
		||||
        .map(
 | 
			
		||||
          role =>
 | 
			
		||||
            rolesJSONbyId.get(role.id) ||
 | 
			
		||||
            state.roles.get(role.id) ||
 | 
			
		||||
            Object.assign({}, customRole, role)
 | 
			
		||||
        )
 | 
			
		||||
        // default empty icons and placeholders
 | 
			
		||||
        .map(role => {
 | 
			
		||||
          if (rolesJSONbyId.get(role.id)) return role;
 | 
			
		||||
          role.imageAlt = // map team to generic icon
 | 
			
		||||
            {
 | 
			
		||||
              townsfolk: "good",
 | 
			
		||||
              outsider: "outsider",
 | 
			
		||||
              minion: "minion",
 | 
			
		||||
              demon: "evil",
 | 
			
		||||
              fabled: "fabled"
 | 
			
		||||
            }[role.team] || "custom";
 | 
			
		||||
          return role;
 | 
			
		||||
        })
 | 
			
		||||
        // filter out roles that don't match an existing role and also don't have name/ability/team
 | 
			
		||||
        .filter(role => role.name && role.ability && role.team)
 | 
			
		||||
        // sort by team
 | 
			
		||||
        .sort((a, b) => b.team.localeCompare(a.team));
 | 
			
		||||
      // convert to Map without Fabled
 | 
			
		||||
      state.roles = new Map(
 | 
			
		||||
        processedRoles
 | 
			
		||||
          .filter(role => role.team !== "fabled")
 | 
			
		||||
          .map(role => [role.id, role])
 | 
			
		||||
      );
 | 
			
		||||
      // update Fabled to include custom Fabled from this script
 | 
			
		||||
      state.fabled = new Map([
 | 
			
		||||
        ...processedRoles.filter(r => r.team === "fabled").map(r => [r.id, r]),
 | 
			
		||||
        ...fabledJSON.map(role => [role.id, role])
 | 
			
		||||
      ]);
 | 
			
		||||
      // update extraTravelers map to only show travelers not in this script
 | 
			
		||||
      state.otherTravelers = new Map(
 | 
			
		||||
        rolesJSON
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,8 +39,8 @@ module.exports = store => {
 | 
			
		|||
  }
 | 
			
		||||
  if (localStorage.fabled !== undefined) {
 | 
			
		||||
    store.commit("players/setFabled", {
 | 
			
		||||
      fabled: JSON.parse(localStorage.fabled).map(id =>
 | 
			
		||||
        store.state.fabled.get(id)
 | 
			
		||||
      fabled: JSON.parse(localStorage.fabled).map(
 | 
			
		||||
        fabled => store.state.fabled.get(fabled.id) || fabled
 | 
			
		||||
      )
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +127,11 @@ module.exports = store => {
 | 
			
		|||
      case "players/setFabled":
 | 
			
		||||
        localStorage.setItem(
 | 
			
		||||
          "fabled",
 | 
			
		||||
          JSON.stringify(state.players.fabled.map(({ id }) => id))
 | 
			
		||||
          JSON.stringify(
 | 
			
		||||
            state.players.fabled.map(fabled =>
 | 
			
		||||
              fabled.isCustom ? fabled : { id: fabled.id }
 | 
			
		||||
            )
 | 
			
		||||
          )
 | 
			
		||||
        );
 | 
			
		||||
        break;
 | 
			
		||||
      case "players/add":
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -272,7 +272,7 @@ class LiveSession {
 | 
			
		|||
        votingSpeed: session.votingSpeed,
 | 
			
		||||
        lockedVote: session.lockedVote,
 | 
			
		||||
        isVoteInProgress: session.isVoteInProgress,
 | 
			
		||||
        fabled: fabled.map(({ id }) => id),
 | 
			
		||||
        fabled: fabled.map(f => (f.isCustom ? f : { id: f.id })),
 | 
			
		||||
        ...(session.nomination ? { votes: session.votes } : {})
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -348,7 +348,7 @@ class LiveSession {
 | 
			
		|||
        isVoteInProgress
 | 
			
		||||
      });
 | 
			
		||||
      this._store.commit("players/setFabled", {
 | 
			
		||||
        fabled: fabled.map(id => this._store.state.fabled.get(id))
 | 
			
		||||
        fabled: fabled.map(f => this._store.state.fabled.get(f.id) || f)
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -407,7 +407,7 @@ class LiveSession {
 | 
			
		|||
    const { fabled } = this._store.state.players;
 | 
			
		||||
    this._send(
 | 
			
		||||
      "fabled",
 | 
			
		||||
      fabled.map(({ id }) => id)
 | 
			
		||||
      fabled.map(f => (f.isCustom ? f : { id: f.id }))
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -419,7 +419,7 @@ class LiveSession {
 | 
			
		|||
  _updateFabled(fabled) {
 | 
			
		||||
    if (!this._isSpectator) return;
 | 
			
		||||
    this._store.commit("players/setFabled", {
 | 
			
		||||
      fabled: fabled.map(id => this._store.state.fabled.get(id))
 | 
			
		||||
      fabled: fabled.map(f => this._store.state.fabled.get(f.id) || f)
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue