Skip to content

Commit

Permalink
Upgrade to 9.x and add API (#10)
Browse files Browse the repository at this point in the history
* Fix biography rolling, add API

* Fix script for tokens with no table string

* Update to take back over published repo

* Document API

* Sanity check API

* Update to 9.254

* Remove instructions for random biograhies
  • Loading branch information
jsabol authored Mar 5, 2022
1 parent d33284f commit 65fecfa
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 100 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)



Expand All @@ -23,18 +23,20 @@ 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

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.
13 changes: 7 additions & 6 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
216 changes: 130 additions & 86 deletions scripts/index.js
Original file line number Diff line number Diff line change
@@ -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);
}
}

/**
Expand Down Expand Up @@ -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);
});
});

0 comments on commit 65fecfa

Please sign in to comment.