Skip to content

Extensibility

Ben Straub edited this page Aug 29, 2024 · 10 revisions

Moves have two ways to customize them, and oracles provide three. Assets only provide one.

Using Folders

The system will look for folders using localized names; check the lang folder for your language, and search the file for "Custom Oracles". In English, these names are:

  • "Custom Oracles" in the Roll Tables tab
  • "Custom Moves" and "Custom Assets" in the Items tab

If any of these folders are found, the system will include its contents in the moves/oracles/assets area of the sheet:

 

Only top-level items will be shown for moves and assets, but oracles will import the entire tree.

Using Hooks

Note This method is a bit brittle. Prefer the registration method below if at all possible.

This method is more useful for modules, and involves handling the ironswornOracles hook. The type of object in the tree is shown in customoracles.ts. Here's a simple hook that adds more rows to the "Background Assets" oracle (when using the Starforged oracle set):

const extraAssetsTableUuid = await game.tables.get('MSjHr7AahxxJXAqe').uuid
Hooks.on('ironswornOracles', (root) => {
	root.children[0].children[0].tables.push(extraAssetsTableUuid)
})

Another example, which renames "Oracle 14: Elf Names" to "XYZ" (when using the Ironsworn oracle set):

Hooks.on('ironswornOracles', x => x.children[4].children[1].displayName = 'XYZ')

Here is a helper you can use to search the tree for an oracle by name, instead of navigating by array indexes.

function findOracleByName(node, searchName) {
	if (node.displayName === searchName) return node

	for (const child of node.children) {
		const childResult = findOracleByName(child, searchName)
		if (childResult) return childResult
	}
}

There is also an ironswornMoves hook, which passes the final tree before display. See custommoves.ts for the structure of those objects.

Installing Hooks

In order to install these hooks as a GM or player, do this:

  1. Write a macro called "Ironsworn Startup", change its type to "script", and add a Hooks.on(...) invocation like above
  2. Change that macro's permissions so that all players have at least "Limited" rights
  3. Reload your browser window to make sure the macro is invoked.

Using registration

Another way of customizing oracles, starting with 1.21.0, is custom registration. The oracle tree is computed on startup, and can be fetched using CONFIG.IRONSWORN.getOracleTree('classic') (other options are 'delve', 'starforged', and 'sundered_isles'). The ironswornOracleTreesReady hook will fire when the default tree is ready, and you can fetch that tree, modify it, and send it to CONFIG.IRONSWORN.registerOracleTree to update the base tree of oracles that's used in every oracle tool in the game. Here's a sample hook body:

Hooks.on('ironswornOracleTreesReady', () => {
	const ironswornOracles = CONFIG.IRONSWORN.getOracleTree('classic')
	ironswornOracles.children[0].children.push({
		displayName: 'Philosophy',
		tables: ['uuid-of-philosophy-table'],
		children: []
	})
	CONFIG.IRONSWORN.registerOracleTree('classic', ironswornOracles)
})

One thing to note is that Foundry's hooks are synchronous – they won't await your callback function. That means that if your callback is using promises, Foundry might fire off another one while you're awaiting or thening, and the results could be unexpected. Your best bet right now is to do all your asynchronous processing before the hook is called, perhaps on an init or ready hook, and only do synchronous code inside the callback body. (If this ends up being a problem for you, leave us an issue, and we can see about doing an async hooks method.)