diff --git a/src/config.ts b/src/config.ts index a1f2a8b73..2772fa6f5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,7 +2,7 @@ import { IronswornActor } from './module/actor/actor' import * as dataforgedHelpers from './module/dataforged' import { importFromDatasworn } from './module/datasworn' // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { starforged, ironsworn } from 'dataforged' +import { starforged } from 'dataforged' import type { Emitter, EventType } from 'mitt' import Mitt from 'mitt' import { @@ -13,7 +13,6 @@ import { import { AssetCompendiumBrowser } from './module/item/asset-compendium-browser' import { FirstStartDialog } from './module/applications/firstStartDialog' import { SFSettingTruthsDialogVue } from './module/applications/vueSfSettingTruthsDialog' -import { WorldTruthsDialog } from './module/applications/worldTruthsDialog' import { OracleWindow } from './module/applications/oracle-window' import { getOracleTree, @@ -39,7 +38,6 @@ export interface IronswornConfig { applications: { // Dialogs FirstStartDialog: typeof FirstStartDialog - ISSettingTruthsDialog: typeof WorldTruthsDialog SFSettingTruthsDialog: typeof SFSettingTruthsDialogVue AssetCompendiumBrowser: typeof AssetCompendiumBrowser OracleWindow: typeof OracleWindow @@ -75,7 +73,6 @@ export const IRONSWORN: IronswornConfig = { applications: { FirstStartDialog, - ISSettingTruthsDialog: WorldTruthsDialog, SFSettingTruthsDialog: SFSettingTruthsDialogVue, AssetCompendiumBrowser, OracleWindow, diff --git a/src/module/applications/firstStartDialog.ts b/src/module/applications/firstStartDialog.ts index 039fd0328..624e95db1 100644 --- a/src/module/applications/firstStartDialog.ts +++ b/src/module/applications/firstStartDialog.ts @@ -1,6 +1,6 @@ +import { registerDefaultOracleTrees } from '../features/customoracles' import { IronswornSettings, RULESETS } from '../helpers/settings' import { SFSettingTruthsDialogVue } from './vueSfSettingTruthsDialog' -import { WorldTruthsDialog } from './worldTruthsDialog' export class FirstStartDialog extends FormApplication { constructor() { @@ -16,7 +16,7 @@ export class FirstStartDialog extends FormApplication { resizable: false, classes: ['ironsworn', 'sheet', 'first-start'], width: 650, - height: 560 + height: 685 } as FormApplicationOptions) } @@ -28,8 +28,6 @@ export class FirstStartDialog extends FormApplication { super.activateListeners(html) // eslint-disable-next-line @typescript-eslint/no-misused-promises html.find('button.ironsworn__save').on('click', async (ev) => { - console.log(html) - // Update default character sheet const defaultSheet = html.find('input[name=sheet]:checked').val() const setting = game.settings.get('core', 'sheetClasses') @@ -45,11 +43,27 @@ export class FirstStartDialog extends FormApplication { ) await IronswornSettings.enableOnlyRulesets(...checkedRulesets) + // If you chose SI, probably you want 'hold' enabled + await IronswornSettings.set( + 'character-hold', + checkedRulesets.includes('sundered_isles') + ) + + // Update the live content + void registerDefaultOracleTrees() + + // Launch truths dialog + const truthsFlavor = html.find('input[name=truths]:checked').val() + if (truthsFlavor) { + // @ts-expect-error coercing this string to a DataswornRulesetKey + void new SFSettingTruthsDialogVue(truthsFlavor).render(true) + } + if (IronswornSettings.get('show-first-start-dialog')) { if (defaultSheet === 'StarforgedCharacterSheet') { - void new SFSettingTruthsDialogVue().render(true) + void new SFSettingTruthsDialogVue('starforged').render(true) } else { - void new WorldTruthsDialog().render(true) + void new SFSettingTruthsDialogVue('classic').render(true) } } await IronswornSettings.set('show-first-start-dialog', false) diff --git a/src/module/applications/vueSfSettingTruthsDialog.ts b/src/module/applications/vueSfSettingTruthsDialog.ts index 44fb6061f..1f396eac7 100644 --- a/src/module/applications/vueSfSettingTruthsDialog.ts +++ b/src/module/applications/vueSfSettingTruthsDialog.ts @@ -1,11 +1,17 @@ -import type { Starforged } from 'dataforged' -import { starforged } from 'dataforged' +import { DataswornTree } from '../datasworn2' +import { DataswornRulesetKey } from '../helpers/settings' import { IronswornJournalEntry } from '../journal/journal-entry' import sfTruthsVue from '../vue/sf-truths.vue' import { VueAppMixin } from '../vue/vueapp.js' +const DS_TRUTH_COMPENDIUM_KEYS = { + classic: 'foundry-ironsworn.ironsworntruths', + starforged: 'foundry-ironsworn.starforgedtruths', + sundered_isles: 'foundry-ironsworn.sunderedislestruths' +} + export class SFSettingTruthsDialogVue extends VueAppMixin(FormApplication) { - constructor() { + constructor(protected truthset: DataswornRulesetKey) { super({}) } @@ -33,26 +39,26 @@ export class SFSettingTruthsDialogVue extends VueAppMixin(FormApplication) { getData( options?: Partial | undefined ): MaybePromise - async getData(options?: unknown) { - const pack = game.packs.get('foundry-ironsworn.starforgedtruths') + async getData(_options?: unknown) { + const pack = game.packs.get(DS_TRUTH_COMPENDIUM_KEYS[this.truthset]) const documents = (await pack?.getDocuments()) as IronswornJournalEntry[] if (!documents) throw new Error("can't load truth JEs") - // Avoid rollupjs's over-aggressive tree shaking - const dfTruths = ((starforged as any).default as Starforged)[ - 'Setting Truths' - ] - const truths = dfTruths.map((df) => ({ - df, + // Get the order from DS + const dsTruths = DataswornTree.get(this.truthset)?.truths + if (!dsTruths) throw new Error("can't find DS truths") + + const truths = Object.values(dsTruths).map((ds) => ({ + ds, je: documents.find( - (x) => x.getFlag('foundry-ironsworn', 'dfid') === df.$id + (x) => x.getFlag('foundry-ironsworn', 'dsid') === ds._id ) })) return { ...(await super.getData()), - truths: truths.map(({ df, je }) => ({ - df, + truths: truths.map(({ ds, je }) => ({ + ds, je: () => je // Prevent vue from wrapping this in Reactive })) } diff --git a/src/module/applications/worldTruthsDialog.ts b/src/module/applications/worldTruthsDialog.ts deleted file mode 100644 index 829ccb7ce..000000000 --- a/src/module/applications/worldTruthsDialog.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { IronswornSettings } from '../helpers/settings' -import { IronswornJournalEntry } from '../journal/journal-entry' -import { IronswornJournalPage } from '../journal/journal-entry-page' - -export class WorldTruthsDialog extends FormApplication { - constructor() { - super({}) - } - - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - title: game.i18n.localize('IRONSWORN.YourWorldTruths'), - template: 'systems/foundry-ironsworn/templates/truths.hbs', - id: 'world-truths-dialog', - resizable: true, - classes: ['ironsworn', 'sheet', 'world-truths'], - width: 600, - height: 700 - } as FormApplicationOptions) - } - - async _updateObject() { - // Nothing to do - } - - async getData() { - const truths = await fetch( - 'systems/foundry-ironsworn/assets/world-truths.json' - ).then(async (x) => await x.json()) - - // Run truths text through I18n - for (const category of truths.Categories) { - for (let i = 0; i < category.Options.length; i++) { - const option = category.Options[i] - option.Truth = game.i18n.localize( - `IRONSWORN.WorldTruths.${category.Name}.option${i + 1}` - ) - option.Quest = game.i18n.localize( - `IRONSWORN.WorldTruths.${category.Name}.quest${i + 1}` - ) - } - category.Name = game.i18n.localize( - `IRONSWORN.WorldTruths.${category.Name}.name` - ) - } - - return await foundry.utils.mergeObject(super.getData(), { - truths - }) - } - - activateListeners(html: JQuery) { - super.activateListeners(html) - - html.find('.ironsworn__custom__truth').on('focus', (ev) => { - this._customTruthFocus.call(this, ev) - }) - html.find('.ironsworn__save__truths').on('click', async (ev) => { - await this._save.call(this, ev) - }) - } - - _customTruthFocus(ev: JQuery.FocusEvent) { - $(ev.currentTarget).siblings('input').prop('checked', true) - } - - async _save(ev: JQuery.ClickEvent) { - ev.preventDefault() - - // Get elements that are checked - const sections: string[] = [] - for (const radio of this.element.find(':checked')) { - const { category } = radio.dataset - const descriptionElement = $(radio).parent().find('.description') - const description = - descriptionElement.html() || `

${descriptionElement.val()}

` - sections.push(`

${category}

${description}`) - } - - const title = game.i18n.localize('IRONSWORN.YourWorldTruths') - const journal = await IronswornJournalEntry.create({ - name: title - }) - await IronswornJournalPage.create( - { - name: title, - // @ts-expect-error - let Foundry choose the default format, because it's a number in v11 and a string in v12 - text: { content: sections.join('\n') } - }, - { parent: journal } - ) - journal?.sheet?.render(true) - this.close() - } -} diff --git a/src/module/datasworn2/import/index.ts b/src/module/datasworn2/import/index.ts index 50bcc970b..18d4348d4 100644 --- a/src/module/datasworn2/import/index.ts +++ b/src/module/datasworn2/import/index.ts @@ -589,7 +589,7 @@ for (const collection of collections) { txt.replace(/{{table>.*?}}/, '(roll below)').trim() return { type: 'truth', - name: o.summary ?? '', + name: o.summary ?? o._id, system: { Subtable: firstOracle?.rows?.map((row) => ({ dfid: LegacyToDataswornIds[row._id], @@ -605,6 +605,7 @@ for (const collection of collections) { Description: renderLinksInStr( stripTableEmbeds(o.description ?? '') ), + Summary: renderLinksInStr(o.summary ?? ''), Quest: renderLinksInStr(o.quest_starter), 'Roll template': { Description: o.description diff --git a/src/module/datasworn2/import/rendering.ts b/src/module/datasworn2/import/rendering.ts index 5ca9b2239..480e59356 100644 --- a/src/module/datasworn2/import/rendering.ts +++ b/src/module/datasworn2/import/rendering.ts @@ -5,6 +5,8 @@ import { hash, lookupLegacyId } from '../ids' export const markdownRenderer = new Showdown.Converter({ tables: true }) const MARKDOWN_LINK_RE = /\[(.*?)\]\((.*?)\)/g + +// These are Foundry keys for these, not the paths to the JSON files const COMPENDIUM_KEY_MAP = { asset: { classic: 'ironswornassets', @@ -40,10 +42,10 @@ const COMPENDIUM_KEY_MAP = { sundered_isles: 'sunderedislesnpcs' }, delve_site_theme: { - delve: 'delve-themes' + delve: 'ironsworndelvethemes' }, delve_site_domain: { - delve: 'delve-domains' + delve: 'ironsworndelvedomains' }, truth: { classic: 'ironsworntruths', @@ -53,7 +55,7 @@ const COMPENDIUM_KEY_MAP = { } export function renderLinksInStr(str: string): string { - return str.replace(MARKDOWN_LINK_RE, (match, text, url) => { + return str.replace(MARKDOWN_LINK_RE, (match, text: string, url: string) => { if (!url.startsWith('datasworn:')) return match url = url.substring('datasworn:'.length) const parsed = IdParser.parse(url) @@ -85,7 +87,7 @@ export function renderLinksInStr(str: string): string { if (parsed.primaryTypeId === 'oracle_collection') { return ` ${text}` } - const urlHash = hash(legacyId || url) + const urlHash = hash(legacyId) return `@Compendium[foundry-ironsworn.${compendiumKey}.${urlHash}]{${text}}` }) } diff --git a/src/module/features/custommoves.ts b/src/module/features/custommoves.ts index a198942c1..81141f8a5 100644 --- a/src/module/features/custommoves.ts +++ b/src/module/features/custommoves.ts @@ -1,7 +1,7 @@ import type { IMove, IMoveCategory } from 'dataforged' import { ISMoveCategories, SFMoveCategories } from '../dataforged/data' import type { IronswornItem } from '../item/item' -import { cachedDocumentsForPack } from './pack-cache' +import { cachedDocumentsForPack, PackContents } from './pack-cache' export interface MoveCategory { color: string | null diff --git a/src/module/features/customoracles.ts b/src/module/features/customoracles.ts index 4080c4b0c..47c4dc8bc 100644 --- a/src/module/features/customoracles.ts +++ b/src/module/features/customoracles.ts @@ -7,7 +7,7 @@ import type { import type { IOracle, IOracleCategory } from 'dataforged' import { cloneDeep, compact } from 'lodash-es' import { DataswornTree } from '../datasworn2' -import { IronswornSettings } from '../helpers/settings' +import { DataswornRulesetKey, IronswornSettings } from '../helpers/settings' import { DS_ORACLE_COMPENDIUM_KEYS, OracleTable @@ -146,12 +146,10 @@ export function findPathToNodeByDfId(rootNode: IOracleTreeNode, dfid: string) { return ret } -type OracleCategory = 'classic' | 'delve' | 'starforged' | 'sundered_isles' - const ORACLES: Record = {} export function registerOracleTreeInternal( - category: OracleCategory, + category: DataswornRulesetKey, rootNode: IOracleTreeNode ) { ORACLES[category] = rootNode @@ -203,7 +201,8 @@ async function generateTreeFromDsData( ruleset: RulesetId ): Promise { const pack = game.packs.get(DS_ORACLE_COMPENDIUM_KEYS[ruleset]) - const index = await pack.getIndex({ fields: ['flags'] }) + const index = await pack?.getIndex({ fields: ['flags'] }) + if (!index) return emptyNode() const rp = DataswornTree.get(ruleset) return { @@ -227,7 +226,7 @@ export async function registerDefaultOracleTrees() { // Available in browser export function registerOracleTree( - category: OracleCategory, + category: DataswornRulesetKey, rootNode: IOracleTreeNode ) { // Check if internal registrations have been done @@ -242,12 +241,12 @@ export function registerOracleTree( registerOracleTreeInternal(category, rootNode) } -export function getOracleTree(category: OracleCategory): IOracleTreeNode { +export function getOracleTree(category: DataswornRulesetKey): IOracleTreeNode { return cloneDeep(ORACLES[category]) } export async function getOracleTreeWithCustomOracles( - category: OracleCategory + category: DataswornRulesetKey ): Promise { const rootNode = getOracleTree(category) diff --git a/src/module/features/pack-cache.ts b/src/module/features/pack-cache.ts index 42d181ee3..00c82ac1b 100644 --- a/src/module/features/pack-cache.ts +++ b/src/module/features/pack-cache.ts @@ -6,7 +6,7 @@ import type { OracleTable } from '../roll-table/oracle-table' const ONE_MINUTE_IN_MS = 60 * 1000 -type PackContents = Array< +export type PackContents = Array< StoredDocument< | Scene | IronswornActor diff --git a/src/module/helpers/settings.ts b/src/module/helpers/settings.ts index be328adb4..17a3348fb 100644 --- a/src/module/helpers/settings.ts +++ b/src/module/helpers/settings.ts @@ -3,11 +3,20 @@ import { kebabCase, mapValues } from 'lodash-es' import type { IronswornActor } from '../actor/actor.js' import { FirstStartDialog } from '../applications/firstStartDialog' import { SFSettingTruthsDialogVue } from '../applications/vueSfSettingTruthsDialog.js' -import { WorldTruthsDialog } from '../applications/worldTruthsDialog.js' import * as IronColor from '../features/ironcolor' import * as IronTheme from '../features/irontheme' -export const RULESETS = ['classic', 'delve', 'starforged', 'sundered_isles'] +export type DataswornRulesetKey = + | 'classic' + | 'delve' + | 'starforged' + | 'sundered_isles' +export const RULESETS: DataswornRulesetKey[] = [ + 'classic', + 'delve', + 'starforged', + 'sundered_isles' +] declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -82,22 +91,6 @@ export class IronswornSettings { type: FirstStartDialog, restricted: true }) - game.settings.registerMenu('foundry-ironsworn', 'is-truths-dialog', { - name: 'IRONSWORN.Settings.ISTruthsDialog.Name', - label: 'IRONSWORN.Settings.ISTruthsDialog.Label', - icon: 'fas fa-feather', - hint: 'IRONSWORN.Settings.ISTruthsDialog.Hint', - type: WorldTruthsDialog, - restricted: true - }) - game.settings.registerMenu('foundry-ironsworn', 'sf-truths-dialog', { - name: 'IRONSWORN.Settings.SFTruthsDialog.Name', - label: 'IRONSWORN.Settings.SFTruthsDialog.Label', - icon: 'fas fa-feather', - hint: 'IRONSWORN.Settings.SFTruthsDialog.Hint', - type: SFSettingTruthsDialogVue, - restricted: true - }) // Toolbox/ruleset. this goes at the top because it's a "showstopper" if folks need it but can't find it. // Legacy toolbox selection. This has been converted to individual rulesets below @@ -266,6 +259,7 @@ export class IronswornSettings { static get defaultToolbox(): 'ironsworn' | 'starforged' | 'sunderedisles' { const setting = this.get('toolbox') + if (setting === 'migrated') return 'ironsworn' if (setting === 'sheet') { const sheetClasses = game.settings.get('core', 'sheetClasses') const defaultCharacterSheet = sheetClasses.Actor?.character @@ -300,10 +294,8 @@ export class IronswornSettings { } } - static get enabledRulesets(): Array< - 'classic' | 'delve' | 'starforged' | 'sundered_isles' - > { - const ret: string[] = [] + static get enabledRulesets(): DataswornRulesetKey[] { + const ret: DataswornRulesetKey[] = [] for (const ruleset of RULESETS) { if (IronswornSettings.get(`ruleset-${ruleset}`)) { ret.push(ruleset) @@ -312,9 +304,7 @@ export class IronswornSettings { return ret } - static async enableOnlyRulesets( - ...enabled: Array<'classic' | 'delve' | 'starforged' | 'sundered_isles'> - ) { + static async enableOnlyRulesets(...enabled: DataswornRulesetKey[]) { for (const ruleset of RULESETS) { await game.settings.set( 'foundry-ironsworn', diff --git a/src/module/journal/journal-entry-page-types.ts b/src/module/journal/journal-entry-page-types.ts index d5ed6da22..7699cc9bf 100644 --- a/src/module/journal/journal-entry-page-types.ts +++ b/src/module/journal/journal-entry-page-types.ts @@ -68,6 +68,7 @@ export interface ClockDataProperties { /// ///////// SETTING TRUTH OPTION export interface TruthOptionDataSourceData extends ISettingTruthOption { dfid: string + Summary: string Quest: string } export interface TruthOptionDataPropertiesData diff --git a/src/module/roll-table/oracle-table.ts b/src/module/roll-table/oracle-table.ts index 8e3f6dc6d..244fc1c0f 100644 --- a/src/module/roll-table/oracle-table.ts +++ b/src/module/roll-table/oracle-table.ts @@ -8,7 +8,9 @@ import { ISOracleCategories, SFOracleCategories } from '../dataforged/data' import { IdParser } from '../datasworn2' import { findPathToNodeByTableUuid, - getOracleTreeWithCustomOracles + getCustomizedOracleTrees, + getOracleTreeWithCustomOracles, + IOracleTreeNode } from '../features/customoracles' import { cachedDocumentsForPack } from '../features/pack-cache' import type { IronswornJournalEntry } from '../journal/journal-entry' @@ -104,23 +106,29 @@ export class OracleTable extends RollTable { const pack = game.packs.get(packId) const index = await pack?.getIndex({ fields: ['flags'] }) return index?.find((entry) => { + // @ts-expect-error flags are untyped return entry.flags?.['foundry-ironsworn']?.dsid === dsid }) } - static async getByDsId(dsid: string) { + static async getByDsId( + dsid: string + ): Promise | undefined> { const parsed = IdParser.parse(dsid) const packId = DS_ORACLE_COMPENDIUM_KEYS[parsed.rulesPackageId] const pack = game.packs.get(packId) if (!pack) return - const id = IdParser.get(dsid) const index = await pack?.getIndex({ fields: ['flags'] }) for (const entry of index.contents) { + // @ts-expect-error flags are untyped if (entry.flags?.['foundry-ironsworn']?.dsid === dsid) { - return pack.getDocument(entry._id) + return pack.getDocument(entry._id) as any as + | StoredDocument + | undefined } } + return undefined } /** @@ -177,16 +185,15 @@ export class OracleTable extends RollTable { /** * @returns a string representing the path this table in the Ironsworn oracle tree (not including this table) */ async getDfPath() { - const starforgedRoot = await getOracleTreeWithCustomOracles('starforged') - const ironswornRoot = await getOracleTreeWithCustomOracles('ironsworn') - - const pathElements = - findPathToNodeByTableUuid(starforgedRoot, this.uuid) ?? - findPathToNodeByTableUuid(ironswornRoot, this.uuid) + const trees = await getCustomizedOracleTrees() + let pathElements: IOracleTreeNode[] | undefined + for (const tree of trees) { + pathElements = findPathToNodeByTableUuid(tree, this.uuid) + if (pathElements?.length > 0) break + } + if (pathElements === undefined || pathElements?.length < 1) return '' const pathNames = pathElements.map((x) => x.displayName) - // root node (0) has no display name - pathNames.shift() // last node is *this* node pathNames.pop() diff --git a/src/module/vue/components/mce-editor.vue b/src/module/vue/components/mce-editor.vue index 29367d142..66ae5869a 100644 --- a/src/module/vue/components/mce-editor.vue +++ b/src/module/vue/components/mce-editor.vue @@ -1,5 +1,5 @@