From fad09a1b3a73c0fe932da14541732fcc894d8d24 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 16 Jan 2026 07:43:25 +0100 Subject: [PATCH 1/5] [Feature] 1541 - Trigger Improvements (#1542) * Improved registration and unregistration of triggers * Added logging * Fixed Feature level unregistration * Fixed action deletion unregistration * SceneEnvironment stub * Update module/data/registeredTriggers.mjs Co-authored-by: Carlos Fernandez --------- Co-authored-by: Carlos Fernandez --- daggerheart.mjs | 56 ++--------- lang/en.json | 3 + module/data/_module.mjs | 1 + module/data/item/base.mjs | 43 ++++---- module/data/registeredTriggers.mjs | 154 +++++++++++++++++++++++++++++ module/documents/actor.mjs | 10 ++ module/documents/item.mjs | 19 ++++ module/documents/token.mjs | 6 ++ 8 files changed, 226 insertions(+), 66 deletions(-) create mode 100644 module/data/registeredTriggers.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index 4e88c148..3abcd210 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -78,6 +78,7 @@ CONFIG.ux.ContextMenu = applications.ux.DHContextMenu; CONFIG.ux.TooltipManager = documents.DhTooltipManager; CONFIG.ux.TemplateManager = new TemplateManager(); CONFIG.ux.TokenManager = new TokenManager(); +CONFIG.debug.triggers = false; Hooks.once('init', () => { game.system.api = { @@ -89,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); @@ -389,49 +390,12 @@ Hooks.on('refreshToken', (_, options) => { Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); -class RegisteredTriggers extends Map { - constructor() { - super(); - } +/* Non actor-linked Actors should unregister the triggers of their tokens if a scene's token layer is torn down */ +Hooks.on('canvasTearDown', canvas => { + game.system.registeredTriggers.unregisterSceneTriggers(canvas.scene); +}); - async registerTriggers(trigger, actor, triggeringActorType, uuid, commands) { - const existingTrigger = this.get(trigger); - if (!existingTrigger) this.set(trigger, new Map()); - - this.get(trigger).set(uuid, { actor, triggeringActorType, commands }); - } - - 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) { - for (let { actor, triggeringActorType, commands } of dualityTrigger.values()) { - const triggerData = CONFIG.DH.TRIGGER.triggers[trigger]; - if (triggerData.usesActor && triggeringActorType !== 'any') { - if (triggeringActorType === 'self' && currentActor?.uuid !== actor) continue; - else if (triggeringActorType === 'other' && currentActor?.uuid === actor) continue; - } - - for (let command of commands) { - try { - 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; - } -} +/* 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); +}); diff --git a/lang/en.json b/lang/en.json index 1157c7b9..8e64ab7d 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2710,6 +2710,9 @@ "rerollDamage": "Reroll Damage", "assignTagRoll": "Assign as Tag Roll" }, + "ConsoleLogs": { + "triggerRun": "DH TRIGGER | Item '{item}' on actor '{actor}' ran a '{trigger}' trigger." + }, "Countdowns": { "title": "Countdowns", "toggleIconMode": "Toggle Icon Only", 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/item/base.mjs b/module/data/item/base.mjs index 415fc8d4..0c9fdabe 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -164,26 +164,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { prepareBaseData() { super.prepareBaseData(); - - for (const action of this.actions ?? []) { - if (!action.actor) continue; - - const actionsToRegister = []; - for (let i = 0; i < action.triggers.length; i++) { - const trigger = action.triggers[i]; - const { args } = CONFIG.DH.TRIGGER.triggers[trigger.trigger]; - const fn = new foundry.utils.AsyncFunction(...args, `{${trigger.command}\n}`); - actionsToRegister.push(fn.bind(action)); - if (i === action.triggers.length - 1) - game.system.registeredTriggers.registerTriggers( - trigger.trigger, - action.actor?.uuid, - trigger.triggeringActorType, - this.parent.uuid, - actionsToRegister - ); - } - } + game.system.registeredTriggers.registerItemTriggers(this.parent); } async _preCreate(data, options, user) { @@ -246,6 +227,28 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { const armorData = getScrollTextData(this.parent.parent.system.resources, changed.system.marks, 'armor'); options.scrollingTextData = [armorData]; } + + if (changed.system?.actions) { + const triggersToRemove = Object.keys(changed.system.actions).reduce((acc, key) => { + if (!changed.system.actions[key]) { + const strippedKey = key.replace('-=', ''); + acc.push(...this.actions.get(strippedKey).triggers.map(x => x.trigger)); + } + + return acc; + }, []); + + game.system.registeredTriggers.unregisterTriggers(triggersToRemove, this.parent.uuid); + + if (!(this.parent.parent.token instanceof game.system.api.documents.DhToken)) { + for (const token of this.parent.parent.getActiveTokens()) { + game.system.registeredTriggers.unregisterTriggers( + triggersToRemove, + `${token.document.uuid}.${this.parent.uuid}` + ); + } + } + } } _onUpdate(changed, options, userId) { diff --git a/module/data/registeredTriggers.mjs b/module/data/registeredTriggers.mjs new file mode 100644 index 00000000..fe962c5e --- /dev/null +++ b/module/data/registeredTriggers.mjs @@ -0,0 +1,154 @@ +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, registerOverride) { + 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 ( + !registerOverride && + !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) { + /* TODO: Finish sceneEnvironment registration and unreg */ + // const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart); + // for (const environment of systemData.sceneEnvironments) { + // for (const feature of environment.system.features) { + // if(feature) this.registerItemTriggers(feature, true); + // } + // } + + 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 (const 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 1a4153ad..27c310ae 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -104,6 +104,16 @@ export default class DhpActor extends Actor { } } + async _preDelete() { + 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) { super._onDelete(options, userId); for (const party of this.parties) { diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 7607658c..0a163dab 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -208,4 +208,23 @@ export default class DHItem extends foundry.documents.Item { cls.create(msg); } + + deleteTriggers() { + const actions = Array.from(this.system.actions ?? []); + if (!actions.length) return; + + 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() { + this.deleteTriggers(); + } } diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 4ac29264..317f3acf 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -536,4 +536,10 @@ export default class DHToken extends CONFIG.Token.documentClass { }; } //#endregion + + async _preDelete() { + if (this.actor && !this.actor.prototypeToken?.actorLink) { + game.system.registeredTriggers.unregisterItemTriggers(this.actor.items); + } + } } From 822c522f61baa6460963bde60ea7627712eddf0e Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:35:02 +0100 Subject: [PATCH 2/5] [Fix] 1535 - Toggle Bonuses (#1538) * Fixed all SRD instances of Powerful and Massive * Fixed suppressed effects being added to roll formula options * Fixed weapon effects being presented when it's no the weapon itself * . * . * Fixed secondary weapons effects * Raised system version * Update module/data/action/baseAction.mjs Co-authored-by: Carlos Fernandez * Update module/documents/chatMessage.mjs Co-authored-by: Carlos Fernandez --------- Co-authored-by: Carlos Fernandez --- .../applications/sheets/actors/character.mjs | 2 +- .../sheets/api/application-mixin.mjs | 5 +++- module/data/action/baseAction.mjs | 28 +++++++++++++------ module/documents/chatMessage.mjs | 4 ++- ..._Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json | 13 +-------- ..._Advanced_Greatsword_MAC6YWTo4lzSotQc.json | 10 ------- .../weapon_Double_Flail_xm1yU7k58fMgXxRR.json | 13 +-------- .../weapon_Elder_Bow_JdWcn9W1edhAEInL.json | 13 +-------- ...Floating_Bladeshards_3vti3xfo0wJND7ew.json | 13 +-------- ...apon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json | 13 +-------- .../weapon_Greatbow_MXBpbqQsZFln4rZk.json | 13 +-------- .../weapon_Greatstaff_Yk8pTEmyLLi4095S.json | 13 +-------- .../weapon_Greatsword_70ysaFJDREwTgvZa.json | 10 ------- ..._Improved_Greatstaff_LCuTrYXi4lhg6LqW.json | 13 +-------- ..._Improved_Greatsword_FPX4ouDrxXiQ5MDf.json | 10 ------- ...Legendary_Greatstaff_jDtvEabkHY1GFgfc.json | 13 +-------- ...Legendary_Greatsword_zMZ46F9VR7zdTxb9.json | 10 ------- .../weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json | 13 +-------- 18 files changed, 38 insertions(+), 171 deletions(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index dd5f35fc..806d7a45 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -712,7 +712,7 @@ export default class CharacterSheet extends DHBaseActorSheet { headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }), - effects: Array.from(await this.document.allApplicableEffects()), + effects: await await game.system.api.data.actions.actionsTypes.base.getEffects(this.document), roll: { trait: button.dataset.attribute, type: 'trait' diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 7276316f..b590de86 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -505,7 +505,10 @@ export default function DHApplicationMixin(Base) { const doc = await getDocFromElement(target), action = doc?.system?.attack ?? doc; const config = action.prepareConfig(event); - config.effects = Array.from(await this.document.allApplicableEffects()); + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects( + this.document, + doc + ); config.hasRoll = false; return action && action.workflow.get('damage').execute(config, null, true); } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index dac4cf68..0addc6b3 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -198,7 +198,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel let config = this.prepareConfig(event); if (!config) return; - await this.addEffects(config); + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item); if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; @@ -266,14 +266,26 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return config; } - /** */ - async addEffects(config) { - let effects = []; - if (this.actor) { - effects = Array.from(await this.actor.allApplicableEffects()); - } + /** + * Get the all potentially applicable effects on the actor + * @param {DHActor} actor The actor performing the action + * @param {DHItem|DhActor} effectParent The parent of the effect + * @returns {DhActiveEffect[]} + */ + static async getEffects(actor, effectParent) { + if (!actor) return []; + + return Array.from(await actor.allApplicableEffects()).filter(effect => { + /* Effects on weapons only ever apply for the weapon itself */ + if (effect.parent.type === 'weapon') { + /* Unless they're secondary - then they apply only to other primary weapons */ + if (effect.parent.system.secondary) { + if (effectParent.type !== 'weapon' || effectParent.system.secondary) return false; + } else if (effectParent?.id !== effect.parent.id) return false; + } - config.effects = effects; + return !effect.isSuppressed; + }); } /** diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index d85bcb45..c965f2e5 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -158,7 +158,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { const config = foundry.utils.deepClone(this.system); config.event = event; if (this.system.action) { - await this.system.action.addEffects(config); + const actor = await foundry.utils.fromUuid(config.source.actor); + const item = actor?.items.get(config.source.item) ?? null; + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(actor, item); await this.system.action.workflow.get('damage')?.execute(config, this._id, true); } diff --git a/src/packs/items/weapons/weapon_Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json b/src/packs/items/weapons/weapon_Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json index 6ce54823..c66354c2 100644 --- a/src/packs/items/weapons/weapon_Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json +++ b/src/packs/items/weapons/weapon_Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "sGVVxSM68Fmr1sSM", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Advanced_Greatsword_MAC6YWTo4lzSotQc.json b/src/packs/items/weapons/weapon_Advanced_Greatsword_MAC6YWTo4lzSotQc.json index fe3fff0e..71226630 100644 --- a/src/packs/items/weapons/weapon_Advanced_Greatsword_MAC6YWTo4lzSotQc.json +++ b/src/packs/items/weapons/weapon_Advanced_Greatsword_MAC6YWTo4lzSotQc.json @@ -118,16 +118,6 @@ "key": "system.evasion", "mode": 2, "value": "-1" - }, - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" } ], "_id": "E0PjC15OP55vIype", diff --git a/src/packs/items/weapons/weapon_Double_Flail_xm1yU7k58fMgXxRR.json b/src/packs/items/weapons/weapon_Double_Flail_xm1yU7k58fMgXxRR.json index 2e00f9c1..a118b399 100644 --- a/src/packs/items/weapons/weapon_Double_Flail_xm1yU7k58fMgXxRR.json +++ b/src/packs/items/weapons/weapon_Double_Flail_xm1yU7k58fMgXxRR.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "DCie5eR1dZH2Qvln", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Elder_Bow_JdWcn9W1edhAEInL.json b/src/packs/items/weapons/weapon_Elder_Bow_JdWcn9W1edhAEInL.json index b6437781..35659402 100644 --- a/src/packs/items/weapons/weapon_Elder_Bow_JdWcn9W1edhAEInL.json +++ b/src/packs/items/weapons/weapon_Elder_Bow_JdWcn9W1edhAEInL.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "sZ1XotFlGdkPPDG4", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Floating_Bladeshards_3vti3xfo0wJND7ew.json b/src/packs/items/weapons/weapon_Floating_Bladeshards_3vti3xfo0wJND7ew.json index fa7b7d45..232f26e9 100644 --- a/src/packs/items/weapons/weapon_Floating_Bladeshards_3vti3xfo0wJND7ew.json +++ b/src/packs/items/weapons/weapon_Floating_Bladeshards_3vti3xfo0wJND7ew.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "T831j6kZiMnpMNmv", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json b/src/packs/items/weapons/weapon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json index 551dcf56..ee8afebc 100644 --- a/src/packs/items/weapons/weapon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json +++ b/src/packs/items/weapons/weapon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "ir4iKLIQ4CH1Qckn", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Greatbow_MXBpbqQsZFln4rZk.json b/src/packs/items/weapons/weapon_Greatbow_MXBpbqQsZFln4rZk.json index f97e5432..f56e77c7 100644 --- a/src/packs/items/weapons/weapon_Greatbow_MXBpbqQsZFln4rZk.json +++ b/src/packs/items/weapons/weapon_Greatbow_MXBpbqQsZFln4rZk.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "K4VgrDjVj1U1m9Ie", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Greatstaff_Yk8pTEmyLLi4095S.json b/src/packs/items/weapons/weapon_Greatstaff_Yk8pTEmyLLi4095S.json index 0fbfc2b4..66c12e5e 100644 --- a/src/packs/items/weapons/weapon_Greatstaff_Yk8pTEmyLLi4095S.json +++ b/src/packs/items/weapons/weapon_Greatstaff_Yk8pTEmyLLi4095S.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "904orawScurM9GjG", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Greatsword_70ysaFJDREwTgvZa.json b/src/packs/items/weapons/weapon_Greatsword_70ysaFJDREwTgvZa.json index 4707e397..f60e438d 100644 --- a/src/packs/items/weapons/weapon_Greatsword_70ysaFJDREwTgvZa.json +++ b/src/packs/items/weapons/weapon_Greatsword_70ysaFJDREwTgvZa.json @@ -118,16 +118,6 @@ "key": "system.evasion", "mode": 2, "value": "-1" - }, - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" } ], "_id": "cffkpiwGpEGhjiUC", diff --git a/src/packs/items/weapons/weapon_Improved_Greatstaff_LCuTrYXi4lhg6LqW.json b/src/packs/items/weapons/weapon_Improved_Greatstaff_LCuTrYXi4lhg6LqW.json index 5faa0b0e..cf1bdf63 100644 --- a/src/packs/items/weapons/weapon_Improved_Greatstaff_LCuTrYXi4lhg6LqW.json +++ b/src/packs/items/weapons/weapon_Improved_Greatstaff_LCuTrYXi4lhg6LqW.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "hnayB09P25ZW3gVY", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Improved_Greatsword_FPX4ouDrxXiQ5MDf.json b/src/packs/items/weapons/weapon_Improved_Greatsword_FPX4ouDrxXiQ5MDf.json index f8407b13..f71e5ea6 100644 --- a/src/packs/items/weapons/weapon_Improved_Greatsword_FPX4ouDrxXiQ5MDf.json +++ b/src/packs/items/weapons/weapon_Improved_Greatsword_FPX4ouDrxXiQ5MDf.json @@ -118,16 +118,6 @@ "key": "system.evasion", "mode": 2, "value": "-1" - }, - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" } ], "_id": "2nl35v8sPAudiOIb", diff --git a/src/packs/items/weapons/weapon_Legendary_Greatstaff_jDtvEabkHY1GFgfc.json b/src/packs/items/weapons/weapon_Legendary_Greatstaff_jDtvEabkHY1GFgfc.json index 0d317f0d..a5ea82f9 100644 --- a/src/packs/items/weapons/weapon_Legendary_Greatstaff_jDtvEabkHY1GFgfc.json +++ b/src/packs/items/weapons/weapon_Legendary_Greatstaff_jDtvEabkHY1GFgfc.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "OV1Ly7vX4owBUgLQ", "type": "base", "system": {}, diff --git a/src/packs/items/weapons/weapon_Legendary_Greatsword_zMZ46F9VR7zdTxb9.json b/src/packs/items/weapons/weapon_Legendary_Greatsword_zMZ46F9VR7zdTxb9.json index fb7a2ed3..840e7ec7 100644 --- a/src/packs/items/weapons/weapon_Legendary_Greatsword_zMZ46F9VR7zdTxb9.json +++ b/src/packs/items/weapons/weapon_Legendary_Greatsword_zMZ46F9VR7zdTxb9.json @@ -118,16 +118,6 @@ "key": "system.evasion", "mode": 2, "value": "-1" - }, - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" } ], "_id": "oRCiXSElN5xufUfn", diff --git a/src/packs/items/weapons/weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json b/src/packs/items/weapons/weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json index 8d3fd741..3b5983f5 100644 --- a/src/packs/items/weapons/weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json +++ b/src/packs/items/weapons/weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json @@ -113,18 +113,7 @@ "name": "Powerful", "description": "On a successful attack, roll an additional damage die and discard the lowest result.", "img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp", - "changes": [ - { - "key": "system.bonuses.damage.primaryWeapon.extraDice", - "mode": 2, - "value": "1" - }, - { - "key": "system.rules.weapon.dropLowestDamageDice", - "mode": 5, - "value": "1" - } - ], + "changes": [], "_id": "2J6vzNUel78JFypp", "type": "base", "system": {}, From 78c6f3bf48f4ffc0ad1b5c7b184c64960817058c Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 16 Jan 2026 09:39:22 +0100 Subject: [PATCH 3/5] Removed a 'aware aware' duplicate typo --- module/applications/sheets/actors/character.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 806d7a45..e11fee05 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -712,7 +712,7 @@ export default class CharacterSheet extends DHBaseActorSheet { headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }), - effects: await await game.system.api.data.actions.actionsTypes.base.getEffects(this.document), + effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document), roll: { trait: button.dataset.attribute, type: 'trait' From d626285a88a6054e8842f8492d5d47a4f5fd69b6 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:05:38 +0100 Subject: [PATCH 4/5] . (#1545) --- module/documents/chatMessage.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index c965f2e5..2f23cc1a 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -194,7 +194,16 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); this.consumeOnSuccess(); - this.system.action?.workflow.get('applyDamage')?.execute(config, targets, true); + if (this.system.action) this.system.action.workflow.get('applyDamage')?.execute(config, targets, true); + else { + for (const target of targets) { + const actor = await foundry.utils.fromUuid(target.actorId); + if (!actor) continue; + + if (this.system.hasHealing) actor.takeHealing(this.system.damage); + else actor.takeDamage(this.system.damage); + } + } } async onRollSave(event) { From 325a48532b8f22c5322a62a50bc64056bc66852e Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 16 Jan 2026 10:16:01 +0100 Subject: [PATCH 5/5] Nullable fix in baseAction.getEffects --- module/data/action/baseAction.mjs | 4 ++-- system.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 0addc6b3..b5f95aff 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -274,13 +274,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel */ static async getEffects(actor, effectParent) { if (!actor) return []; - + return Array.from(await actor.allApplicableEffects()).filter(effect => { /* Effects on weapons only ever apply for the weapon itself */ if (effect.parent.type === 'weapon') { /* Unless they're secondary - then they apply only to other primary weapons */ if (effect.parent.system.secondary) { - if (effectParent.type !== 'weapon' || effectParent.system.secondary) return false; + if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false; } else if (effectParent?.id !== effect.parent.id) return false; } diff --git a/system.json b/system.json index 50b4cd2d..50a9c83b 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.5.1", + "version": "1.5.2", "compatibility": { "minimum": "13.346", "verified": "13.351",