diff --git a/daggerheart.mjs b/daggerheart.mjs index 48f4a615..0cd82014 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, { @@ -379,3 +381,50 @@ 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(); + } + + 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; + } +} diff --git a/lang/en.json b/lang/en.json index a78ed588..1378daef 100755 --- a/lang/en.json +++ b/lang/en.json @@ -90,7 +90,9 @@ "customFormula": "Custom Formula", "formula": "Formula" }, - "displayInChat": "Display in chat" + "displayInChat": "Display in chat", + "deleteTriggerTitle": "Delete Trigger", + "deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?" }, "RollField": { "diceRolling": { @@ -1144,7 +1146,8 @@ "any": "Any", "friendly": "Friendly", "hostile": "Hostile", - "self": "Self" + "self": "Self", + "other": "Other" }, "TemplateTypes": { "circle": "Circle", @@ -1218,6 +1221,29 @@ } } }, + "Triggers": { + "postDamageReduction": { + "label": "After Damage Reduction" + }, + "preDamageReduction": { + "label": "Before Damage Reduction" + }, + "dualityRoll": { + "label": "Duality Roll" + }, + "fearRoll": { + "label": "Fear Roll" + }, + "triggerTexts": { + "strangePatternsContentTitle": "Matched {nr} times.", + "strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.", + "ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?", + "ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you." + }, + "triggerType": "Trigger Type", + "triggeringActorType": "Triggering Actor Type", + "triggerError": "{trigger} trigger failed for {actor}. It's probably configured wrong." + }, "WeaponFeature": { "barrier": { "name": "Barrier", @@ -2058,7 +2084,8 @@ "itemFeatures": "Item Features", "questions": "Questions", "configuration": "Configuration", - "base": "Base" + "base": "Base", + "triggers": "Triggers" }, "Tiers": { "singular": "Tier", @@ -2432,6 +2459,12 @@ "hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions." } }, + "triggers": { + "enabled": { + "label": "Enabled", + "hint": "Advanced automation such as triggering a popup for a wizard's Strange Patterns" + } + }, "summaryMessages": { "label": "Summary Messages" } @@ -2441,6 +2474,9 @@ }, "roll": { "title": "Actions" + }, + "trigger": { + "title": "Triggers" } }, "Homebrew": { diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 96790a5b..16ebcab5 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -7,6 +7,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) this.action = action; this.openSection = null; + this.openTrigger = this.action.triggers.length > 0 ? 0 : null; } get title() { @@ -15,7 +16,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) static DEFAULT_OPTIONS = { tag: 'form', - classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'], + classes: ['daggerheart', 'dh-style', 'action-config', 'dialog', 'max-800'], window: { icon: 'fa-solid fa-wrench', resizable: false @@ -29,7 +30,10 @@ 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, + expandTrigger: this.expandTrigger }, form: { handler: this.updateForm, @@ -55,6 +59,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 +90,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 +127,16 @@ 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.triggers = context.source.triggers.map((trigger, index) => { + const { hint, returns, usesActor } = CONFIG.DH.TRIGGER.triggers[trigger.trigger]; + return { + ...trigger, + hint, + returns, + usesActor, + revealed: this.openTrigger === index + }; + }); const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers; context.tierOptions = [ @@ -224,6 +250,60 @@ 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({ + trigger: CONFIG.DH.TRIGGER.triggers.dualityRoll.id, + triggeringActor: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id + }); + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + + static async removeTrigger(_event, button) { + const trigger = CONFIG.DH.TRIGGER.triggers[this.action.triggers[button.dataset.index].trigger]; + const confirmed = await foundry.applications.api.DialogV2.confirm({ + window: { + title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.deleteTriggerTitle') + }, + content: game.i18n.format('DAGGERHEART.ACTIONS.Config.deleteTriggerContent', { + trigger: game.i18n.localize(trigger.label) + }) + }); + + if (!confirmed) return; + + 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) }); + } + + static async expandTrigger(_event, button) { + const index = Number.parseInt(button.dataset.index); + const toggle = (element, codeMirror) => { + codeMirror.classList.toggle('revealed'); + const button = element.querySelector('a > i'); + button.classList.toggle('fa-angle-up'); + button.classList.toggle('fa-angle-down'); + }; + + const fieldset = button.closest('fieldset'); + const codeMirror = fieldset.querySelector('.code-mirror-wrapper'); + toggle(fieldset, codeMirror); + + if (this.openTrigger !== null && this.openTrigger !== index) { + const previouslyExpanded = fieldset + .closest(`section`) + .querySelector(`fieldset[data-index="${this.openTrigger}"]`); + const codeMirror = previouslyExpanded.querySelector('.code-mirror-wrapper'); + toggle(previouslyExpanded, codeMirror); + this.openTrigger = index; + } else if (this.openTrigger === index) { + this.openTrigger = null; + } else { + this.openTrigger = index; + } + } + /** 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..35609bd1 --- /dev/null +++ b/module/config/triggerConfig.mjs @@ -0,0 +1,42 @@ +/* hints and returns are intentionally not translated. They are programatical terms and best understood in english */ +export const triggers = { + dualityRoll: { + id: 'dualityRoll', + usesActor: true, + args: ['roll', 'actor'], + label: 'DAGGERHEART.CONFIG.Triggers.dualityRoll.label', + hint: 'this: Action, roll: DhRoll, actor: DhActor', + returns: '{ updates: [{ key, value, total }] }' + }, + fearRoll: { + id: 'fearRoll', + usesActor: true, + args: ['roll', 'actor'], + label: 'DAGGERHEART.CONFIG.Triggers.fearRoll.label', + hint: 'this: Action, roll: DhRoll, actor: DhActor', + returns: '{ updates: [{ key, value, total }] }' + }, + postDamageReduction: { + id: 'postDamageReduction', + usesActor: true, + args: ['damageUpdates', 'actor'], + label: 'DAGGERHEART.CONFIG.Triggers.postDamageReduction.label', + hint: 'damageUpdates: ResourceUpdates, actor: DhActor', + returns: '{ updates: [{ originActor: this.actor, updates: [{ key, value, total }] }] }' + } +}; + +export const triggerActorTargetType = { + any: { + id: 'any', + label: 'DAGGERHEART.CONFIG.TargetTypes.any' + }, + self: { + id: 'self', + label: 'DAGGERHEART.CONFIG.TargetTypes.self' + }, + other: { + id: 'other', + label: 'DAGGERHEART.CONFIG.TargetTypes.other' + } +}; 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..8378ea19 --- /dev/null +++ b/module/data/fields/triggerField.mjs @@ -0,0 +1,24 @@ +export default class TriggerField extends foundry.data.fields.SchemaField { + constructor(context) { + super( + { + trigger: new foundry.data.fields.StringField({ + nullable: false, + blank: false, + initial: CONFIG.DH.TRIGGER.triggers.dualityRoll.id, + choices: CONFIG.DH.TRIGGER.triggers, + label: 'DAGGERHEART.CONFIG.Triggers.triggerType' + }), + triggeringActorType: new foundry.data.fields.StringField({ + nullable: false, + blank: false, + initial: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id, + choices: CONFIG.DH.TRIGGER.triggerActorTargetType, + label: 'DAGGERHEART.CONFIG.Triggers.triggeringActorType' + }), + 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..60d29792 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,30 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { return data; } + 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 + ); + } + } + } + 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/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index 2aec990f..3376b153 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -173,6 +173,13 @@ export default class DhAutomation extends foundry.abstract.DataModel { label: 'DAGGERHEART.GENERAL.player.plurial' }) }) + }), + triggers: new fields.SchemaField({ + enabled: new fields.BooleanField({ + nullable: false, + initial: true, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.triggers.enabled.label' + }) }) }; } 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/module/documents/actor.mjs b/module/documents/actor.mjs index f6666a5e..1a4153ad 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -646,6 +646,19 @@ export default class DhpActor extends Actor { } } + const results = await game.system.registeredTriggers.runTrigger( + CONFIG.DH.TRIGGER.triggers.postDamageReduction.id, + this, + updates, + this + ); + + if (results?.length) { + const resourceMap = new ResourceUpdateMap(results[0].originActor); + for (var result of results) resourceMap.addResources(result.updates); + resourceMap.updateResources(); + } + updates.forEach( u => (u.value = diff --git a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json index bd364e6f..95f42c06 100644 --- a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json +++ b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json @@ -80,7 +80,14 @@ }, "name": "Clear Stress", "img": "icons/magic/symbols/rune-sigil-black-pink.webp", - "range": "" + "range": "", + "triggers": [ + { + "trigger": "dualityRoll", + "triggeringActorType": "self", + "command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n
${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}
\n
${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}
\n
\n \n \n
\n
`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;" + } + ] } }, "originItemType": null, diff --git a/src/packs/domains/domainCard_Ferocity_jSQsSP61CX4MhSN7.json b/src/packs/domains/domainCard_Ferocity_jSQsSP61CX4MhSN7.json index 9e46e6ba..78593c62 100644 --- a/src/packs/domains/domainCard_Ferocity_jSQsSP61CX4MhSN7.json +++ b/src/packs/domains/domainCard_Ferocity_jSQsSP61CX4MhSN7.json @@ -17,7 +17,16 @@ "description": "

When you cause an adversary to mark 1 or more Hit Points, you can spend 2 Hope to increase your Evasion by the number of Hit Points they marked. This bonus lasts until after the next attack made against you.

", "chatDisplay": true, "actionType": "action", - "cost": [], + "cost": [ + { + "scalable": false, + "key": "hope", + "value": 2, + "itemId": null, + "step": null, + "consumeOnSuccess": false + } + ], "uses": { "value": null, "max": "", @@ -30,8 +39,15 @@ "amount": null }, "name": "Spend Hope", - "img": "icons/skills/melee/maneuver-daggers-paired-orange.webp", - "range": "" + "img": "icons/skills/melee/maneuver-sword-katana-yellow.webp", + "range": "", + "triggers": [ + { + "trigger": "postDamageReduction", + "triggeringActorType": "other", + "command": "/* Check if sufficient hope */\nif (this.actor.system.resources.hope.value < 2) return;\n\n/* Check if hit point damage was dealt */\nconst hpDamage = damageUpdates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);\nif (hpDamage.value < 0) return;\n\n/* Dialog to give player choice */\nconst confirmed = await foundry.applications.api.DialogV2.confirm({\n window: { title: this.item?.name ?? '' },\n content: game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.ferocityContent', { bonus: hpDamage.value }),\n});\n\nif (!confirmed) return;\n\n/* Create the effect */\nthis.actor.createEmbeddedDocuments('ActiveEffect', [{\n name: this.item.name,\n img: 'icons/skills/melee/maneuver-sword-katana-yellow.webp',\n description: game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.ferocityEffectDescription', { bonus: hpDamage.value }),\n changes: [{ key: 'system.evasion', mode: 2, value: hpDamage.value }]\n}]);\n\n/* Update hope */\nreturn { updates: [{ \n originActor: this.actor, \n updates: [{\n key: CONFIG.DH.GENERAL.healingTypes.hope.id,\n value: -2,\n total: 2\n }] \n}]}" + } + ] } }, "attribution": { diff --git a/styles/less/sheets/actions/actions.less b/styles/less/sheets/actions/actions.less new file mode 100644 index 00000000..6796006c --- /dev/null +++ b/styles/less/sheets/actions/actions.less @@ -0,0 +1,55 @@ +.application.daggerheart.dh-style.action-config { + .trigger-data { + width: 100%; + display: flex; + justify-content: space-between; + gap: 8px; + + .trigger-data-inner { + flex: 1; + display: flex; + flex-direction: column; + + select { + flex: 1; + } + + .select-section { + flex: 1; + display: flex; + gap: 8px; + } + + .programmer-section { + flex: 3; + display: flex; + flex-direction: column; + + .hint-section { + display: flex; + gap: 4px; + + .hint { + flex: 1; + flex-wrap: wrap; + } + } + } + } + + .expand-trigger { + font-size: 18px; + } + } + + .code-mirror-wrapper { + width: 100%; + height: 0; + min-height: 0; + transition: height 0.1s ease-in-out; + + &.revealed { + height: 300px; + } + } +} diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index 1de1b055..44a6aa4d 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -1,3 +1,5 @@ +@import './actions/actions.less'; + @import './actors/actor-sheet-shared.less'; @import './actors/adversary/actions.less'; diff --git a/templates/settings/automation-settings/roll.hbs b/templates/settings/automation-settings/roll.hbs index 5769bf61..dc65f8ae 100644 --- a/templates/settings/automation-settings/roll.hbs +++ b/templates/settings/automation-settings/roll.hbs @@ -19,4 +19,15 @@ {{/each}} + +
+ + {{localize "DAGGERHEART.SETTINGS.Automation.trigger.title"}} + + +
+ {{formGroup settingFields.schema.fields.triggers.fields.enabled value=settingFields.triggers.enabled localize=true}} +

{{localize "DAGGERHEART.SETTINGS.Automation.FIELDS.triggers.enabled.hint"}}

+
+
\ No newline at end of file diff --git a/templates/sheets-settings/action-settings/trigger.hbs b/templates/sheets-settings/action-settings/trigger.hbs new file mode 100644 index 00000000..b048461e --- /dev/null +++ b/templates/sheets-settings/action-settings/trigger.hbs @@ -0,0 +1,37 @@ +
+ + + {{#each @root.triggers as |trigger index|}} +
+ + +
+
+
+ {{formGroup @root.fields.triggers.element.fields.trigger value=trigger.trigger name=(concat "triggers." index ".trigger") blank=false localize=true}} + {{#if trigger.usesActor}}{{formGroup @root.fields.triggers.element.fields.triggeringActorType value=trigger.triggeringActorType name=(concat "triggers." index ".triggeringActorType") blank=false localize=true}}{{/if}} +
+
+
+ {{localize "Context: "}} + {{localize trigger.hint}} +
+
+ {{localize "Return: "}} + {{localize trigger.returns}} +
+
+
+ +
+ +
+ {{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