diff --git a/README.md b/README.md index fa14106..526a301 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # All Goblins Have Names -[![Version (latest)](https://img.shields.io/github/v/release/toastygm/all-goblins-have-names)](https://github.com/toastygm/all-goblins-have-names/releases/latest) +[![Version (latest)](https://img.shields.io/github/v/release/jsabol/all-goblins-have-names)](https://github.com/jsabol/all-goblins-have-names/releases/latest) [![Forge Installs](https://img.shields.io/badge/dynamic/json?label=Forge%20Installs&query=package.installs&suffix=%25&url=https%3A%2F%2Fforge-vtt.com%2Fapi%2Fbazaar%2Fpackage%2Fall-goblins-have-names&colorB=4aa94a)](https://forge-vtt.com/bazaar#package=all-goblins-have-names) -[![GitHub downloads (latest)](https://img.shields.io/badge/dynamic/json?label=Downloads@latest&query=assets[?(@.name.includes('zip'))].download_count&url=https://api.github.com/repos/toastygm/all-goblins-have-names/releases/latest&color=green)](https://github.com/toastygm/all-goblins-have-names/releases/latest) +[![GitHub downloads (latest)](https://img.shields.io/badge/dynamic/json?label=Downloads@latest&query=assets[?(@.name.includes('zip'))].download_count&url=https://api.github.com/repos/jsabol/all-goblins-have-names/releases/latest&color=green)](https://github.com/jsabol/all-goblins-have-names/releases/latest) @@ -23,12 +23,14 @@ When multiple lines are returned from a table, the lines will be joined together ## Random biographies with Better Rolltables -If you install the module [Better Rolltables](https://foundryvtt.com/packages/better-rolltables/), you can also generate random biographies. Create a Story Table, then place a reference to it as the first line of the biography. For example, you can use the Random NPC Story Table that comes with the BetterTables mod: `@Compendium[better-rolltables.brt-story.8vjHa6SoZibxeTke]{Random NPC}`. This currently supports the Simple Worldbuilding system, D&D 5e, HarnMaster, or any system with the data formats of... +Story tables are currently bugged in Better Rolltables. When this changes, we will update this README with instructions on how to randomize biographies. -- `actor.data.data.biography` (Simple Worldbuilding) -- `actor.data.data.details.biography.value` (D&D 5e) +## API -Note: This will only work for tokens with "Link Actor Data" unchecked. +This module provides a few methods under the `game.allGoblinsHaveNames` namespace for use in macros or other modules. + +- `game.allGoblinsHaveNames.rerollSelectedTokens()` - Re-rolls the names of all selected tokens. +- `game.allGoblinsHaveNames.rollFromTableString(tableStr)` - Takes a string like `@RollTable[...` or `@Compendium[...` and returns a random result from that table. ## Installation @@ -36,5 +38,5 @@ You can install this module through the Foundry module UI ## Get help -You can [file an issue](https://github.com/toastygm/all-goblins-have-names/issues/new) on github if -you're running into a bug or reach me on the Foundry VTT discord as toasty#8538. +You can [file an issue](https://github.com/jsabol/all-goblins-have-names/issues/new) on github if +you're running into a bug or reach me on the Foundry VTT discord as Cattegy#7436. diff --git a/module.json b/module.json index 0ed2fbf..3a8c989 100644 --- a/module.json +++ b/module.json @@ -8,19 +8,20 @@ "discord": "toasty#8538" }, { - "name": "Cattegy" + "name": "Cattegy", + "discord": "Cattegy#7436" } ], "version": "1.3.0", "minimumCoreVersion": "9.0", - "compatibleCoreVersion": "9.249", + "compatibleCoreVersion": "9.254", "esmodules": ["scripts/index.js"], - "url": "https://github.com/toastygm/all-goblins-have-names", + "url": "https://github.com/jsabol/all-goblins-have-names", "readme": "README.md", "license": "LICENSE", - "bugs": "https://github.com/toastygm/all-goblins-have-names/issues", - "download": "https://github.com/toastygm/all-goblins-have-names/releases/latest/download/module.zip", - "manifest": "https://github.com/toastygm/all-goblins-have-names/releases/latest/download/module.json", + "bugs": "https://github.com/jsabol/all-goblins-have-names/issues", + "download": "https://github.com/jsabol/all-goblins-have-names/releases/latest/download/module.zip", + "manifest": "https://github.com/jsabol/all-goblins-have-names/releases/latest/download/module.json", "flags": { "allowBugReporter": true } diff --git a/scripts/index.js b/scripts/index.js index 55eb08c..7bc758c 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -1,13 +1,30 @@ import { isWorldTable, isCompendiumTable, rollTable } from "./table-utils.js"; -function accessNestedRef(obj = {}, str = "", setValue) { - const parts = str.split("."); - return parts.reduce((o, key, i) => { - if (setValue && i == parts.length - 1) { - o[key] = setValue; +// API interface for the module +class AllGoblinsHaveNames { + /** + * rerolls the name and bio of all selected tokens + */ + async rerollSelectedTokens() { + for (let token of canvas.tokens.controlled) { + const result = await getNewRolledValues(token.actor.data.token); + saveRolledValues(token.document, result); } - return o[key]; - }, obj); + } + + /** + * takes a string referencing a table and rolls + * @param {string} tableStr (like @Compendium[id]{name}) + */ + rollFromTableString(tableStr) { + // not a valid table string, just return the input + if (!isWorldTable(tableStr) && !isCompendiumTable(tableStr)) { + return tableStr; + } + return isWorldTable(tableStr) + ? getRollTableResult(tableStr) + : getCompendiumTableResult(tableStr); + } } /** @@ -70,90 +87,117 @@ async function getCompendiumTableResult(displayName) { } /** - * ------------------------------------------------------------------------------ - * Initialize the All Goblins Have NAmes module + * Searches for tables in the name field and biogrpahy + * @param {TokenData} tokenData */ -Hooks.on("ready", () => { - function prepareTokenFlags(tokenData) { - const displayName = tokenData.name.trim(); - if (isWorldTable(displayName) || isCompendiumTable(displayName)) { - // clear token name so we don't display software gore to the user - tokenData.name = " "; - // temporarily put it here so we can access it in our async function - tokenData.flags._AGHN_nameTable = displayName; +function mineForTableStrings(tokenData) { + const displayName = tokenData.name.trim(); + let nameTableStr, bioDataPath, bioTableStr; + if (isWorldTable(displayName) || isCompendiumTable(displayName)) { + nameTableStr = displayName; + } + // Mine biography for tables + const actorId = tokenData.actorId || tokenData.document.id; + if (!tokenData.actorLink && actorId) { + let actor = game.actors.get(actorId); + let actorData = actor.data.data; + + let bio; + // structure of simple worldbuilding system + if (actorData.biography) { + bio = actorData.biography; + bioDataPath = "data.biography"; } - // Mine biography for tables - if (!tokenData.actorLink && tokenData.actorId) { - let actor = game.actors.get(tokenData.actorId); - let data = actor.data.data; - - let bio; - // structure of simple worldbuilding system - if (data.biography) { - bio = data.biography; - tokenData.flags._AGHN_bioDataPath = "data.biography"; - } - // structure of D&D 5e NPCs and PCs - else if ( - data.details && - data.details.biography && - data.details.biography.value - ) { - bio = data.details.biography.value; - tokenData.flags._AGHN_bioDataPath = "data.details.biography.value"; - } - - // get text out of bio - let el = document.createElement("div"); - el.innerHTML = bio; - let bioText = el.innerText.trim(); - if (isWorldTable(bioText) || isCompendiumTable(bioText)) { - tokenData.flags._AGHN_bioTable = bioText; - } + // structure of D&D 5e NPCs and PCs + else if ( + actorData.details && + actorData.details.biography && + actorData.details.biography.value + ) { + bio = actorData.details.biography.value; + bioDataPath = "data.details.biography.value"; + } + + // get text out of bio + let el = document.createElement("div"); + el.innerHTML = bio; + let bioText = el.innerText.trim(); + if (isWorldTable(bioText) || isCompendiumTable(bioText)) { + bioTableStr = bioText; + } + } + return { nameTableStr, bioDataPath, bioTableStr }; +} + +/** + * Rolls for new values + * @param {TokenData} tokenData + * @returns {Promise} resolves to an object with name and bio. + */ +async function getNewRolledValues({ nameTableStr, bioTableStr, bioDataPath }) { + try { + let result = { bioDataPath }; + // name + if (nameTableStr) { + result.name = await game.allGoblinsHaveNames.rollFromTableString( + nameTableStr + ); + } + + // bio + if (bioTableStr) { + result.bio = await game.allGoblinsHaveNames.rollFromTableString( + bioTableStr + ); } + + return result; + } catch (e) { + console.warn("[All Goblins Have Names]: " + e.message); } +} - // Creating the token - Hooks.on("createToken", async (tokenDocument, scene) => { - // pick up the temporary flags we set in preCreateToken - const tokenData = tokenDocument.data; - prepareTokenFlags(tokenData); - - const tableStr = tokenData.flags._AGHN_nameTable; - const bioTableStr = tokenData.flags._AGHN_bioTable; - const bioDataPath = tokenData.flags._AGHN_bioDataPath; - // bail early if there is nothing relevant here - if (!tableStr && !bioTableStr) return; - // clean up our temporary storage - delete tokenData.flags._AGHN_nameTable; - delete tokenData.flags._AGHN_bioTable; - delete tokenData.flags._AGHN_bioDataPath; - - try { - // name - if (tableStr) { - let resultPromise = isWorldTable(tableStr) - ? getRollTableResult(tableStr) - : getCompendiumTableResult(tableStr); - - resultPromise.then((result) => { - // token.update will be deprecated in 0.9. - tokenDocument.update({ name: result }); - }); - } - - // bio - if (bioTableStr) { - let resultPromise = isWorldTable(bioTableStr) - ? getRollTableResult(bioTableStr) - : getCompendiumTableResult(bioTableStr); - - resultPromise.then((result) => { - accessNestedRef(tokenDocument.actor.data, bioDataPath, result); - }); - } - } catch (e) { - console.warn("[All Goblins Have Names]: " + e.message); +/** + * Saves the result from getNewRolledValues to the token + * @param {TokenDocument} tokenDocument + * @param {object} result + */ +function saveRolledValues(tokenDocument, result) { + // do the update + tokenDocument.update({ + name: result.name, + }); + tokenDocument.actor.update({ [result.bioDataPath]: result.bio }); +} + +/** + * ------------------------------------------------------------------------------ + * Initialize the All Goblins Have Names module + */ + +Hooks.once("init", () => { + // add the API + game.allGoblinsHaveNames = new AllGoblinsHaveNames(); +}); + +Hooks.on("ready", () => { + /** + * @param {TokenDocument} tokenDocument + */ + Hooks.on("createToken", async (tokenDocument) => { + const toRoll = mineForTableStrings(tokenDocument.data); + + // bail if there is no table strings to roll on + if (!toRoll.nameTableStr && !toRoll.bioTableStr) { + return; } + + // clear token name so we don't display software gore to the user while async is running + tokenDocument.data.name = " "; + + // do the roll + const result = await getNewRolledValues(toRoll); + + saveRolledValues(tokenDocument, result); }); });