From 36eac510418b21a8b9de94f69f538963eacad241 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 2 Jan 2026 21:07:05 +0100 Subject: [PATCH] Initial --- daggerheart.mjs | 32 +++++++++++++++++++ lang/en.json | 7 +++- .../sheets-configs/action-base-config.mjs | 29 ++++++++++++++++- module/config/_module.mjs | 1 + module/config/hooksConfig.mjs | 4 +-- module/config/system.mjs | 4 ++- module/config/triggerConfig.mjs | 10 ++++++ module/data/action/baseAction.mjs | 8 ++++- module/data/fields/_module.mjs | 1 + module/data/fields/triggerField.mjs | 15 +++++++++ module/data/item/base.mjs | 22 ++++++++++++- module/dice/dualityRoll.mjs | 24 ++++++++++++++ styles/less/global/elements.less | 4 +++ .../action-settings/trigger.hbs | 18 +++++++++++ 14 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 module/config/triggerConfig.mjs create mode 100644 module/data/fields/triggerField.mjs create mode 100644 templates/sheets-settings/action-settings/trigger.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index f1d8c67a..09d4302c 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -84,6 +84,8 @@ Hooks.once('init', () => { fields }; + game.system.registeredTriggers = new RegisteredTriggers(); + const { DocumentSheetConfig } = foundry.applications.apps; DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig); DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, { @@ -378,3 +380,33 @@ Hooks.on('moveToken', async (movedToken, data) => { 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(); + } + + async registerTriggers(trigger, actor, uuid, commands) { + const existingTrigger = this.get(trigger); + if (!existingTrigger) this.set(trigger, new Map()); + + this.get(trigger).set(uuid, { actor, commands }); + } + + async runTrigger(trigger, currentActor, ...args) { + const updates = []; + const dualityTrigger = this.get(trigger); + if (dualityTrigger) { + for (let { actor, commands } of dualityTrigger.values()) { + if (currentActor?.uuid !== actor) continue; + + for (let command of commands) { + const commandUpdates = await command(...args); + if (commandUpdates?.length) updates.push(...commandUpdates); + } + } + } + + return updates; + } +} diff --git a/lang/en.json b/lang/en.json index 3f8c4321..532ff3e3 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1217,6 +1217,10 @@ } } }, + "Triggers": { + "dualityRoll": "Duality Roll", + "fearRoll": "Fear Roll" + }, "WeaponFeature": { "barrier": { "name": "Barrier", @@ -2057,7 +2061,8 @@ "itemFeatures": "Item Features", "questions": "Questions", "configuration": "Configuration", - "base": "Base" + "base": "Base", + "triggers": "Triggers" }, "Tiers": { "singular": "Tier", diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 96790a5b..b4e9b971 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -29,7 +29,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) removeElement: this.removeElement, editEffect: this.editEffect, addDamage: this.addDamage, - removeDamage: this.removeDamage + removeDamage: this.removeDamage, + addTrigger: this.addTrigger, + removeTrigger: this.removeTrigger }, form: { handler: this.updateForm, @@ -55,6 +57,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) effect: { id: 'effect', template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs' + }, + trigger: { + id: 'trigger', + template: 'systems/daggerheart/templates/sheets-settings/action-settings/trigger.hbs' } }; @@ -82,6 +88,14 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) id: 'effect', icon: null, label: 'DAGGERHEART.GENERAL.Tabs.effects' + }, + trigger: { + active: false, + cssClass: '', + group: 'primary', + id: 'trigger', + icon: null, + label: 'DAGGERHEART.GENERAL.Tabs.triggers' } }; @@ -111,6 +125,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty; context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus; context.hasRoll = this.action.hasRoll; + context.triggerOptions = CONFIG.DH.TRIGGER.triggers; const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers; context.tierOptions = [ @@ -224,6 +239,18 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } + static addTrigger() { + const data = this.action.toObject(); + data.triggers.push({ hook: CONFIG.DH.TRIGGER.triggers.dualityRoll.id }); + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + + static removeTrigger(_event, button) { + const data = this.action.toObject(); + data.triggers = data.triggers.filter((_, index) => index !== Number.parseInt(button.dataset.index)); + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + /** Specific implementation in extending classes **/ static async addEffect(_event) {} static removeEffect(_event, _button) {} diff --git a/module/config/_module.mjs b/module/config/_module.mjs index ef26b958..560f3fec 100644 --- a/module/config/_module.mjs +++ b/module/config/_module.mjs @@ -10,3 +10,4 @@ export * as itemConfig from './itemConfig.mjs'; export * as settingsConfig from './settingsConfig.mjs'; export * as systemConfig from './system.mjs'; export * as itemBrowserConfig from './itemBrowserConfig.mjs'; +export * as triggerConfig from './triggerConfig.mjs'; diff --git a/module/config/hooksConfig.mjs b/module/config/hooksConfig.mjs index d316c4d4..9140ea0a 100644 --- a/module/config/hooksConfig.mjs +++ b/module/config/hooksConfig.mjs @@ -1,5 +1,3 @@ -const hooksConfig = { +export const hooksConfig = { effectDisplayToggle: 'DHEffectDisplayToggle' }; - -export default hooksConfig; diff --git a/module/config/system.mjs b/module/config/system.mjs index ac15b1d9..47a41e8d 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -7,7 +7,8 @@ import * as SETTINGS from './settingsConfig.mjs'; import * as EFFECTS from './effectConfig.mjs'; import * as ACTIONS from './actionConfig.mjs'; import * as FLAGS from './flagsConfig.mjs'; -import HOOKS from './hooksConfig.mjs'; +import * as HOOKS from './hooksConfig.mjs'; +import * as TRIGGER from './triggerConfig.mjs'; import * as ITEMBROWSER from './itemBrowserConfig.mjs'; export const SYSTEM_ID = 'daggerheart'; @@ -24,5 +25,6 @@ export const SYSTEM = { ACTIONS, FLAGS, HOOKS, + TRIGGER, ITEMBROWSER }; diff --git a/module/config/triggerConfig.mjs b/module/config/triggerConfig.mjs new file mode 100644 index 00000000..8ef7e989 --- /dev/null +++ b/module/config/triggerConfig.mjs @@ -0,0 +1,10 @@ +export const triggers = { + dualityRoll: { + id: 'dualityRoll', + label: 'DAGGERHEART.CONFIG.Triggers.dualityRoll' + }, + fearRoll: { + id: 'fearRoll', + label: 'DAGGERHEART.CONFIG.Triggers.fearRoll' + } +}; diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 18a09904..3bf97564 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -2,6 +2,7 @@ import DhpActor from '../../documents/actor.mjs'; import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs'; import { ActionMixin } from '../fields/actionField.mjs'; import { originItemField } from '../chat-message/actorRoll.mjs'; +import TriggerField from '../fields/triggerField.mjs'; const fields = foundry.data.fields; @@ -34,7 +35,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel nullable: false, required: true }), - targetUuid: new fields.StringField({ initial: undefined }) + targetUuid: new fields.StringField({ initial: undefined }), + triggers: new fields.ArrayField(new TriggerField()) }; this.extraSchemas.forEach(s => { @@ -343,6 +345,10 @@ export class ResourceUpdateMap extends Map { } addResources(resources) { + if (!resources?.length) return; + const invalidResources = resources.some(resource => !resource.key); + if (invalidResources) return; + for (const resource of resources) { if (!resource.key) continue; diff --git a/module/data/fields/_module.mjs b/module/data/fields/_module.mjs index 8d36b76d..2a8ba454 100644 --- a/module/data/fields/_module.mjs +++ b/module/data/fields/_module.mjs @@ -2,5 +2,6 @@ export { ActionCollection } from './actionField.mjs'; export { default as FormulaField } from './formulaField.mjs'; export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs'; export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs'; +export { default as TriggerField } from './triggerField.mjs'; export { default as MappingField } from './mappingField.mjs'; export * as ActionFields from './action/_module.mjs'; diff --git a/module/data/fields/triggerField.mjs b/module/data/fields/triggerField.mjs new file mode 100644 index 00000000..a36786e9 --- /dev/null +++ b/module/data/fields/triggerField.mjs @@ -0,0 +1,15 @@ +export default class TriggerField extends foundry.data.fields.SchemaField { + constructor(context) { + super( + { + trigger: new foundry.data.fields.StringField({ + nullable: false, + initial: CONFIG.DH.TRIGGER.triggers.dualityRoll.id, + choices: CONFIG.DH.TRIGGER.triggers + }), + command: new foundry.data.fields.JavaScriptField({ async: true }) + }, + context + ); + } +} diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 11be0a52..772c2b73 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -8,7 +8,7 @@ * @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item */ -import { addLinkedItemsDiff, createScrollText, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs'; +import { addLinkedItemsDiff, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs'; import { ActionsField } from '../fields/actionField.mjs'; import FormulaField from '../fields/formulaField.mjs'; @@ -135,6 +135,26 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { return data; } + prepareBaseData() { + super.prepareBaseData(); + + for (const action of this.actions) { + const actionsToRegister = []; + for (let i = 0; i < action.triggers.length; i++) { + const trigger = action.triggers[i]; + const fn = new foundry.utils.AsyncFunction('roll', 'actor', `{${trigger.command}\n}`); + actionsToRegister.push(fn.bind(action)); + if (i === action.triggers.length - 1) + game.system.registeredTriggers.registerTriggers( + trigger.trigger, + action.actor?.uuid, + this.parent.uuid, + actionsToRegister + ); + } + } + } + async _preCreate(data, options, user) { // Skip if no initial action is required or actions already exist if (this.metadata.hasInitialAction && foundry.utils.isEmpty(this.actions)) { diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index d2e20213..f15e0b09 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -224,6 +224,30 @@ export default class DualityRoll extends D20Roll { await super.buildPost(roll, config, message); await DualityRoll.dualityUpdate(config); + await DualityRoll.handleTriggers(roll, config); + } + + static async handleTriggers(roll, config) { + const updates = []; + const dualityUpdates = await game.system.registeredTriggers.runTrigger( + CONFIG.DH.TRIGGER.triggers.dualityRoll.id, + roll.data?.parent, + roll, + roll.data?.parent + ); + if (dualityUpdates?.length) updates.push(...dualityUpdates); + + if (config.roll.result.duality === -1) { + const fearUpdates = await game.system.registeredTriggers.runTrigger( + CONFIG.DH.TRIGGER.triggers.fearRoll.id, + roll.data?.parent, + roll, + roll.data?.parent + ); + if (fearUpdates?.length) updates.push(...fearUpdates); + } + + config.resourceUpdates.addResources(updates); } static async addDualityResourceUpdates(config) { diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index e740d917..e5fd3646 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -545,6 +545,10 @@ font-size: var(--font-size-12); padding-left: 3px; } + + code-mirror { + width: 100%; + } } .application.setting.dh-style { diff --git a/templates/sheets-settings/action-settings/trigger.hbs b/templates/sheets-settings/action-settings/trigger.hbs new file mode 100644 index 00000000..61d856cf --- /dev/null +++ b/templates/sheets-settings/action-settings/trigger.hbs @@ -0,0 +1,18 @@ +
+ + + {{#each @root.source.triggers as |trigger index|}} +
+ + + + {{formInput @root.fields.triggers.element.fields.command value=trigger.command elementType="code-mirror" name=(concat "triggers." index ".command") aria=(object label=(localize "Test"))}} +
+ {{/each}} +
\ No newline at end of file