Added ferocity trigger

This commit is contained in:
WBHarry 2026-01-05 02:17:29 +01:00
parent ce2e5871bc
commit 4b86bb229a
11 changed files with 165 additions and 42 deletions

View file

@ -386,11 +386,11 @@ class RegisteredTriggers extends Map {
super();
}
async registerTriggers(trigger, actor, uuid, commands) {
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, commands });
this.get(trigger).set(uuid, { actor, triggeringActorType, commands });
}
async runTrigger(trigger, currentActor, ...args) {
@ -400,15 +400,19 @@ class RegisteredTriggers extends Map {
const dualityTrigger = this.get(trigger);
if (dualityTrigger) {
for (let { actor, commands } of dualityTrigger.values()) {
if (currentActor?.uuid !== actor) continue;
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(CONFIG.DH.TRIGGER.triggers[trigger].label);
const triggerName = game.i18n.localize(triggerData.label);
ui.notifications.error(
game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', {
trigger: triggerName,

View file

@ -1145,7 +1145,8 @@
"any": "Any",
"friendly": "Friendly",
"hostile": "Hostile",
"self": "Self"
"self": "Self",
"other": "Other"
},
"TemplateTypes": {
"circle": "Circle",
@ -1220,17 +1221,25 @@
}
},
"Triggers": {
"postDamageReduction": {
"label": "After Damage Reduction"
},
"preDamageReduction": {
"label": "Before Damage Reduction"
},
"dualityRoll": {
"label": "Duality Roll",
"hint": "this: Action, roll: DhRoll, actor: DhActor"
"label": "Duality Roll"
},
"fearRoll": {
"label": "Fear Roll",
"hint": "this: Action, roll: DhRoll, actor: DhActor"
"label": "Fear Roll"
},
"triggerTexts": {
"strangePatternsContent": "Increase Hope or clear stress?"
"strangePatternsContent": "Increase Hope or clear stress?",
"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": {

View file

@ -127,12 +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.triggerOptions = CONFIG.DH.TRIGGER.triggers;
context.triggers = context.source.triggers.map((trigger, index) => ({
...trigger,
hint: CONFIG.DH.TRIGGER.triggers[trigger.trigger].hint,
revealed: this.openTrigger === index
}));
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 = [
@ -248,7 +252,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
static addTrigger() {
const data = this.action.toObject();
data.triggers.push({ trigger: CONFIG.DH.TRIGGER.triggers.dualityRoll.id });
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) });
}

View file

@ -1,12 +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: 'DAGGERHEART.CONFIG.Triggers.dualityRoll.hint'
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: 'DAGGERHEART.CONFIG.Triggers.fearRoll.hint'
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'
}
};

View file

@ -6,7 +6,15 @@ export default class TriggerField extends foundry.data.fields.SchemaField {
nullable: false,
blank: false,
initial: CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
choices: CONFIG.DH.TRIGGER.triggers
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 })
},

View file

@ -139,15 +139,19 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
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 fn = new foundry.utils.AsyncFunction('roll', 'actor', `{${trigger.command}\n}`);
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
);

View file

@ -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 =

View file

@ -84,6 +84,7 @@
"triggers": [
{
"trigger": "dualityRoll",
"triggeringActorType": "self",
"command": "/* 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 diceMatch = dice.includes(resource);\n\nif (!diceMatch) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst choice = await foundry.applications.api.DialogV2.wait({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContent'),\n buttons: [\n {\n action: 'hope',\n label: game.i18n.localize('DAGGERHEART.GENERAL.hope'),\n icon: 'fa-solid fa-hands-holding'\n },\n {\n action: 'stress',\n label: game.i18n.localize('DAGGERHEART.GENERAL.stress'),\n icon: 'fa-solid fa-bolt-lightning'\n }\n ]\n});\n \nif (!choice) return;\n\n/* Return resource update according to choice */\nconst isHope = choice === 'hope';\nconst updates = [{ key: choice, value: isHope ? 1 : -1, total: isHope ? -1 : 1, enabled: true }];\nreturn { updates };"
}
]

View file

@ -17,7 +17,16 @@
"description": "<p class=\"Body-Foundation\">When you cause an adversary to mark 1 or more Hit Points, you can <strong>spend 2 Hope</strong> to increase your Evasion by the number of Hit Points they marked. This bonus lasts until after the next attack made against you.</p>",
"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": {

View file

@ -2,23 +2,43 @@
.trigger-data {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
select {
.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;
}
}
}
}
.hint-section {
flex: 3;
display: flex;
align-items: center;
gap: 4px;
.hint {
flex: 1;
flex-wrap: wrap;
}
.expand-trigger {
font-size: 18px;
}
}

View file

@ -10,12 +10,23 @@
<legend><a data-action="removeTrigger" data-index="{{index}}"><i class="fa-solid fa-trash"></i></a></legend>
<div class="trigger-data">
{{formInput @root.fields.triggers.element.fields.trigger value=trigger.trigger name=(concat "triggers." index ".trigger") blank=false localize=true}}
<div class="hint-section">
<strong>{{localize "Context: "}}</strong>
<span class="hint">{{localize trigger.hint}}</span>
<div class="trigger-data-inner">
<div class="select-section">
{{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}}
</div>
<div class="programmer-section">
<div class="hint-section">
<strong>{{localize "Context: "}}</strong>
<span class="hint">{{localize trigger.hint}}</span>
</div>
<div class="hint-section">
<strong>{{localize "Return: "}}</strong>
<span class="hint">{{localize trigger.returns}}</span>
</div>
</div>
</div>
<a data-action="expandTrigger" data-index="{{index}}"><i class="fa-solid fa-angle-down"></i></a>
<a class="expand-trigger" data-action="expandTrigger" data-index="{{index}}"><i class="fa-solid fa-angle-down"></i></a>
</div>
<div class="code-mirror-wrapper {{#if trigger.revealed}}revealed{{/if}}">