diff --git a/daggerheart.mjs b/daggerheart.mjs index 3a3e1195..3abcd210 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -90,7 +90,7 @@ Hooks.once('init', () => { fields }; - game.system.registeredTriggers = new RegisteredTriggers(); + game.system.registeredTriggers = new game.system.api.data.RegisteredTriggers(); const { DocumentSheetConfig } = foundry.applications.apps; DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig); @@ -395,152 +395,7 @@ Hooks.on('canvasTearDown', canvas => { game.system.registeredTriggers.unregisterSceneTriggers(canvas.scene); }); +/* Non actor-linked Actors should register the triggers of their tokens on a readied scene */ Hooks.on('canvasReady', canas => { game.system.registeredTriggers.registerSceneTriggers(canvas.scene); }); - -class RegisteredTriggers extends Map { - constructor() { - super(); - } - - registerTriggers(triggers, actor, uuid) { - for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) { - const match = triggers[triggerKey]; - const existingTrigger = this.get(triggerKey); - - if (!match) { - if (existingTrigger?.get(uuid)) this.get(triggerKey).delete(uuid); - } else { - const { trigger, triggeringActorType, commands } = match; - - if (!existingTrigger) this.set(trigger, new Map()); - this.get(trigger).set(uuid, { actor, triggeringActorType, commands }); - } - } - } - - registerItemTriggers(item) { - for (const action of item.system.actions ?? []) { - if (!action.actor) continue; - - /* Non actor-linked should only prep synthetic actors so they're not registering triggers unless they're on the canvas */ - if ( - !action.actor.prototypeToken.actorLink && - (!(action.actor.parent instanceof game.system.api.documents.DhToken) || !action.actor.parent?.uuid) - ) - continue; - - const triggers = {}; - for (const trigger of action.triggers) { - const { args } = CONFIG.DH.TRIGGER.triggers[trigger.trigger]; - const fn = new foundry.utils.AsyncFunction(...args, `{${trigger.command}\n}`); - - if (!triggers[trigger.trigger]) - triggers[trigger.trigger] = { - trigger: trigger.trigger, - triggeringActorType: trigger.triggeringActorType, - commands: [] - }; - triggers[trigger.trigger].commands.push(fn.bind(action)); - } - - this.registerTriggers(triggers, action.actor?.uuid, item.uuid); - } - } - - unregisterTriggers(triggerKeys, uuid) { - for (const triggerKey of triggerKeys) { - const existingTrigger = this.get(triggerKey); - if (!existingTrigger) return; - - existingTrigger.delete(uuid); - } - } - - unregisterItemTriggers(items) { - for (const item of items) { - if (!item.system.actions.size) continue; - - const triggers = (item.system.actions ?? []).reduce((acc, action) => { - acc.push(...action.triggers.map(x => x.trigger)); - return acc; - }, []); - - this.unregisterTriggers(triggers, item.uuid); - } - } - - unregisterSceneTriggers(scene) { - for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) { - const existingTrigger = this.get(triggerKey); - if (!existingTrigger) continue; - const filtered = new Map(); - for (const [uuid, data] of existingTrigger.entries()) { - if (!uuid.startsWith(scene.uuid)) filtered.set(uuid, data); - } - this.set(triggerKey, filtered); - } - } - - registerSceneTriggers(scene) { - for (const actor of scene.tokens.filter(x => x.actor).map(x => x.actor)) { - if (actor.prototypeToken.actorLink) continue; - - for (const item of actor.items) { - this.registerItemTriggers(item); - } - } - } - - async runTrigger(trigger, currentActor, ...args) { - const updates = []; - const triggerSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).triggers; - if (!triggerSettings.enabled) return updates; - - const dualityTrigger = this.get(trigger); - if (dualityTrigger) { - const tokenBoundActors = ['adversary', 'environment']; - const triggerActors = ['character', ...tokenBoundActors]; - for (let [itemUuid, { actor: actorUuid, triggeringActorType, commands }] of dualityTrigger.entries()) { - const actor = await foundry.utils.fromUuid(actorUuid); - if (!actor || !triggerActors.includes(actor.type)) continue; - if (tokenBoundActors.includes(actor.type) && !actor.getActiveTokens().length) continue; - - const triggerData = CONFIG.DH.TRIGGER.triggers[trigger]; - if (triggerData.usesActor && triggeringActorType !== 'any') { - if (triggeringActorType === 'self' && currentActor?.uuid !== actorUuid) continue; - else if (triggeringActorType === 'other' && currentActor?.uuid === actorUuid) continue; - } - - for (let command of commands) { - try { - if (CONFIG.debug.triggers) { - const item = await foundry.utils.fromUuid(itemUuid); - console.log( - game.i18n.format('DAGGERHEART.UI.ConsoleLogs.triggerRun', { - actor: actor.name ?? '', - item: item?.name ?? '', - trigger: game.i18n.localize(triggerData.label) - }) - ); - } - - const result = await command(...args); - if (result?.updates?.length) updates.push(...result.updates); - } catch (_) { - const triggerName = game.i18n.localize(triggerData.label); - ui.notifications.error( - game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', { - trigger: triggerName, - actor: currentActor?.name - }) - ); - } - } - } - } - - return updates; - } -} diff --git a/module/data/_module.mjs b/module/data/_module.mjs index 0a476ee9..7ad20808 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -1,6 +1,7 @@ export { default as DhCombat } from './combat.mjs'; export { default as DhCombatant } from './combatant.mjs'; export { default as DhTagTeamRoll } from './tagTeamRoll.mjs'; +export { default as RegisteredTriggers } from './registeredTriggers.mjs'; export * as countdowns from './countdowns.mjs'; export * as actions from './action/_module.mjs'; diff --git a/module/data/registeredTriggers.mjs b/module/data/registeredTriggers.mjs new file mode 100644 index 00000000..2c3f6af1 --- /dev/null +++ b/module/data/registeredTriggers.mjs @@ -0,0 +1,145 @@ +export default class RegisteredTriggers extends Map { + constructor() { + super(); + } + + registerTriggers(triggers, actor, uuid) { + for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) { + const match = triggers[triggerKey]; + const existingTrigger = this.get(triggerKey); + + if (!match) { + if (existingTrigger?.get(uuid)) this.get(triggerKey).delete(uuid); + } else { + const { trigger, triggeringActorType, commands } = match; + + if (!existingTrigger) this.set(trigger, new Map()); + this.get(trigger).set(uuid, { actor, triggeringActorType, commands }); + } + } + } + + registerItemTriggers(item) { + for (const action of item.system.actions ?? []) { + if (!action.actor) continue; + + /* Non actor-linked should only prep synthetic actors so they're not registering triggers unless they're on the canvas */ + if ( + !action.actor.prototypeToken.actorLink && + (!(action.actor.parent instanceof game.system.api.documents.DhToken) || !action.actor.parent?.uuid) + ) + continue; + + const triggers = {}; + for (const trigger of action.triggers) { + const { args } = CONFIG.DH.TRIGGER.triggers[trigger.trigger]; + const fn = new foundry.utils.AsyncFunction(...args, `{${trigger.command}\n}`); + + if (!triggers[trigger.trigger]) + triggers[trigger.trigger] = { + trigger: trigger.trigger, + triggeringActorType: trigger.triggeringActorType, + commands: [] + }; + triggers[trigger.trigger].commands.push(fn.bind(action)); + } + + this.registerTriggers(triggers, action.actor?.uuid, item.uuid); + } + } + + unregisterTriggers(triggerKeys, uuid) { + for (const triggerKey of triggerKeys) { + const existingTrigger = this.get(triggerKey); + if (!existingTrigger) return; + + existingTrigger.delete(uuid); + } + } + + unregisterItemTriggers(items) { + for (const item of items) { + if (!item.system.actions.size) continue; + + const triggers = (item.system.actions ?? []).reduce((acc, action) => { + acc.push(...action.triggers.map(x => x.trigger)); + return acc; + }, []); + + this.unregisterTriggers(triggers, item.uuid); + } + } + + unregisterSceneTriggers(scene) { + for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) { + const existingTrigger = this.get(triggerKey); + if (!existingTrigger) continue; + const filtered = new Map(); + for (const [uuid, data] of existingTrigger.entries()) { + if (!uuid.startsWith(scene.uuid)) filtered.set(uuid, data); + } + this.set(triggerKey, filtered); + } + } + + registerSceneTriggers(scene) { + for (const actor of scene.tokens.filter(x => x.actor).map(x => x.actor)) { + if (actor.prototypeToken.actorLink) continue; + + for (const item of actor.items) { + this.registerItemTriggers(item); + } + } + } + + async runTrigger(trigger, currentActor, ...args) { + const updates = []; + const triggerSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).triggers; + if (!triggerSettings.enabled) return updates; + + const dualityTrigger = this.get(trigger); + if (dualityTrigger) { + const tokenBoundActors = ['adversary', 'environment']; + const triggerActors = ['character', ...tokenBoundActors]; + for (let [itemUuid, { actor: actorUuid, triggeringActorType, commands }] of dualityTrigger.entries()) { + const actor = await foundry.utils.fromUuid(actorUuid); + if (!actor || !triggerActors.includes(actor.type)) continue; + if (tokenBoundActors.includes(actor.type) && !actor.getActiveTokens().length) continue; + + const triggerData = CONFIG.DH.TRIGGER.triggers[trigger]; + if (triggerData.usesActor && triggeringActorType !== 'any') { + if (triggeringActorType === 'self' && currentActor?.uuid !== actorUuid) continue; + else if (triggeringActorType === 'other' && currentActor?.uuid === actorUuid) continue; + } + + for (let command of commands) { + try { + if (CONFIG.debug.triggers) { + const item = await foundry.utils.fromUuid(itemUuid); + console.log( + game.i18n.format('DAGGERHEART.UI.ConsoleLogs.triggerRun', { + actor: actor.name ?? '', + item: item?.name ?? '', + trigger: game.i18n.localize(triggerData.label) + }) + ); + } + + const result = await command(...args); + if (result?.updates?.length) updates.push(...result.updates); + } catch (_) { + const triggerName = game.i18n.localize(triggerData.label); + ui.notifications.error( + game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', { + trigger: triggerName, + actor: currentActor?.name + }) + ); + } + } + } + } + + return updates; + } +} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index baca3326..27c310ae 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -105,7 +105,13 @@ export default class DhpActor extends Actor { } async _preDelete() { - game.system.registeredTriggers.unregisterItemTriggers(this.items); + if (this.prototypeToken.actorLink) { + game.system.registeredTriggers.unregisterItemTriggers(this.items); + } else { + for (const token of this.getActiveTokens()) { + game.system.registeredTriggers.unregisterItemTriggers(token.actor.items); + } + } } _onDelete(options, userId) { diff --git a/module/documents/item.mjs b/module/documents/item.mjs index e795c5dd..0a163dab 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -213,10 +213,15 @@ export default class DHItem extends foundry.documents.Item { const actions = Array.from(this.system.actions ?? []); if (!actions.length) return; - game.system.registeredTriggers.unregisterTriggers( - actions.flatMap(action => action.triggers.map(x => x.trigger)), - this.uuid - ); + const triggerKeys = actions.flatMap(action => action.triggers.map(x => x.trigger)); + + game.system.registeredTriggers.unregisterTriggers(triggerKeys, this.uuid); + + if (!(this.actor.parent instanceof game.system.api.documents.DhToken)) { + for (const token of this.actor.getActiveTokens()) { + game.system.registeredTriggers.unregisterTriggers(triggerKeys, `${token.document.uuid}.${this.uuid}`); + } + } } async _preDelete() {