diff --git a/CHANGELOG.md b/CHANGELOG.md index 70a04bd..9fc86f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index f4587c4..e67ae5b 100644 --- a/README.md +++ b/README.md @@ -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`
+ _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) diff --git a/package-lock.json b/package-lock.json index f4661ef..e227db4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } diff --git a/package.json b/package.json index 9e2878c..c53f899 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/assets/icons/fabled.png b/src/assets/icons/fabled.png new file mode 100644 index 0000000..cf11af2 Binary files /dev/null and b/src/assets/icons/fabled.png differ diff --git a/src/store/index.js b/src/store/index.js index 06a03be..08d8ddb 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -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 diff --git a/src/store/persistence.js b/src/store/persistence.js index 55c2b78..5660db7 100644 --- a/src/store/persistence.js +++ b/src/store/persistence.js @@ -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": diff --git a/src/store/socket.js b/src/store/socket.js index e193a08..1537fcc 100644 --- a/src/store/socket.js +++ b/src/store/socket.js @@ -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) }); }