diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e915029 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/.vs diff --git a/README.md b/README.md index 526a301..2bb475d 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ -# All Goblins Have Names -[![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/jsabol/all-goblins-have-names/releases/latest&color=green)](https://github.com/jsabol/all-goblins-have-names/releases/latest) +# All Goblins Have Names Reborn +[![Version (latest)](https://img.shields.io/github/v/release/mnoreke/all-goblins-have-names)](https://github.com/mnoreke/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-reborn&colorB=4aa94a)](https://forge-vtt.com/bazaar#package=all-goblins-have-names-reborn) +[![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/mnoreke/all-goblins-have-names/releases/latest&color=green)](https://github.com/mnoreke/all-goblins-have-names/releases/latest) +A module for Foundry VTT. Allows you to use a rolltable as the Display Name for a token so each new +token will get a random name. It can also roll random biographies for tokens when used with [Better Rolltables](https://foundryvtt.com/packages/better-rolltables/). +## Reborn Status -A module for Foundry VTT. Allows you to use a table as the Display Name for a token so each new -token will get a random name. It can also roll random biographies for tokens when used with [Better Rolltables](https://foundryvtt.com/packages/better-rolltables/). +I picked this module up in December 2023 as it hadn't been updated in a while. I have it working for what I need for DnD5e. Anything else that doesn't work will be reviewd at as bug reports come in here on GitHub. ## How to use it First, grab your random name table and drag it into a Journal entry. That will give you some -text that looks similar to `@RollTable[2cbm3cP46dxcxO5Z]{Dwarf Female Name}`. Now, open the Actor -and click Prototype Token. Paste your `@RollTable[...` text into the Token Name. When you drag your Actor +text that looks similar to `@UUID[RollTable.OfTzIzGy7fyCgGz7]{Elf First Name}`. Now, open the Actor +and click Prototype Token. Paste your `@UUID[RollTable...` text into the Token Name. When you drag your Actor onto the map to create a new Token, its name will be randomized! +If you are working directly with a compendium, then the string will instead look like `@UUID[Compendium.monks-enhanced-journal.person-names.RollTable.AQBywLCajYDmBTay]{Dwarf Last Name}`. + ### Return more than one result for firstname + lastname When multiple lines are returned from a table, the lines will be joined together with a space. For example, you could have a roll table formula of 1d1, and have two results which are also tables for a firstname and a lastname, both with range 1-1. @@ -34,9 +38,9 @@ This module provides a few methods under the `game.allGoblinsHaveNames` namespac ## Installation -You can install this module through the Foundry module UI +You can install this module through the Foundry module UI. ## Get help -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. +You can [file an issue](https://github.com/mnoreke/all-goblins-have-names/issues/new) on github if +you're running into a bug or reach me on the Foundry VTT discord as Gandamir. diff --git a/module.json b/module.json index a24cd82..b3c0755 100644 --- a/module.json +++ b/module.json @@ -1,27 +1,31 @@ { - "name": "all-goblins-have-names", - "title": "All Goblins Have Names", - "description": "Use a table as the display name for a token. New tokens get a random name.", + "name": "all-goblins-have-names-reborn", + "title": "All Goblins Have Names Reborn", + "description": "Use a rolltable as the display name for a token. New tokens get a random name.", "authors": [ { - "name": "Tom Rodriguez", + "name": "Gandamir", + "discord": "gandamir" + }, + { + "name": "Tom Rodriguez (previous)", "discord": "toasty#8538" }, { - "name": "Cattegy", + "name": "Cattegy (previous)", "discord": "Cattegy#7436" } ], - "version": "2.0.1", - "minimumCoreVersion": "9.0", - "compatibleCoreVersion": "9.254", + "version": "3.0.0", + "minimumCoreVersion": "11.0", + "compatibleCoreVersion": "11.315", "esmodules": ["scripts/index.js"], - "url": "https://github.com/jsabol/all-goblins-have-names", + "url": "https://github.com/mnoreke/all-goblins-have-names", "readme": "README.md", "license": "LICENSE", - "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", + "bugs": "https://github.com/mnoreke/all-goblins-have-names/issues", + "download": "https://github.com/mnoreke/all-goblins-have-names/releases/latest/download/module.zip", + "manifest": "https://github.com/mnoreke/all-goblins-have-names/releases/latest/download/module.json", "flags": { "allowBugReporter": true } diff --git a/scripts/better-table-util.js b/scripts/better-table-util.js index 4305682..2b29ca6 100644 --- a/scripts/better-table-util.js +++ b/scripts/better-table-util.js @@ -1,25 +1,25 @@ export function isBetterTable(table) { - return ( - table.data.flags && - table.data.flags["better-rolltables"] && - table.data.flags["better-rolltables"]["table-type"] == "better" - ); + return ( + table.data.flags && + table.data.flags["better-rolltables"] && + table.data.flags["better-rolltables"]["table-type"] == "better" + ); } export function isStoryTable(table) { - return ( - table.data.flags && - table.data.flags["better-rolltables"] && - table.data.flags["better-rolltables"]["table-type"] == "story" - ); + return ( + table.data.flags && + table.data.flags["better-rolltables"] && + table.data.flags["better-rolltables"]["table-type"] == "story" + ); } export async function rollBetterTable(table) { - const result = await game.betterTables.getBetterTableResults(table); - return result; + const result = await game.betterTables.getBetterTableResults(table); + return result; } export async function rollStoryTable(table) { - const result = await game.betterTables.getStoryResults(table); - return result.storyHtml; + const result = await game.betterTables.getStoryResults(table); + return result.storyHtml; } diff --git a/scripts/index.js b/scripts/index.js index 7bc758c..3bef219 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -2,29 +2,29 @@ import { isWorldTable, isCompendiumTable, rollTable } from "./table-utils.js"; // 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); + /** + * 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); + } } - } - - /** - * 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; + + /** + * 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); } - return isWorldTable(tableStr) - ? getRollTableResult(tableStr) - : getCompendiumTableResult(tableStr); - } } /** @@ -33,16 +33,17 @@ class AllGoblinsHaveNames { * @resturn {Array.} the result of the roll */ async function getRollTableResult(displayName) { - // get the table by its ID - const endIndex = displayName.indexOf("]"); - const table_id = displayName.substring(11, endIndex); - const table = game.tables.contents.find((t) => t.data._id == table_id); - - if (table) { - return await rollTable(table); - } else { - ui.notifications.error("Can't find a table that matches " + displayName); - } + // @UUID[RollTable.OfTzIzGy7fyCgGz7]{Elf First Name} + // get the table by its ID + const endIndex = displayName.indexOf("]"); + const table_id = displayName.substring(16, endIndex); + const table = game.tables.contents.find((t) => t.data._id == table_id); + + if (table) { + return await rollTable(table); + } else { + ui.notifications.error("Can't find a table that matches " + displayName); + } } /** @@ -51,39 +52,41 @@ async function getRollTableResult(displayName) { * @returns {Promise} */ async function getCompendiumTableResult(displayName) { - // get the identifier - const endIndex = displayName.indexOf("]"); - const idParts = displayName.substring(12, endIndex).split("."); - - // sanity check that it matches the expected format - if (idParts.length !== 3) { - throw new Error( - `Expected format to match @Compendium[module.table.id] Got: ${displayName}` - ); - } - - // get pack - const packId = `${idParts[0]}.${idParts[1]}`; - const pack = game.packs.get(packId); - if (!pack) { - throw new Error(`Couldn't find a compendium with id ${packId}`); - } - - // get table - const table = await pack.getDocument(idParts[2]); - - if (!table) { - throw new Error( - `Couldn't find table with id ${idParts[2]} in pack ${packId}` - ); - } - - // check if is better table - const results = await rollTable(table); - if (!results || !results.length) { - throw new Error(`Couldn't roll table id ${idParts[2]} in pack ${packId}`); - } - return results; + // get the identifier + const endIndex = displayName.indexOf("]"); + const idParts = displayName.substring(6, endIndex).split("."); + + // @UUID[Compendium.monks-enhanced-journal.person-names.RollTable.AQBywLCajYDmBTay]{Dwarf Last Name} + + // sanity check that it matches the expected format + if (idParts.length !== 5) { + throw new Error( + `Expected format to match @UUID[Compendium.module.table.Rolltable.id] Got: ${displayName}` + ); + } + + // get pack + const packId = `${idParts[1]}.${idParts[2]}`; + const pack = game.packs.get(packId); + if (!pack) { + throw new Error(`Couldn't find a compendium with id ${packId}`); + } + + // get table + const table = await pack.getDocument(idParts[4]); + + if (!table) { + throw new Error( + `Couldn't find table with id ${idParts[4]} in pack ${packId}` + ); + } + + // check if is better table + const results = await rollTable(table); + if (!results || !results.length) { + throw new Error(`Couldn't roll table id ${idParts[4]} in pack ${packId}`); + } + return results; } /** @@ -91,42 +94,43 @@ async function getCompendiumTableResult(displayName) { * @param {TokenData} tokenData */ 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"; - } - // 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"; + const displayName = tokenData.name.trim(); + let nameTableStr, bioDataPath, bioTableStr; + if (isWorldTable(displayName) || isCompendiumTable(displayName)) { + nameTableStr = displayName; } - // 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; + // 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"; + } + // 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 }; + return { nameTableStr, bioDataPath, bioTableStr }; } /** @@ -135,26 +139,26 @@ function mineForTableStrings(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 - ); + 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); } - - // bio - if (bioTableStr) { - result.bio = await game.allGoblinsHaveNames.rollFromTableString( - bioTableStr - ); - } - - return result; - } catch (e) { - console.warn("[All Goblins Have Names]: " + e.message); - } } /** @@ -163,11 +167,11 @@ async function getNewRolledValues({ nameTableStr, bioTableStr, bioDataPath }) { * @param {object} result */ function saveRolledValues(tokenDocument, result) { - // do the update - tokenDocument.update({ - name: result.name, - }); - tokenDocument.actor.update({ [result.bioDataPath]: result.bio }); + // do the update + tokenDocument.update({ + name: result.name, + }); + tokenDocument.actor.update({ [result.bioDataPath]: result.bio }); } /** @@ -176,28 +180,28 @@ function saveRolledValues(tokenDocument, result) { */ Hooks.once("init", () => { - // add the API - game.allGoblinsHaveNames = new AllGoblinsHaveNames(); + // 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; - } + /** + * @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 = " "; + // 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); + // do the roll + const result = await getNewRolledValues(toRoll); - saveRolledValues(tokenDocument, result); - }); + saveRolledValues(tokenDocument, result); + }); }); diff --git a/scripts/table-utils.js b/scripts/table-utils.js index 8ad7d76..9b4b000 100644 --- a/scripts/table-utils.js +++ b/scripts/table-utils.js @@ -1,27 +1,27 @@ import { - isBetterTable, - isStoryTable, - rollBetterTable, - rollStoryTable, +isBetterTable, +isStoryTable, +rollBetterTable, +rollStoryTable, } from "./better-table-util.js"; export function isWorldTable(str) { - return str.startsWith("@RollTable["); + return str.startsWith("@UUID[RollTable"); } export function isCompendiumTable(str) { - return str.startsWith("@Compendium["); + return str.startsWith("@UUID[Compendium"); } export async function rollTable(table) { - if (isBetterTable(table)) { - return joinResults(await rollBetterTable(table)); - } else if (isStoryTable(table)) { - return await rollStoryTable(table); - } else { - let results = await table.roll(); - return joinResults(results['results']); - } + if (isBetterTable(table)) { + return joinResults(await rollBetterTable(table)); + } else if (isStoryTable(table)) { + return await rollStoryTable(table); + } else { + let results = await table.roll(); + return joinResults(results['results']); + } } /** @@ -29,5 +29,5 @@ export async function rollTable(table) { * @param {Array.} results */ function joinResults(results) { - return results.map((r) => r.data.text).join(" "); + return results.map((r) => r.data.text).join(" "); }