diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4278c32..daeea4224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## In progress - Add the "path" to the rolled oracle to the chat card output ([#329](https://github.com/ben/foundry-ironsworn/pull/329)) +- Allow custom oracles ([#330](https://github.com/ben/foundry-ironsworn/pull/330)) ## 1.10.57 diff --git a/README.md b/README.md index ffcd3bf25..2e0fc8657 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,15 @@ Assets are in their own compendium, drag them onto your character sheet. Now you're ready to create vows, embark on journeys, and slay beasts. Keep track of your story using journal entries. +## Extensibility + +**Custom oracles** can be added in one of two ways. +The first is by creating a table folder called "Custom Oracles" (this name may be translated). +Whatever folder and table structure is found there will be mirrored into the oracle sidebar. + +The second is more useful for modules, and involves intercepting the `ironswornOracles` hook. +The type of object in the tree is shown in `customoracles.ts`. + ## How to hack on this 1. Install Foundry 0.8.8 or later, and start it up. diff --git a/src/module/features/customoracles.ts b/src/module/features/customoracles.ts new file mode 100644 index 000000000..fa0e96960 --- /dev/null +++ b/src/module/features/customoracles.ts @@ -0,0 +1,111 @@ +import { starforged, IOracle, IOracleCategory } from 'dataforged' +import { getFoundryTableByDfId } from '../dataforged' + +export interface OracleTreeNode { + dataforgedNode?: IOracle | IOracleCategory + table?: RollTable + displayName: string + children: OracleTreeNode[] +} + +const emptyNode = () => + ({ + displayName: '', + children: [], + } as OracleTreeNode) + +export async function createStarforgedOracleTree(): Promise { + const rootNode = emptyNode() + + // Make sure the compendium is loaded + const pack = game.packs.get('foundry-ironsworn.starforgedoracles') + await pack?.getDocuments() + + // Build the default tree + for (const category of starforged.oracles) { + rootNode.children.push(await walkOracleCategory(category)) + } + + // TODO: Add in custom oracles from a well-known directory + await augmentWithFolderContents(rootNode) + + // Fire the hook and allow extensions to modify the tree + await Hooks.call('ironswornOracles', rootNode) + + return rootNode +} + +async function walkOracleCategory(cat: IOracleCategory): Promise { + const node: OracleTreeNode = { + ...emptyNode(), + dataforgedNode: cat, + displayName: game.i18n.localize(`IRONSWORN.SFOracleCategories.${cat.Display.Title}`), + } + + for (const childCat of cat.Categories ?? []) node.children.push(await walkOracleCategory(childCat)) + for (const oracle of cat.Oracles ?? []) node.children.push(await walkOracle(oracle)) + + return node +} + +async function walkOracle(oracle: IOracle): Promise { + const table = await getFoundryTableByDfId(oracle.$id) + + const node: OracleTreeNode = { + ...emptyNode(), + dataforgedNode: oracle, + table, + displayName: table?.name || game.i18n.localize(`IRONSWORN.SFOracleCategories.${oracle.Display.Title}`), + } + + for (const childOracle of oracle.Oracles ?? []) node.children.push(await walkOracle(childOracle)) + + return node +} + +async function augmentWithFolderContents(node:OracleTreeNode) { + const name = game.i18n.localize('IRONSWORN.Custom Oracles') + const folder = game.tables?.directory?.folders.find(x => x.name === name) + if (!folder) return + + function walkFolder(parent:OracleTreeNode, folder: Folder) { + // Add this folder + const newNode:OracleTreeNode = { + ...emptyNode(), + displayName: folder.name || '(folder)' + } + parent.children.push(newNode) + + // Add its folder children + for (const sub of folder.getSubfolders()) { + walkFolder(newNode, sub) + } + + // Add its table children + for (const table of folder.contents) { + newNode.children.push({ + ...emptyNode(), + table, + displayName: table.name ?? '(table)', + }) + } + } + + walkFolder(node, folder) +} + +export function findPathToNodeByTableId(rootNode: OracleTreeNode, tableId: string): OracleTreeNode[] { + const ret: OracleTreeNode[] = [] + function walk(node:OracleTreeNode) { + ret.push(node) + if (node.table?.id === tableId) return true + for (const child of node.children) { + if (walk(child)) return true + } + ret.pop() + return false + } + + walk(rootNode) + return ret +} diff --git a/src/module/vue/components/oracletree-node.vue b/src/module/vue/components/oracletree-node.vue index fadae78b8..6a87f2ea3 100644 --- a/src/module/vue/components/oracletree-node.vue +++ b/src/module/vue/components/oracletree-node.vue @@ -2,14 +2,14 @@
-
+

- {{ name }} + {{ node.displayName }} @@ -38,16 +38,16 @@ - {{ name }} + {{ node.displayName }}

x.matchesSearch) }, @@ -101,31 +96,24 @@ export default { return this.manuallyExpanded || !!this.searchQuery }, - name() { - return ( - this.oracle.foundryTable?.name ?? - this.$t(`IRONSWORN.SFOracleCategories.${this.oracle.Display.Title}`) - ) - }, - matchesSearch() { const re = new RegExp(this.searchQuery, 'i') - return re.test(this.name) + return re.test(this.node.displayName) }, hidden() { if (!this.searchQuery) return false return !( - this.matchesSearch || // This matches - this.parentMatchesSearch || // Parent matches + this.matchesSearch || + this.parentMatchesSearch || this.childMatchesSearch ) }, tablePreview() { - const description = this.oracle.foundryTable.data.description || '' + const description = this.node.table.data.description || '' const tableRows = CONFIG.IRONSWORN._.sortBy( - this.oracle.foundryTable.data.results.contents.map((x) => ({ + this.node.table.data.results.contents.map((x) => ({ low: x.data.range[0], high: x.data.range[1], text: x.data.text, @@ -144,16 +132,8 @@ export default { }, methods: { - click() { - if (this.oracle.foundryTable) { - CONFIG.IRONSWORN.rollAndDisplayOracleResult(this.oracle.foundryTable) - } else { - this.manuallyExpanded = !this.manuallyExpanded - } - }, - rollOracle() { - CONFIG.IRONSWORN.rollAndDisplayOracleResult(this.oracle.foundryTable) + CONFIG.IRONSWORN.rollAndDisplayOracleResult(this.node.table) }, moveclick(item) { diff --git a/src/module/vue/components/sf-movesheetoracles.vue b/src/module/vue/components/sf-movesheetoracles.vue index c88fc7e8a..7a634905a 100644 --- a/src/module/vue/components/sf-movesheetoracles.vue +++ b/src/module/vue/components/sf-movesheetoracles.vue @@ -20,10 +20,10 @@
@@ -38,7 +38,7 @@