From dbb08fec8c73d672344c2e3f33ef96a73fba90f8 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 28 Apr 2026 20:30:32 +0200 Subject: [PATCH 01/72] Initial --- lang/en.json | 3 +- .../sheets-configs/action-base-config.mjs | 35 ++++++++++++++++ module/data/fields/action/damageField.mjs | 40 ++++++++++++++++--- styles/less/sheets/actions/actions.less | 19 +++++++++ .../action-settings/configuration.hbs | 4 +- .../action-settings/effect.hbs | 16 ++++++-- .../sheets-settings/action-settings/range.hbs | 8 ++++ 7 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 templates/sheets-settings/action-settings/range.hbs diff --git a/lang/en.json b/lang/en.json index b4b1410e9..0d09acf96 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2347,7 +2347,8 @@ "triggers": "Triggers", "deathMoves": "Deathmoves", "sources": "Sources", - "packs": "Packs" + "packs": "Packs", + "range": "Range" }, "Tiers": { "singular": "Tier", diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 7406b0847..a20cc2515 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -62,6 +62,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) id: 'configuration', template: 'systems/daggerheart/templates/sheets-settings/action-settings/configuration.hbs' }, + configuration: { + id: 'range', + template: 'systems/daggerheart/templates/sheets-settings/action-settings/range.hbs' + }, effect: { id: 'effect', template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs' @@ -89,6 +93,14 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) icon: null, label: 'DAGGERHEART.GENERAL.Tabs.configuration' }, + range: { + active: false, + cssClass: '', + group: 'primary', + id: 'range', + icon: null, + label: 'DAGGERHEART.GENERAL.Tabs.range' + }, effect: { active: false, cssClass: '', @@ -107,6 +119,25 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) } }; + static OUTCOME_TABS = { + successWithHope: { + active: true, + cssClass: '', + group: 'outcomes', + id: 'successWithHope', + icon: null, + label: 'Success With Hope' + }, + successWithFear: { + active: false, + cssClass: '', + group: 'outcomes', + id: 'successWithFear', + icon: null, + label: 'Success With Fear' + }, + }; + static CLEAN_ARRAYS = ['cost', 'effects', 'summon']; _getTabs(tabs) { @@ -155,6 +186,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.openSection = this.openSection; context.tabs = this._getTabs(this.constructor.TABS); + + context.outcomeTabs = this._getTabs(this.constructor.OUTCOME_TABS); + context.outcomeData = context.outcomeTabs.successWithHope.active ? context.source.damage : context.source.damage.altOutcomes.successWithFear; + context.config = CONFIG.DH; if (this.action.damage) { context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length; diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 30a5ad7c4..ba8277ef4 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -4,6 +4,15 @@ import IterableTypedObjectField from '../iterableTypedObjectField.mjs'; const fields = foundry.data.fields; +const getDamageBaseFields = () => ({ + parts: new IterableTypedObjectField(DHDamageData), + includeBase: new fields.BooleanField({ + initial: false, + label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label' + }), + direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }), +}); + export default class DamageField extends fields.SchemaField { /** * Action Workflow order @@ -13,12 +22,13 @@ export default class DamageField extends fields.SchemaField { /** @inheritDoc */ constructor(options, context = {}) { const damageFields = { - parts: new IterableTypedObjectField(DHDamageData), - includeBase: new fields.BooleanField({ - initial: false, - label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label' + ...getDamageBaseFields(), + altOutcomes: new fields.SchemaField({ + successFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), + failureHope: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), + failureFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), + critical: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }) }), - direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }), groupAttack: new fields.StringField({ choices: CONFIG.DH.GENERAL.groupAttackRange, blank: true, @@ -28,6 +38,12 @@ export default class DamageField extends fields.SchemaField { super(damageFields, options, context); } + getDamageData(outcome) { + if (outcome === 'successHope') return this; + + return this.altOutcomes[outcome].data; + } + /** * Roll Damage/Healing Action Workflow part. * Must be called within Action context or similar. @@ -326,3 +342,17 @@ export class DHDamageData extends DHResourceData { }; } } + +class AltDamageOutcome extends foundry.abstract.DataModel { + static defineSchema() { + return { + copyStandard: new fields.BooleanField({ required: true, initial: true }), + ...getDamageBaseFields(), + /* Stuff */ + } + } + + get data() { + return this.copyStandard ? this.parent : {}; // If not copying, return data from the this alternate outcome + } +} \ No newline at end of file diff --git a/styles/less/sheets/actions/actions.less b/styles/less/sheets/actions/actions.less index 485f8e91b..19912a985 100644 --- a/styles/less/sheets/actions/actions.less +++ b/styles/less/sheets/actions/actions.less @@ -153,4 +153,23 @@ align-items: center; gap: 4px; } + + .inner-tabs { + justify-content: left; + + .inner-tab { + line-height: 1.5; + border: 1px solid light-dark(@beige, @beige); + border-radius: 6px; + padding: 0 4px; + background: light-dark(@beige, @dark-blue); + color: light-dark(@dark-blue, @beige); + + a { + &.active { + + } + } + } + } } diff --git a/templates/sheets-settings/action-settings/configuration.hbs b/templates/sheets-settings/action-settings/configuration.hbs index 8df528c9d..9fb54cc8b 100644 --- a/templates/sheets-settings/action-settings/configuration.hbs +++ b/templates/sheets-settings/action-settings/configuration.hbs @@ -3,8 +3,8 @@ data-group="primary" data-tab="config" > + {{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}} + {{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}} {{> 'systems/daggerheart/templates/actionTypes/uses.hbs' fields=fields.uses.fields source=source.uses}} {{> 'systems/daggerheart/templates/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost costOptions=costOptions}} - {{> 'systems/daggerheart/templates/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}} - {{> 'systems/daggerheart/templates/actionTypes/areas.hbs' fields=fields.areas.element.fields source=source.areas}} \ No newline at end of file diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs index 567cb81c4..aa22a28ee 100644 --- a/templates/sheets-settings/action-settings/effect.hbs +++ b/templates/sheets-settings/action-settings/effect.hbs @@ -1,11 +1,19 @@ -
- {{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}} - {{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}} - {{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage baseFields=fields.damage.fields }}{{/if}} + + + {{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=outcomeData baseFields=fields.damage.fields }}{{/if}} {{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}} {{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}} {{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}} diff --git a/templates/sheets-settings/action-settings/range.hbs b/templates/sheets-settings/action-settings/range.hbs new file mode 100644 index 000000000..273e89c9b --- /dev/null +++ b/templates/sheets-settings/action-settings/range.hbs @@ -0,0 +1,8 @@ +
+ {{> 'systems/daggerheart/templates/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}} + {{> 'systems/daggerheart/templates/actionTypes/areas.hbs' fields=fields.areas.element.fields source=source.areas}} +
\ No newline at end of file From 3c9951966027e3f88e3e68844bdeb278a0d35a7e Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 3 May 2026 19:04:44 +0200 Subject: [PATCH 02/72] Start of different outcomes --- .../sheets-configs/action-base-config.mjs | 26 +++++++++++++------ module/data/fields/action/damageField.mjs | 10 ++++--- templates/actionTypes/damage.hbs | 4 +-- .../action-settings/effect.hbs | 17 +++++++++++- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index a20cc2515..51243f396 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -1,3 +1,4 @@ +import { AltDamageOutcome } from '../../data/fields/action/damageField.mjs'; import { getUnusedDamageTypes } from '../../helpers/utils.mjs'; import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs'; @@ -62,7 +63,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) id: 'configuration', template: 'systems/daggerheart/templates/sheets-settings/action-settings/configuration.hbs' }, - configuration: { + range: { id: 'range', template: 'systems/daggerheart/templates/sheets-settings/action-settings/range.hbs' }, @@ -120,19 +121,19 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) }; static OUTCOME_TABS = { - successWithHope: { + successHope: { active: true, cssClass: '', group: 'outcomes', - id: 'successWithHope', + id: 'successHope', icon: null, label: 'Success With Hope' }, - successWithFear: { + successFear: { active: false, cssClass: '', group: 'outcomes', - id: 'successWithFear', + id: 'successFear', icon: null, label: 'Success With Fear' }, @@ -188,7 +189,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.tabs = this._getTabs(this.constructor.TABS); context.outcomeTabs = this._getTabs(this.constructor.OUTCOME_TABS); - context.outcomeData = context.outcomeTabs.successWithHope.active ? context.source.damage : context.source.damage.altOutcomes.successWithFear; context.config = CONFIG.DH; if (this.action.damage) { @@ -334,9 +334,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } - static addDamage(_event) { + static addDamage(_event, button) { if (!this.action.damage.parts) return; + const outcome = button.dataset.outcome; const choices = getUnusedDamageTypes(this.action._source.damage.parts); const content = new foundry.data.fields.StringField({ label: game.i18n.localize('Damage Type'), @@ -360,7 +361,16 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial; - data.damage.parts[type] = part; + if (outcome === 'successHope') + data.damage.parts[type] = part; + else { + if (!data.damage.altOutcomes[outcome]) { + data.damage.altOutcomes[outcome] = new AltDamageOutcome(); + } + + data.damage.altOutcomes[outcome].parts[type] = part; + } + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); }; diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index ba8277ef4..6c0d2a88a 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -343,16 +343,18 @@ export class DHDamageData extends DHResourceData { } } -class AltDamageOutcome extends foundry.abstract.DataModel { +export class AltDamageOutcome extends foundry.abstract.DataModel { static defineSchema() { return { - copyStandard: new fields.BooleanField({ required: true, initial: true }), + useStandardHitPointDamage: new fields.BooleanField({ required: true, initial: true }), ...getDamageBaseFields(), - /* Stuff */ } } get data() { - return this.copyStandard ? this.parent : {}; // If not copying, return data from the this alternate outcome + return { + ...this.parent, + ...this, + } } } \ No newline at end of file diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 033008402..f1952c780 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -6,7 +6,7 @@ {{else}} {{localize "DAGGERHEART.GENERAL.damage"}} {{/if}} - {{#unless (eq path 'system.attack.')}}{{/unless}} + {{#unless (eq path 'system.attack.')}}{{/unless}}
{{#if @root.hasBaseDamage}} @@ -19,7 +19,7 @@ {{formField baseFields.groupAttack value=source.groupAttack name=(concat path "damage.groupAttack") localize=true classes="select"}} {{/if}}
- + {{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}} {{#each source.parts as |dmg key|}}
diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs index aa22a28ee..ea30eddff 100644 --- a/templates/sheets-settings/action-settings/effect.hbs +++ b/templates/sheets-settings/action-settings/effect.hbs @@ -12,8 +12,23 @@
{{/each}} + + {{#each outcomeTabs as |tab|}} +
+ {{#if (eq tab.id 'successHope')}} + {{#if ../fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=../fields.damage.fields.parts.element.fields source=../source.damage baseFields=../fields.damage.fields outcome=tab.id }}{{/if}} + {{else}} + {{#with (lookup ../fields.damage.fields.altOutcomes.fields tab.id) as |field|}} + {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=field.fields.parts.element.fields source=(lookup ../../source.damage.altOutcomes ../id) baseFields=field.fields outcome=../id }} + {{/with }} + {{/if}} +
+ {{/each}} - {{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=outcomeData baseFields=fields.damage.fields }}{{/if}} {{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}} {{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}} {{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}} From ec404a61d9ddba60869e832cb6614830d4cb095a Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 14 May 2026 18:51:14 +0200 Subject: [PATCH 03/72] More work on actionConfig outcomes --- lang/en.json | 7 + .../sheets-configs/action-base-config.mjs | 124 ++++++++++++++---- .../sheets-configs/action-config.mjs | 25 ++++ module/config/actionConfig.mjs | 19 +++ module/data/fields/action/damageField.mjs | 15 +-- templates/actionTypes/damage.hbs | 42 +++--- .../action-settings/effect.hbs | 6 +- 7 files changed, 180 insertions(+), 58 deletions(-) diff --git a/lang/en.json b/lang/en.json index 3eea7444d..881ad4e88 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1275,6 +1275,13 @@ "diceValue": "Dice Value", "die": "Die" }, + "OutcomeType": { + "successHope": "Success/ Hope", + "successFear": "Success/ Fear", + "failureHope": "Failure/ Hope", + "failureFear": "Failure/ Fear", + "simpleOutcome": "Outcome" + }, "Range": { "self": { "name": "Self", diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 51243f396..88c6d343c 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -10,12 +10,87 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) this.action = action; this.openSection = null; this.openTrigger = this.action.triggers.length > 0 ? 0 : null; + + this.outcomeTabs = DHActionBaseConfig.getOutcomeTabs(action); } get title() { return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}: ${this.action.name}`; } + /* Needs to consider effect altOutcomes aswell */ + static getOutcomeTabs(action) { + const outcomeKeys = [ + 'successHope', + ...Object.keys(action.damage?.altOutcomes ?? {}).filter(key => action.damage.altOutcomes[key]) + ]; + return outcomeKeys.reduce((acc, key, index) => { + acc[key] = { + active: index === 0, + cssClass: '', + group: 'outcomes', + id: key, + icon: null, + label: + outcomeKeys.length === 1 + ? game.i18n.localize('DAGGERHEART.CONFIG.OutcomeType.simpleOutcome') + : game.i18n.localize(CONFIG.DH.ACTIONS.outcomeTypes[key].label) + }; + return acc; + }, {}); + } + + /* Needs to consider effect altOutcomes aswell */ + static selectOutcome(action, callback) { + const choices = Object.entries(CONFIG.DH.ACTIONS.outcomeTypes).reduce((acc, [key, value]) => { + if (key !== 'successHope' && action.damage.altOutcomes[key] === null) + acc.push({ id: key, label: game.i18n.localize(value.label) }); + + return acc; + }, []); + const content = new foundry.data.fields.StringField({ + label: game.i18n.localize('Outcome'), + choices, + required: true + }).toFormGroup( + {}, + { + name: 'outcome', + localize: true, + nameAttr: 'value', + labelAttr: 'label' + } + ).outerHTML; + + const callbackWrapper = (_, button) => { + const choiceIndex = button.form.elements.outcome.value; + callback(choices[choiceIndex]?.id); + }; + + const typeDialog = new foundry.applications.api.DialogV2({ + buttons: [ + foundry.utils.mergeObject( + { + action: 'ok', + label: 'Confirm', + icon: 'fas fa-check', + default: true + }, + { callback: callbackWrapper } + ) + ], + content: content, + rejectClose: false, + modal: false, + window: { + title: game.i18n.localize('Add Outcome') + }, + position: { width: 300 } + }); + + typeDialog.render(true); + } + static DEFAULT_OPTIONS = { tag: 'form', classes: ['daggerheart', 'dh-style', 'action-config', 'dialog', 'max-800'], @@ -31,6 +106,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) addElement: this.addElement, removeElement: this.removeElement, removeTransformActor: this.removeTransformActor, + addOutcome: this.addOutcome, + removeOutcome: this.removeOutcome, editEffect: this.editEffect, addDamage: this.addDamage, removeDamage: this.removeDamage, @@ -120,25 +197,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) } }; - static OUTCOME_TABS = { - successHope: { - active: true, - cssClass: '', - group: 'outcomes', - id: 'successHope', - icon: null, - label: 'Success With Hope' - }, - successFear: { - active: false, - cssClass: '', - group: 'outcomes', - id: 'successFear', - icon: null, - label: 'Success With Fear' - }, - }; - static CLEAN_ARRAYS = ['cost', 'effects', 'summon']; _getTabs(tabs) { @@ -187,8 +245,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.openSection = this.openSection; context.tabs = this._getTabs(this.constructor.TABS); - - context.outcomeTabs = this._getTabs(this.constructor.OUTCOME_TABS); + + context.outcomeTabs = this._getTabs(this.outcomeTabs); + context.allOutcomesAssigned = Object.keys(this.outcomeTabs).length >= 4; context.config = CONFIG.DH; if (this.action.damage) { @@ -338,7 +397,11 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (!this.action.damage.parts) return; const outcome = button.dataset.outcome; - const choices = getUnusedDamageTypes(this.action._source.damage.parts); + const outcomeParts = + outcome === 'successHope' + ? this.action._source.damage.parts + : this.action._source.damage.altOutcomes[outcome].parts; + const choices = getUnusedDamageTypes(outcomeParts); const content = new foundry.data.fields.StringField({ label: game.i18n.localize('Damage Type'), choices, @@ -361,8 +424,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial; - if (outcome === 'successHope') - data.damage.parts[type] = part; + if (outcome === 'successHope') data.damage.parts[type] = part; else { if (!data.damage.altOutcomes[outcome]) { data.damage.altOutcomes[outcome] = new AltDamageOutcome(); @@ -401,9 +463,15 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) static removeDamage(_event, button) { if (!this.action.damage.parts) return; const data = this.action.toObject(); - const key = button.dataset.key; - delete data.damage.parts[key]; - data.damage.parts[`${key}`] = _del; + const { key, outcome } = button.dataset; + if (outcome === 'successHope') { + delete data.damage.parts[key]; + data.damage.parts[`${key}`] = _del; + } else { + delete data.damage.altOutcomes[outcome].parts[key]; + data.damage.altOutcomes[outcome].parts[`${key}`] = _del; + } + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } @@ -494,6 +562,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) } /** Specific implementation in extending classes **/ + static addOutcome(_event) {} + static removeOutcome(_event) {} static async addEffect(_event) {} static removeEffect(_event, _button) {} static editEffect(_event) {} diff --git a/module/applications/sheets-configs/action-config.mjs b/module/applications/sheets-configs/action-config.mjs index ef529e9b0..833c3ebde 100644 --- a/module/applications/sheets-configs/action-config.mjs +++ b/module/applications/sheets-configs/action-config.mjs @@ -1,3 +1,4 @@ +import { AltDamageOutcome } from '../../data/fields/action/damageField.mjs'; import DHActionBaseConfig from './action-base-config.mjs'; export default class DHActionConfig extends DHActionBaseConfig { @@ -5,6 +6,8 @@ export default class DHActionConfig extends DHActionBaseConfig { ...DHActionBaseConfig.DEFAULT_OPTIONS, actions: { ...DHActionBaseConfig.DEFAULT_OPTIONS.actions, + addOutcome: this.addOutcome, + removeOutcome: this.removeOutcome, addEffect: this.addEffect, removeEffect: this.removeEffect, editEffect: this.editEffect @@ -19,6 +22,28 @@ export default class DHActionConfig extends DHActionBaseConfig { return context; } + static addOutcome() { + const data = this.action.toObject(); + + DHActionBaseConfig.selectOutcome(this.action, key => { + if (!key) return; + + data.damage.altOutcomes[key] = new AltDamageOutcome(); + this.outcomeTabs = DHActionBaseConfig.getOutcomeTabs(data); + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + }); + } + + static removeOutcome(_event, button) { + const { outcome } = button.dataset; + const data = this.action.toObject(); + + data.damage.altOutcomes[outcome] = null; + this.outcomeTabs = DHActionBaseConfig.getOutcomeTabs(data); + + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + static async addEffect(event) { const { areaIndex } = event.target.dataset; if (!this.action.effects) return; diff --git a/module/config/actionConfig.mjs b/module/config/actionConfig.mjs index a6c04e180..012ca4c60 100644 --- a/module/config/actionConfig.mjs +++ b/module/config/actionConfig.mjs @@ -122,3 +122,22 @@ export const areaTypes = { label: 'Placed Area' } }; + +export const outcomeTypes = { + successHope: { + key: 'successHope', + label: 'DAGGERHEART.CONFIG.OutcomeType.successHope' + }, + successFear: { + key: 'successFear', + label: 'DAGGERHEART.CONFIG.OutcomeType.successFear' + }, + failureHope: { + key: 'failureHope', + label: 'DAGGERHEART.CONFIG.OutcomeType.failureHope' + }, + failureFear: { + key: 'failureFear', + label: 'DAGGERHEART.CONFIG.OutcomeType.failureFear' + } +}; diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 6c0d2a88a..6da3f1887 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -10,7 +10,7 @@ const getDamageBaseFields = () => ({ initial: false, label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label' }), - direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }), + direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }) }); export default class DamageField extends fields.SchemaField { @@ -26,8 +26,7 @@ export default class DamageField extends fields.SchemaField { altOutcomes: new fields.SchemaField({ successFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), failureHope: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), - failureFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), - critical: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }) + failureFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }) }), groupAttack: new fields.StringField({ choices: CONFIG.DH.GENERAL.groupAttackRange, @@ -347,14 +346,14 @@ export class AltDamageOutcome extends foundry.abstract.DataModel { static defineSchema() { return { useStandardHitPointDamage: new fields.BooleanField({ required: true, initial: true }), - ...getDamageBaseFields(), - } + ...getDamageBaseFields() + }; } get data() { return { ...this.parent, - ...this, - } + ...this + }; } -} \ No newline at end of file +} diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index f1952c780..fef6591eb 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -10,13 +10,13 @@
{{#if @root.hasBaseDamage}} - {{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }} + {{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name=(concat basePath ".includeBase") classes="checkbox" localize=true }} {{/if}} {{#unless (eq @root.source.type 'healing')}} - {{formField baseFields.direct value=source.direct name=(concat path "damage.direct") localize=true classes="checkbox"}} + {{formField baseFields.direct value=source.direct name=(concat path basePath ".direct") localize=true classes="checkbox"}} {{/unless}} {{#if (and @root.isNPC (not (eq path 'system.attack.')))}} - {{formField baseFields.groupAttack value=source.groupAttack name=(concat path "damage.groupAttack") localize=true classes="select"}} + {{formField baseFields.groupAttack value=source.groupAttack name=(concat path basePath ".groupAttack") localize=true classes="select"}} {{/if}}
@@ -27,44 +27,44 @@ {{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}} {{#unless (or dmg.base ../path)}} - + {{/unless}} {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} - {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}} + {{formField ../fields.resultBased value=dmg.resultBased name=(concat ../basePath ".parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}} {{/if}} {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}} - {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path outcomePath=../outcomePath}}
{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}} - {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path}} + {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path outcomePath=../outcomePath}}
{{else}} - {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path outcomePath=../outcomePath}} {{/if}} {{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}} - {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." dmg.applyTo ".type") localize=true}} + {{formField ../fields.type value=dmg.type name=(concat ../path ../basePath ".parts." dmg.applyTo ".type") localize=true}} {{/if}} {{#if ../horde}}
{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}
- - {{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} - {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.dice") classes="inline-child" localize=true}} - {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.bonus") localize=true classes="inline-child"}} + + {{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path ../basePath ".parts." dmg.applyTo ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} + {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path ../basePath ".parts." dmg.applyTo ".valueAlt.dice") classes="inline-child" localize=true}} + {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path ../basePath ".parts." dmg.applyTo ".valueAlt.bonus") localize=true classes="inline-child"}}
{{/if}} - + {{/each}} @@ -72,21 +72,21 @@ {{#*inline "formula"}} {{#unless dmg.base}} - {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." key "." target ".custom.enabled") classes="checkbox" localize=true}} + {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path outcomePath ".parts." key "." target ".custom.enabled") classes="checkbox" localize=true}} {{/unless}} {{#if source.custom.enabled}} - {{formField fields.custom.fields.formula value=source.custom.formula name=(concat path "damage.parts." key "." target ".custom.formula") localize=true}} + {{formField fields.custom.fields.formula value=source.custom.formula name=(concat path outcomePath ".parts." key "." target ".custom.formula") localize=true}} {{else}}
{{#unless @root.isNPC}} - {{formField fields.multiplier value=source.multiplier name=(concat path "damage.parts." key "." target ".multiplier") localize=true}} + {{formField fields.multiplier value=source.multiplier name=(concat path outcomePath ".parts." key "." target ".multiplier") localize=true}} {{/unless}} - {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." key "." target ".flatMultiplier") localize=true }}{{/if}} - {{formField fields.dice value=source.dice name=(concat path "damage.parts." key "." target ".dice") localize=true}} - {{formField fields.bonus value=source.bonus name=(concat path "damage.parts." key "." target ".bonus") localize=true}} + {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path outcomePath ".parts." key "." target ".flatMultiplier") localize=true }}{{/if}} + {{formField fields.dice value=source.dice name=(concat path outcomePath ".parts." key "." target ".dice") localize=true}} + {{formField fields.bonus value=source.bonus name=(concat path outcomePath ".parts." key "." target ".bonus") localize=true}}
{{/if}} {{#if @root.isNPC}} - + {{/if}} {{/inline}} \ No newline at end of file diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs index ea30eddff..ab03b7eac 100644 --- a/templates/sheets-settings/action-settings/effect.hbs +++ b/templates/sheets-settings/action-settings/effect.hbs @@ -9,8 +9,10 @@ {{localize tab.label}} + {{#unless (eq tab.id 'successHope')}}{{/unless}} {{/each}} + {{#each outcomeTabs as |tab|}} @@ -20,10 +22,10 @@ data-tab="{{tab.id}}" > {{#if (eq tab.id 'successHope')}} - {{#if ../fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=../fields.damage.fields.parts.element.fields source=../source.damage baseFields=../fields.damage.fields outcome=tab.id }}{{/if}} + {{#if ../fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=../fields.damage.fields.parts.element.fields source=../source.damage baseFields=../fields.damage.fields outcome=tab.id outcomePath="damage" }}{{/if}} {{else}} {{#with (lookup ../fields.damage.fields.altOutcomes.fields tab.id) as |field|}} - {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=field.fields.parts.element.fields source=(lookup ../../source.damage.altOutcomes ../id) baseFields=field.fields outcome=../id }} + {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=field.fields.parts.element.fields source=(lookup ../../source.damage.altOutcomes ../id) baseFields=field.fields outcome=../id outcomePath=(concat "damage.altOutcomes." tab.id) }} {{/with }} {{/if}}
From e12d5ce851809222ab4ba9648a7637b9df57be50 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 14 May 2026 21:13:33 +0200 Subject: [PATCH 04/72] Changed to use DefaultOutcome and added successHope as an alt outcome --- lang/en.json | 9 ++++++--- .../sheets-configs/action-base-config.mjs | 18 +++++++----------- module/config/actionConfig.mjs | 4 ++++ module/data/fields/action/damageField.mjs | 12 +++++++++++- templates/actionTypes/damage.hbs | 3 +++ .../sheets-settings/action-settings/effect.hbs | 17 ++++++++++------- 6 files changed, 41 insertions(+), 22 deletions(-) diff --git a/lang/en.json b/lang/en.json index fcce504de..283238982 100755 --- a/lang/en.json +++ b/lang/en.json @@ -136,6 +136,8 @@ "criticalThreshold": "Critical Threshold", "includeBase": { "label": "Include Item Damage" }, "groupAttack": { "label": "Group Attack" }, + "inheritDefaultDamage": { "label": "Inherit Default Damage" }, + "inheritDefaultEffects": { "label": "Inherit Default Effects" }, "multiplier": "Multiplier", "saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.", "resultBased": { @@ -1280,11 +1282,11 @@ "die": "Die" }, "OutcomeType": { + "default": "Default Outcome", "successHope": "Success/ Hope", "successFear": "Success/ Fear", "failureHope": "Failure/ Hope", - "failureFear": "Failure/ Fear", - "simpleOutcome": "Outcome" + "failureFear": "Failure/ Fear" }, "Range": { "self": { @@ -2359,7 +2361,8 @@ "deathMoves": "Deathmoves", "sources": "Sources", "packs": "Packs", - "range": "Range" + "range": "Range", + "outcomes": "Outcomes" }, "Tiers": { "singular": "Tier", diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 88c6d343c..49aa36fbc 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -21,7 +21,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) /* Needs to consider effect altOutcomes aswell */ static getOutcomeTabs(action) { const outcomeKeys = [ - 'successHope', + 'default', ...Object.keys(action.damage?.altOutcomes ?? {}).filter(key => action.damage.altOutcomes[key]) ]; return outcomeKeys.reduce((acc, key, index) => { @@ -31,10 +31,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) group: 'outcomes', id: key, icon: null, - label: - outcomeKeys.length === 1 - ? game.i18n.localize('DAGGERHEART.CONFIG.OutcomeType.simpleOutcome') - : game.i18n.localize(CONFIG.DH.ACTIONS.outcomeTypes[key].label) + label: game.i18n.localize(CONFIG.DH.ACTIONS.outcomeTypes[key].label) }; return acc; }, {}); @@ -43,8 +40,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) /* Needs to consider effect altOutcomes aswell */ static selectOutcome(action, callback) { const choices = Object.entries(CONFIG.DH.ACTIONS.outcomeTypes).reduce((acc, [key, value]) => { - if (key !== 'successHope' && action.damage.altOutcomes[key] === null) - acc.push({ id: key, label: game.i18n.localize(value.label) }); + if (action.damage.altOutcomes[key] === null) acc.push({ id: key, label: game.i18n.localize(value.label) }); return acc; }, []); @@ -185,7 +181,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) group: 'primary', id: 'effect', icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.effects' + label: 'DAGGERHEART.GENERAL.Tabs.outcomes' }, trigger: { active: false, @@ -398,7 +394,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) const outcome = button.dataset.outcome; const outcomeParts = - outcome === 'successHope' + outcome === 'default' ? this.action._source.damage.parts : this.action._source.damage.altOutcomes[outcome].parts; const choices = getUnusedDamageTypes(outcomeParts); @@ -424,7 +420,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial; - if (outcome === 'successHope') data.damage.parts[type] = part; + if (outcome === 'default') data.damage.parts[type] = part; else { if (!data.damage.altOutcomes[outcome]) { data.damage.altOutcomes[outcome] = new AltDamageOutcome(); @@ -464,7 +460,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (!this.action.damage.parts) return; const data = this.action.toObject(); const { key, outcome } = button.dataset; - if (outcome === 'successHope') { + if (outcome === 'default') { delete data.damage.parts[key]; data.damage.parts[`${key}`] = _del; } else { diff --git a/module/config/actionConfig.mjs b/module/config/actionConfig.mjs index 012ca4c60..83a2d6698 100644 --- a/module/config/actionConfig.mjs +++ b/module/config/actionConfig.mjs @@ -124,6 +124,10 @@ export const areaTypes = { }; export const outcomeTypes = { + default: { + key: 'default', + label: 'DAGGERHEART.CONFIG.OutcomeType.default' + }, successHope: { key: 'successHope', label: 'DAGGERHEART.CONFIG.OutcomeType.successHope' diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 6da3f1887..abf2f9a68 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -24,6 +24,7 @@ export default class DamageField extends fields.SchemaField { const damageFields = { ...getDamageBaseFields(), altOutcomes: new fields.SchemaField({ + successHope: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), successFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), failureHope: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), failureFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }) @@ -345,7 +346,16 @@ export class DHDamageData extends DHResourceData { export class AltDamageOutcome extends foundry.abstract.DataModel { static defineSchema() { return { - useStandardHitPointDamage: new fields.BooleanField({ required: true, initial: true }), + inheritDefaultDamage: new fields.BooleanField({ + required: true, + initial: true, + label: 'DAGGERHEART.ACTIONS.Settings.inheritDefaultDamage.label' + }), + inheritDefaultEffects: new fields.BooleanField({ + required: true, + initial: true, + label: 'DAGGERHEART.ACTIONS.Settings.inheritDefaultEffects.label' + }), ...getDamageBaseFields() }; } diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index fef6591eb..9e9541de5 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -18,6 +18,9 @@ {{#if (and @root.isNPC (not (eq path 'system.attack.')))}} {{formField baseFields.groupAttack value=source.groupAttack name=(concat path basePath ".groupAttack") localize=true classes="select"}} {{/if}} + {{#unless isDefaultDamage}} + {{formField @root.fields.damage.fields.altOutcomes.fields.successFear.fields.inheritDefaultDamage name=(concat path basePath ".inheritDefaultDamage") localize=true classes="checkbox"}} + {{/unless}} {{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}} diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs index ab03b7eac..3d91fc1ff 100644 --- a/templates/sheets-settings/action-settings/effect.hbs +++ b/templates/sheets-settings/action-settings/effect.hbs @@ -9,7 +9,7 @@ {{localize tab.label}} - {{#unless (eq tab.id 'successHope')}}{{/unless}} + {{#unless (eq tab.id 'default')}}{{/unless}} {{/each}} @@ -21,13 +21,16 @@ data-group="outcomes" data-tab="{{tab.id}}" > - {{#if (eq tab.id 'successHope')}} - {{#if ../fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=../fields.damage.fields.parts.element.fields source=../source.damage baseFields=../fields.damage.fields outcome=tab.id outcomePath="damage" }}{{/if}} - {{else}} - {{#with (lookup ../fields.damage.fields.altOutcomes.fields tab.id) as |field|}} - {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=field.fields.parts.element.fields source=(lookup ../../source.damage.altOutcomes ../id) baseFields=field.fields outcome=../id outcomePath=(concat "damage.altOutcomes." tab.id) }} - {{/with }} + {{#if ../fields.damage}} + {{#if (eq tab.id 'default')}} + {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=../fields.damage.fields.parts.element.fields source=../source.damage baseFields=../fields.damage.fields outcome=tab.id outcomePath="damage" isDefaultDamage="true" }} + {{else}} + {{#with (lookup ../fields.damage.fields.altOutcomes.fields tab.id) as |field|}} + {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=field.fields.parts.element.fields source=(lookup ../../source.damage.altOutcomes ../id) baseFields=field.fields outcome=../id outcomePath=(concat "damage.altOutcomes." tab.id) }} + {{/with }} + {{/if}} {{/if}} + {{/each}} From 12119bfbfc037cd9f5537315a39713f878ebcb8c Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 15 May 2026 09:07:46 +0200 Subject: [PATCH 05/72] Removed inherit --- lang/en.json | 2 -- module/data/fields/action/damageField.mjs | 10 ---------- templates/actionTypes/damage.hbs | 3 --- 3 files changed, 15 deletions(-) diff --git a/lang/en.json b/lang/en.json index 283238982..624ec525e 100755 --- a/lang/en.json +++ b/lang/en.json @@ -136,8 +136,6 @@ "criticalThreshold": "Critical Threshold", "includeBase": { "label": "Include Item Damage" }, "groupAttack": { "label": "Group Attack" }, - "inheritDefaultDamage": { "label": "Inherit Default Damage" }, - "inheritDefaultEffects": { "label": "Inherit Default Effects" }, "multiplier": "Multiplier", "saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.", "resultBased": { diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index abf2f9a68..78c27774e 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -346,16 +346,6 @@ export class DHDamageData extends DHResourceData { export class AltDamageOutcome extends foundry.abstract.DataModel { static defineSchema() { return { - inheritDefaultDamage: new fields.BooleanField({ - required: true, - initial: true, - label: 'DAGGERHEART.ACTIONS.Settings.inheritDefaultDamage.label' - }), - inheritDefaultEffects: new fields.BooleanField({ - required: true, - initial: true, - label: 'DAGGERHEART.ACTIONS.Settings.inheritDefaultEffects.label' - }), ...getDamageBaseFields() }; } diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 9e9541de5..fef6591eb 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -18,9 +18,6 @@ {{#if (and @root.isNPC (not (eq path 'system.attack.')))}} {{formField baseFields.groupAttack value=source.groupAttack name=(concat path basePath ".groupAttack") localize=true classes="select"}} {{/if}} - {{#unless isDefaultDamage}} - {{formField @root.fields.damage.fields.altOutcomes.fields.successFear.fields.inheritDefaultDamage name=(concat path basePath ".inheritDefaultDamage") localize=true classes="checkbox"}} - {{/unless}} {{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}} From 13a9ddba46adee5740effa43051fe8a6e3377c18 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 18:39:44 -0400 Subject: [PATCH 06/72] Update file organization a bit --- .../sheets-configs/action-base-config.mjs | 158 ++++++++---------- .../sheets-configs/action-config.mjs | 8 +- 2 files changed, 77 insertions(+), 89 deletions(-) diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 49aa36fbc..dda9a2b48 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -18,75 +18,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}: ${this.action.name}`; } - /* Needs to consider effect altOutcomes aswell */ - static getOutcomeTabs(action) { - const outcomeKeys = [ - 'default', - ...Object.keys(action.damage?.altOutcomes ?? {}).filter(key => action.damage.altOutcomes[key]) - ]; - return outcomeKeys.reduce((acc, key, index) => { - acc[key] = { - active: index === 0, - cssClass: '', - group: 'outcomes', - id: key, - icon: null, - label: game.i18n.localize(CONFIG.DH.ACTIONS.outcomeTypes[key].label) - }; - return acc; - }, {}); - } - - /* Needs to consider effect altOutcomes aswell */ - static selectOutcome(action, callback) { - const choices = Object.entries(CONFIG.DH.ACTIONS.outcomeTypes).reduce((acc, [key, value]) => { - if (action.damage.altOutcomes[key] === null) acc.push({ id: key, label: game.i18n.localize(value.label) }); - - return acc; - }, []); - const content = new foundry.data.fields.StringField({ - label: game.i18n.localize('Outcome'), - choices, - required: true - }).toFormGroup( - {}, - { - name: 'outcome', - localize: true, - nameAttr: 'value', - labelAttr: 'label' - } - ).outerHTML; - - const callbackWrapper = (_, button) => { - const choiceIndex = button.form.elements.outcome.value; - callback(choices[choiceIndex]?.id); - }; - - const typeDialog = new foundry.applications.api.DialogV2({ - buttons: [ - foundry.utils.mergeObject( - { - action: 'ok', - label: 'Confirm', - icon: 'fas fa-check', - default: true - }, - { callback: callbackWrapper } - ) - ], - content: content, - rejectClose: false, - modal: false, - window: { - title: game.i18n.localize('Add Outcome') - }, - position: { width: 300 } - }); - - typeDialog.render(true); - } - static DEFAULT_OPTIONS = { tag: 'form', classes: ['daggerheart', 'dh-style', 'action-config', 'dialog', 'max-800'], @@ -97,16 +28,11 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) position: { width: 600, height: 'auto' }, actions: { toggleSection: this.toggleSection, - addEffect: this.addEffect, - removeEffect: this.removeEffect, addElement: this.addElement, removeElement: this.removeElement, removeTransformActor: this.removeTransformActor, - addOutcome: this.addOutcome, - removeOutcome: this.removeOutcome, - editEffect: this.editEffect, - addDamage: this.addDamage, - removeDamage: this.removeDamage, + addDamage: this.onAddDamage, + removeDamage: this.onRemoveDamage, editDoc: this.editDoc, addTrigger: this.addTrigger, removeTrigger: this.removeTrigger, @@ -195,6 +121,75 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) static CLEAN_ARRAYS = ['cost', 'effects', 'summon']; + /* Needs to consider effect altOutcomes aswell */ + static getOutcomeTabs(action) { + const outcomeKeys = [ + 'default', + ...Object.keys(action.damage?.altOutcomes ?? {}).filter(key => action.damage.altOutcomes[key]) + ]; + return outcomeKeys.reduce((acc, key, index) => { + acc[key] = { + active: index === 0, + cssClass: '', + group: 'outcomes', + id: key, + icon: null, + label: game.i18n.localize(CONFIG.DH.ACTIONS.outcomeTypes[key].label) + }; + return acc; + }, {}); + } + + /* Needs to consider effect altOutcomes aswell */ + static selectOutcome(action, callback) { + const choices = Object.entries(CONFIG.DH.ACTIONS.outcomeTypes).reduce((acc, [key, value]) => { + if (action.damage.altOutcomes[key] === null) acc.push({ id: key, label: game.i18n.localize(value.label) }); + + return acc; + }, []); + const content = new foundry.data.fields.StringField({ + label: game.i18n.localize('Outcome'), + choices, + required: true + }).toFormGroup( + {}, + { + name: 'outcome', + localize: true, + nameAttr: 'value', + labelAttr: 'label' + } + ).outerHTML; + + const callbackWrapper = (_, button) => { + const choiceIndex = button.form.elements.outcome.value; + callback(choices[choiceIndex]?.id); + }; + + const typeDialog = new foundry.applications.api.DialogV2({ + buttons: [ + foundry.utils.mergeObject( + { + action: 'ok', + label: 'Confirm', + icon: 'fas fa-check', + default: true + }, + { callback: callbackWrapper } + ) + ], + content: content, + rejectClose: false, + modal: false, + window: { + title: game.i18n.localize('Add Outcome') + }, + position: { width: 300 } + }); + + typeDialog.render(true); + } + _getTabs(tabs) { for (const v of Object.values(tabs)) { v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; @@ -389,7 +384,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } - static addDamage(_event, button) { + static onAddDamage(_event, button) { if (!this.action.damage.parts) return; const outcome = button.dataset.outcome; @@ -456,7 +451,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) typeDialog.render(true); } - static removeDamage(_event, button) { + static onRemoveDamage(_event, button) { if (!this.action.damage.parts) return; const data = this.action.toObject(); const { key, outcome } = button.dataset; @@ -557,13 +552,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } - /** Specific implementation in extending classes **/ - static addOutcome(_event) {} - static removeOutcome(_event) {} - static async addEffect(_event) {} - static removeEffect(_event, _button) {} - static editEffect(_event) {} - async close(options) { this.tabGroups.primary = 'base'; await super.close(options); diff --git a/module/applications/sheets-configs/action-config.mjs b/module/applications/sheets-configs/action-config.mjs index 833c3ebde..a4b0a6c4f 100644 --- a/module/applications/sheets-configs/action-config.mjs +++ b/module/applications/sheets-configs/action-config.mjs @@ -6,8 +6,8 @@ export default class DHActionConfig extends DHActionBaseConfig { ...DHActionBaseConfig.DEFAULT_OPTIONS, actions: { ...DHActionBaseConfig.DEFAULT_OPTIONS.actions, - addOutcome: this.addOutcome, - removeOutcome: this.removeOutcome, + addOutcome: this.onAddOutcome, + removeOutcome: this.onRemoveOutcome, addEffect: this.addEffect, removeEffect: this.removeEffect, editEffect: this.editEffect @@ -22,7 +22,7 @@ export default class DHActionConfig extends DHActionBaseConfig { return context; } - static addOutcome() { + static onAddOutcome() { const data = this.action.toObject(); DHActionBaseConfig.selectOutcome(this.action, key => { @@ -34,7 +34,7 @@ export default class DHActionConfig extends DHActionBaseConfig { }); } - static removeOutcome(_event, button) { + static onRemoveOutcome(_event, button) { const { outcome } = button.dataset; const data = this.action.toObject(); From 1af377ba2ce0a48f27decf9db5f012602a158003 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 20:23:59 -0400 Subject: [PATCH 07/72] Cleanup of damage outcomes add/remove --- .../sheets-configs/action-base-config.mjs | 45 ++++++------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index dda9a2b48..7625d0189 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -388,10 +388,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (!this.action.damage.parts) return; const outcome = button.dataset.outcome; - const outcomeParts = - outcome === 'default' - ? this.action._source.damage.parts - : this.action._source.damage.altOutcomes[outcome].parts; + const source = this.action._source; + const outcomeParts = outcome === 'default' ? source.damage.parts : source.damage.altOutcomes[outcome].parts; const choices = getUnusedDamageTypes(outcomeParts); const content = new foundry.data.fields.StringField({ label: game.i18n.localize('Damage Type'), @@ -412,32 +410,23 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) const type = choices[button.form.elements.type.value].value; const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue(); part.applyTo = type; - if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) + if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial; - - if (outcome === 'default') data.damage.parts[type] = part; - else { - if (!data.damage.altOutcomes[outcome]) { - data.damage.altOutcomes[outcome] = new AltDamageOutcome(); - } - - data.damage.altOutcomes[outcome].parts[type] = part; } - + if (outcome !== 'default') data.damage.altOutcomes[outcome] ??= new AltDamageOutcome(); + (outcome === 'default' ? data.damage : data.damage.altOutcomes[outcome]).parts[type] = part; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); }; const typeDialog = new foundry.applications.api.DialogV2({ buttons: [ - foundry.utils.mergeObject( - { - action: 'ok', - label: 'Confirm', - icon: 'fas fa-check', - default: true - }, - { callback: callback } - ) + { + action: 'ok', + label: 'Confirm', + icon: 'fas fa-check', + default: true, + callback + } ], content: content, rejectClose: false, @@ -455,14 +444,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (!this.action.damage.parts) return; const data = this.action.toObject(); const { key, outcome } = button.dataset; - if (outcome === 'default') { - delete data.damage.parts[key]; - data.damage.parts[`${key}`] = _del; - } else { - delete data.damage.altOutcomes[outcome].parts[key]; - data.damage.altOutcomes[outcome].parts[`${key}`] = _del; - } - + const parts = outcome === 'default' ? data.damage.parts : data.damage.altOutcomes[outcome].parts; + parts[key] = _del; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } From 2cc6b2ef8483dbe8148ce7b1bb103231c0ffb24e Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 21:29:27 -0400 Subject: [PATCH 08/72] Rename effects tab to outcomes --- .../applications/sheets-configs/action-base-config.mjs | 10 +++++----- .../action-settings/{effect.hbs => outcomes.hbs} | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename templates/sheets-settings/action-settings/{effect.hbs => outcomes.hbs} (97%) diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 7625d0189..d09e464fe 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -66,9 +66,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) id: 'range', template: 'systems/daggerheart/templates/sheets-settings/action-settings/range.hbs' }, - effect: { - id: 'effect', - template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs' + outcomes: { + id: 'outcomes', + template: 'systems/daggerheart/templates/sheets-settings/action-settings/outcomes.hbs' }, trigger: { id: 'trigger', @@ -101,11 +101,11 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) icon: null, label: 'DAGGERHEART.GENERAL.Tabs.range' }, - effect: { + outcomes: { active: false, cssClass: '', group: 'primary', - id: 'effect', + id: 'outcomes', icon: null, label: 'DAGGERHEART.GENERAL.Tabs.outcomes' }, diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/outcomes.hbs similarity index 97% rename from templates/sheets-settings/action-settings/effect.hbs rename to templates/sheets-settings/action-settings/outcomes.hbs index 3d91fc1ff..442333a41 100644 --- a/templates/sheets-settings/action-settings/effect.hbs +++ b/templates/sheets-settings/action-settings/outcomes.hbs @@ -1,7 +1,7 @@
\ No newline at end of file diff --git a/templates/sheets-settings/environment-settings/features.hbs b/templates/sheets-settings/environment-settings/features.hbs index ecda0e6b3..1cab8cfe9 100644 --- a/templates/sheets-settings/environment-settings/features.hbs +++ b/templates/sheets-settings/environment-settings/features.hbs @@ -3,24 +3,31 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > - -
- {{localize tabs.features.label}} -
    - {{#each @root.features as |feature|}} -
  • - -
    - {{feature.name}} -
    -
    - - -
    -
  • - {{/each}} -
-
+ {{#each featureGroups as |group|}} +
+ + {{group.label}} + + + + +
    + {{#each group.features as |feature|}} + {{> 'daggerheart.inventory-item' + item=feature + type='feature' + actorType=@root.document.type + hideTags=true + hideContextMenu=true + hideResources=true + showActions=false + hideTooltip=true + }} + {{/each}} +
+
+ {{/each}} +
+ {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
\ No newline at end of file diff --git a/templates/sheets-settings/npc-settings/features.hbs b/templates/sheets-settings/npc-settings/features.hbs index 3e0ed6545..1cab8cfe9 100644 --- a/templates/sheets-settings/npc-settings/features.hbs +++ b/templates/sheets-settings/npc-settings/features.hbs @@ -3,27 +3,31 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > - -
- {{localize tabs.features.label}} -
    - {{#each @root.features as |feature|}} -
  • - -
    - {{feature.name}} -
    -
    - - -
    -
  • - {{/each}} -
-
- {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} -
-
+ {{#each featureGroups as |group|}} +
+ + {{group.label}} + + + + +
    + {{#each group.features as |feature|}} + {{> 'daggerheart.inventory-item' + item=feature + type='feature' + actorType=@root.document.type + hideTags=true + hideContextMenu=true + hideResources=true + showActions=false + hideTooltip=true + }} + {{/each}} +
+
+ {{/each}} +
+ {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
\ No newline at end of file diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index 523e93042..f7d22a30d 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -27,17 +27,17 @@ Parameters: >
{{!-- Image --}} -
- - {{#if item.usable}} - {{#if @root.isNPC}} - d20 - {{else}} - 2d12 +
+ + {{#if (and item.usable (ne showActions false))}} + {{#if @root.isNPC}} + d20 + {{else}} + 2d12 + {{/if}} {{/if}} - {{/if}}
{{!-- Name & Tags --}} @@ -46,10 +46,10 @@ Parameters: {{localize item.name}} {{#unless (or noExtensible (not item.system.description))}}{{/unless}} {{!-- Tags Start --}} - {{#if (not ../hideTags)}} + {{#if (not hideTags)}} {{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}} {{#if (eq ../type 'feature')}} - {{#if (and system.featureForm (or (eq @root.document.type "adversary") (eq @root.document.type "environment")))}} + {{#if (and system.featureForm (ne @root.document.type "character"))}}
{{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}}
From 149249199843ccada03ec7857894230f1c97e202 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 22 Jun 2026 00:14:40 +0200 Subject: [PATCH 58/72] [Fix] Reroll Countdown Automation (#2031) * Fixed so that automated countdowns reverse progress from Fear when rerolling dice if the duality result changes * . * Removed overprep * Apply suggestion from @CarlosFdez --------- Co-authored-by: Carlos Fernandez --- module/applications/ui/countdowns.mjs | 14 +++++++------ module/dice/helpers.mjs | 29 ++++++++++++++++++--------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index d559582f7..5cf791003 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -342,29 +342,31 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application * Sends updates of the countdowns to the GM player. Since this is asynchronous, be sure to * update all the countdowns at the same time. * - * @param {...any} progressTypes Countdowns to be updated + * @param {...(string | { type: string; undo?: boolean })} progressTypes Countdowns to be updated */ static async updateCountdowns(...progressTypes) { + progressTypes = progressTypes.map(p => typeof p === 'string' ? { type: p } : p); const { countdownAutomation } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); if (!countdownAutomation) return; const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => { const countdown = countdownSetting.countdowns[key]; - if (progressTypes.indexOf(countdown.progress.type) !== -1 && countdown.progress.current > 0) { - acc.push(key); + const progressData = progressTypes.find(x => x.type === countdown.progress.type); + if (progressData && countdown.progress.current > 0) { + acc[key] = { value: progressData.undo ? 1 : -1 }; } return acc; - }, []); + }, {}); const countdownData = countdownSetting.toObject(); const settings = { ...countdownData, countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => { const countdown = foundry.utils.deepClone(countdownData.countdowns[key]); - if (updatedCountdowns.includes(key)) { - countdown.progress.current -= 1; + if (updatedCountdowns[key]) { + countdown.progress.current += updatedCountdowns[key].value; } acc[key] = countdown; diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs index 5f8a7bbb2..d03970d0f 100644 --- a/module/dice/helpers.mjs +++ b/module/dice/helpers.mjs @@ -1,19 +1,28 @@ import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; export function updateResourcesForDualityReroll(oldDuality, newDuality, actor) { - const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); - if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return; - - const updates = []; const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0); const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0); const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0); - if (hope !== 0) updates.push({ key: 'hope', value: hope, enabled: true }); - if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, enabled: true }); - if (fear !== 0) updates.push({ key: 'fear', value: fear, enabled: true }); + const { hopeFear, countdownAutomation } = + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + + if (game.user.isGM ? hopeFear.gm : hopeFear.players) { + const updates = []; + if (hope !== 0) updates.push({ key: 'hope', value: hope, enabled: true }); + if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, enabled: true }); + if (fear !== 0) updates.push({ key: 'fear', value: fear, enabled: true }) + + const resourceUpdates = new ResourceUpdateMap(actor); + resourceUpdates.addResources(updates); + resourceUpdates.updateResources(); + } - const resourceUpdates = new ResourceUpdateMap(actor); - resourceUpdates.addResources(updates); - resourceUpdates.updateResources(); + if (countdownAutomation && fear !== 0) { + game.system.api.applications.ui.DhCountdowns.updateCountdowns({ + type: CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id, + undo: fear === 1 ? false : true + }); + } } From 8e930259474f631d4923b37d45dade63070e6adb Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 22 Jun 2026 00:24:33 +0200 Subject: [PATCH 59/72] [Fix] Weapon Spellcasting Active Effects (#2032) * . * . * Apply suggestion from @CarlosFdez --------- Co-authored-by: Carlos Fernandez --- .../sheets/api/application-mixin.mjs | 2 +- module/applications/sheets/api/base-actor.mjs | 2 +- module/data/action/baseAction.mjs | 39 +++++++++++++------ module/documents/actor.mjs | 2 +- module/documents/chatMessage.mjs | 2 +- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index d89237df6..98f38f031 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -518,7 +518,7 @@ export default function DHApplicationMixin(Base) { const doc = await getDocFromElement(target), action = doc?.system?.attack ?? doc; const config = action.prepareConfig(event); - config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects( + config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects( this.document, doc ); diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index e65745c0f..007b641b1 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -212,7 +212,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { const doc = await getDocFromElement(target), action = doc?.system?.attack ?? doc; const config = action.prepareConfig(event); - config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects( + config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects( this.document, doc ); diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index ea4361b9b..f568436e0 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -228,7 +228,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel let config = this.prepareConfig(event, configOptions); if (!config) return; - config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item); + config.effects = + await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(this.actor, this.item); if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; @@ -333,27 +334,43 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } /** - * Get the all potentially applicable effects on the actor + * Get the all potentially applicable effects on the actor for the action's RollDialog * @param {DHActor} actor The actor performing the action * @param {DHItem|DhActor} effectParent The parent of the effect * @returns {DhActiveEffect[]} */ - static async getEffects(actor, effectParent) { + static async getActionRelevantEffects(actor, effectParent) { if (!actor) return []; - return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).filter( - effect => { - /* Effects on weapons only ever apply for the weapon itself */ + // Changes on weapon effects are not typically only applicable to show in the roll dialog for the weapon itself + // The exemptions to this rule are listed below + const weaponTransferredEffectKeys = [ + 'system.bonuses.roll.spellcast.bonus' + ]; + + return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).reduce( + (acc, effect) => { + const effectData = effect.toObject(); + /* Effects on weapons only ever apply for the weapon itself, with a few defined exceptions */ 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; + if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) { + effectData.system.changes = + effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); + } + } else if (effectParent?.id !== effect.parent.id) { + effectData.system.changes = + effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); + } } - return !effect.isSuppressed; - } - ); + if (!effect.isSuppressed) { + acc.push(effectData); + } + + return acc; + }, []); } /** diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 1642ed30b..8ef64f655 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -549,7 +549,7 @@ export default class DhpActor extends Actor { headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }), - effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this), + effects: await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(this), roll: { trait: trait, type: 'trait' diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 480f8c694..b555dfca5 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -167,7 +167,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (this.system.action) { 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); + config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(actor, item); await this.system.action.workflow.get('damage')?.execute(config, this._id, true); } } From 329d820aab0ce538ab4c1696afa27b1e4c827ad4 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 22 Jun 2026 00:26:24 +0200 Subject: [PATCH 60/72] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index f5e13a62a..086930740 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.3.4", + "version": "2.4.0", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.4/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.0/system.zip", "authors": [ { "name": "WBHarry" From 9f29229c9455efefbe331abf2c9d6bb55ca99d11 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 23 Jun 2026 02:57:22 -0400 Subject: [PATCH 61/72] Fix resolving formulas in weapon change effects (#2035) --- module/data/action/baseAction.mjs | 42 ++++++++++++++++--------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f568436e0..27383b7a5 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -348,29 +348,31 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel 'system.bonuses.roll.spellcast.bonus' ]; - return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).reduce( - (acc, effect) => { - const effectData = effect.toObject(); - /* Effects on weapons only ever apply for the weapon itself, with a few defined exceptions */ - 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) { - effectData.system.changes = - effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); - } - } else if (effectParent?.id !== effect.parent.id) { - effectData.system.changes = - effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); + const results = []; + const applicableEffects = await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true }); + for (const effect of [...applicableEffects].filter(e => !e.isSuppressed)) { + if (effect.parent.type === 'weapon') { + // Effects on weapons only ever apply for the weapon itself (with a few exceptions) + const restricted = + effect.parent.system.secondary + // Secondary applies only to other primary weapons + ? effectParent?.type !== 'weapon' || effectParent?.system.secondary + // Primary only applies to itself + : effectParent?.id !== effect.parent.id; + if (restricted) { + const sourceChanges = effect._source.system.changes; + const changes = sourceChanges.filter(x => weaponTransferredEffectKeys.includes(x.key)); + if (changes.length) { + results.push(effect.clone({ 'system.changes': changes })); } + continue; } + } + + results.push(effect); + } - if (!effect.isSuppressed) { - acc.push(effectData); - } - - return acc; - }, []); + return results; } /** From f5fa59b3bd6d6dcdd8e3b1f3fda68168cf2c3bec Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 23 Jun 2026 03:40:12 -0400 Subject: [PATCH 62/72] Make prosemirror editor look a bit nicer (#2034) --- styles/less/global/prose-mirror.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index e4b1249f7..fc8e49f92 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -3,6 +3,8 @@ .application.daggerheart { prose-mirror { + --menu-padding: 4px 0px; + --menu-height: calc(var(--menu-button-height) + 8px); height: 100% !important; width: 100%; From 958eaa310c2b33414527b68afbe6b4ecc64f02c3 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 23 Jun 2026 06:22:03 -0400 Subject: [PATCH 63/72] Simplify ActiveEffect sheet tags (#2037) * Simplify ActiveEffect sheet tags * Show origin actor and exclude tag if it is a top level actor tag without origin --- lang/en.json | 3 ++- module/documents/activeEffect.mjs | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lang/en.json b/lang/en.json index a4a5edf00..3f21f5eb1 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2010,7 +2010,8 @@ "Attachments": { "attachHint": "Drop items here to attach them", "transferHint": "If checked, this effect will be applied to any actor that owns this Effect's parent Item. The effect is always applied if this Item is attached to another one." - } + }, + "OriginTag": "Origin: {name}" }, "GENERAL": { "Ability": { diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 0e7f5d1ec..083b39509 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -224,12 +224,13 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { * @returns {string[]} An array of localized tag strings. */ _getTags() { - const tags = [ - `${game.i18n.localize(this.parent.system.metadata.label)}: ${this.parent.name}`, - game.i18n.localize( - this.isTemporary ? 'DAGGERHEART.EFFECTS.Duration.temporary' : 'DAGGERHEART.EFFECTS.Duration.passive' - ) - ]; + const tags = []; + const originActor = DhActiveEffect.#resolveParentDocument(fromUuidSync(this.origin), Actor); + if (originActor && originActor !== this.actor) { + tags.push(_loc('DAGGERHEART.EFFECTS.OriginTag', { name: originActor.name })); + } else if (!(this.parent instanceof Actor)) { + tags.push(`${_loc(this.parent.system.metadata.label)}: ${this.parent.name}`); + } for (const statusId of this.statuses) { const status = CONFIG.statusEffects.find(s => s.id === statusId); From 07b7c8209440dfc350c1b668ab95487d7cfcf799 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 23 Jun 2026 17:26:53 -0400 Subject: [PATCH 64/72] Fetch origin fetch when in compendium (#2042) --- module/documents/activeEffect.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 083b39509..cdfc9a521 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -225,7 +225,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { */ _getTags() { const tags = []; - const originActor = DhActiveEffect.#resolveParentDocument(fromUuidSync(this.origin), Actor); + const originActor = DhActiveEffect.#resolveParentDocument(fromUuidSync(this.origin, { strict: false }), Actor); if (originActor && originActor !== this.actor) { tags.push(_loc('DAGGERHEART.EFFECTS.OriginTag', { name: originActor.name })); } else if (!(this.parent instanceof Actor)) { From ca82cbcf669240ba6034d0200f84781df5e185de Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:28:04 +0200 Subject: [PATCH 65/72] . (#2041) --- module/documents/combat.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index 20996b77b..e74127e97 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -46,7 +46,9 @@ export default class DhpCombat extends Combat { for (let actor of actors) { await actor.createEmbeddedDocuments( 'ActiveEffect', - effects.filter(x => x.effectTargetTypes.includes(actor.type)) + effects + .filter(x => x.effectTargetTypes.includes(actor.type)) + .map(x => foundry.utils.deepClone(x)) ); } } else { From 2c1f52413d60004c8d4933caf586258ff4baf1cd Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 23 Jun 2026 23:33:29 +0200 Subject: [PATCH 66/72] Raised verison --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 086930740..4660a1964 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.4.0", + "version": "2.4.1", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.0/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.1/system.zip", "authors": [ { "name": "WBHarry" From 1ebbad47973b631bb6779340e5cab85dd4532a7a Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 24 Jun 2026 17:37:00 -0400 Subject: [PATCH 67/72] [Fix] quirks involving expanding items and hovering over them (#2033) --- .../sheets/api/application-mixin.mjs | 2 +- module/data/action/baseAction.mjs | 4 + module/data/item/armor.mjs | 8 +- module/data/item/weapon.mjs | 8 +- module/documents/activeEffect.mjs | 4 + module/documents/item.mjs | 4 + styles/less/global/inventory-item.less | 28 +- .../global/partials/inventory-item-V2.hbs | 264 +++++++++--------- 8 files changed, 162 insertions(+), 160 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 98f38f031..0168f46d6 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -603,7 +603,7 @@ export default function DHApplicationMixin(Base) { const doc = await fromUuid(itemUuid); //get inventory-item description element - const descriptionElement = el.querySelector('.invetory-description'); + const descriptionElement = el.querySelector('.inventory-description'); if (!doc || !descriptionElement) continue; // localize the description (idk if it's still necessary) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 27383b7a5..f30087046 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -54,6 +54,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return {}; } + get hasDescription() { + return Boolean(this.description); + } + /** * Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property. * diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 21c56f9aa..15bb620db 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -52,6 +52,10 @@ export default class DHArmor extends AttachableItem { ); } + get itemFeatures() { + return this.armorFeatures; + } + /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; @@ -169,8 +173,4 @@ export default class DHArmor extends AttachableItem { const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`]; return labels; } - - get itemFeatures() { - return this.armorFeatures; - } } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 84e4de7fe..39c0fc8e5 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -113,6 +113,10 @@ export default class DHWeapon extends AttachableItem { ); } + get itemFeatures() { + return this.weaponFeatures; + } + /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; @@ -269,8 +273,4 @@ export default class DHWeapon extends AttachableItem { return labels; } - - get itemFeatures() { - return this.weaponFeatures; - } } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index cdfc9a521..4a9f3cc4b 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -65,6 +65,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { ); } + get hasDescription() { + return Boolean(this.description); + } + /* -------------------------------------------- */ /* Event Handlers */ /* -------------------------------------------- */ diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 32543ebd3..147175382 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -89,6 +89,10 @@ export default class DHItem extends foundry.documents.Item { return !pack?.locked && this.isOwner && isValidType && hasActions; } + get hasDescription() { + return Boolean(this.system.description) || Boolean(this.system.itemFeatures?.length); + } + /** @inheritdoc */ static async createDialog(data = {}, createOptions = {}, options = {}) { const { folders, types, template, context = {}, ...dialogOptions } = options; diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index 3a5a93216..fc73ba958 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -43,16 +43,19 @@ } } + .item-main { + border-radius: 5px; + padding: 2px; + margin: -2px; + } + &:hover { .inventory-item-header .item-label .item-name .expanded-icon { margin-left: 10px; display: inline-block; } - &:has(.inventory-item-content.extensible) { - .inventory-item-header, - .inventory-item-content { - background: light-dark(@dark-blue-40, @golden-40); - } + .item-main { + background: light-dark(@dark-blue-40, @golden-40); } &:has(.inventory-item-content.extended) { .inventory-item-header .item-label .item-name .expanded-icon { @@ -60,19 +63,6 @@ } } } - - &:has(.inventory-item-content.extensible) { - .inventory-item-header { - border-radius: 5px 5px 0 0; - } - .inventory-item-content { - border-radius: 0 0 5px 5px; - } - } - - &:not(:has(.inventory-item-content.extensible)) .inventory-item-header { - border-radius: 5px; - } } .inventory-item-header, @@ -171,7 +161,7 @@ grid-template-rows: 1fr; padding-top: 4px; } - .invetory-description { + .inventory-description { overflow: hidden; h1 { diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index f7d22a30d..775690d49 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -25,146 +25,146 @@ Parameters: data-type="{{type}}" data-item-type="{{item.type}}" data-item-uuid="{{item.uuid}}" data-no-compendium-edit="{{noCompendiumEdit}}" > -
- {{!-- Image --}} -
- - {{#if (and item.usable (ne showActions false))}} - {{#if @root.isNPC}} - d20 - {{else}} - 2d12 - {{/if}} - {{/if}} -
- - {{!-- Name & Tags --}} -
- {{!-- Item Name --}} - {{localize item.name}} {{#unless (or noExtensible (not item.system.description))}}{{/unless}} - - {{!-- Tags Start --}} - {{#if (not hideTags)}} - {{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}} - {{#if (eq ../type 'feature')}} - {{#if (and system.featureForm (ne @root.document.type "character"))}} -
- {{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}} -
+
+
+ {{!-- Image --}} +
+ + {{#if (and item.usable (ne showActions false))}} + {{#if @root.isNPC}} + d20 + {{else}} + 2d12 {{/if}} {{/if}} - {{/ "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs"}} - {{/if}} - - {{!--Tags End --}} -
+
- {{!-- Simple Resource --}} - {{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}} - {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} - {{/if}} - {{#if (or isQuantifiable (or (eq item.system.quantity 0) (gt item.system.quantity 1)))}} -
- -
- {{/if}} + {{!-- Name & Tags --}} +
+ {{!-- Item Name --}} + {{localize item.name}} {{#unless (or noExtensible (not item.hasDescription))}}{{/unless}} - {{!-- Controls --}} - {{#unless hideControls}} -
- {{!-- Toggle/Equip buttons --}} - {{#if @root.editable}} - {{#if (and (eq actorType 'character') (eq type 'weapon'))}} - - - - {{/if}} - {{#if (and (eq actorType 'character') (eq type 'armor'))}} - - - + {{!-- Tags Start --}} + {{#if (not hideTags)}} + {{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}} + {{#if (and (eq ../type 'feature') system.featureForm (ne @root.document.type "character"))}} +
+ {{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}} +
+ {{/if}} + {{/ "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs"}} {{/if}} - {{#if (and (eq type 'domainCard'))}} - - - - {{/if}} - {{#if (and (and (eq type 'effect') (not (eq item.type 'beastform'))))}} - - - - {{/if}} - {{/if}} - {{!-- Send to Chat --}} - {{#if (hasProperty item "toChat")}} - - - + {{!--Tags End --}} +
+ + {{!-- Simple Resource --}} + {{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}} + {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} + {{/if}} + {{#if (or isQuantifiable (or (eq item.system.quantity 0) (gt item.system.quantity 1)))}} +
+ +
{{/if}} - {{!-- Document management buttons or context menu --}} - {{#if (and (not isActor) (not hideContextMenu))}} - - - - {{else if (and @root.editable (not hideModifyControls))}} - - - - {{#if (not isActor)}} - - - - {{else if (eq type 'adversary')}} - - - - {{/if}} + {{!-- Controls --}} + {{#unless hideControls}} +
+ {{!-- Toggle/Equip buttons --}} + {{#if @root.editable}} + {{#if (and (eq actorType 'character') (eq type 'weapon'))}} + + + + {{/if}} + {{#if (and (eq actorType 'character') (eq type 'armor'))}} + + + + {{/if}} + {{#if (and (eq type 'domainCard'))}} + + + + {{/if}} + {{#if (and (and (eq type 'effect') (not (eq item.type 'beastform'))))}} + + + + {{/if}} + {{/if}} + + {{!-- Send to Chat --}} + {{#if (hasProperty item "toChat")}} + + + + {{/if}} + + {{!-- Document management buttons or context menu --}} + {{#if (and (not isActor) (not hideContextMenu))}} + + + + {{else if (and @root.editable (not hideModifyControls))}} + + + + {{#if (not isActor)}} + + + + {{else if (eq type 'adversary')}} + + + + {{/if}} + {{/if}} +
+ {{/unless}} +
+ {{#unless hideDescription}} +
+ {{!-- Description --}} +
+
+ {{/unless}} +
+ {{!-- Dice Resource --}} + {{#if (and (not hideResources) (eq item.system.resource.type 'diceValue'))}} + {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} + {{/if}} + {{!-- Actions Buttons --}} + {{#if (and showActions item.system.actions.size)}} +
+ {{#each item.system.actions as | action |}} +
+ {{#if (and (eq action.type 'beastform') @root.beastformActive)}} + + {{else}} + + {{/if}} + {{#if action.uses.max}} +
+ {{/if}}
- {{/unless}} -
-
- {{!-- Description --}} - {{#unless hideDescription}} -
- {{/unless}} -
- {{!-- Dice Resource --}} - {{#if (and (not hideResources) (eq item.system.resource.type 'diceValue'))}} - {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} - {{/if}} - {{!-- Actions Buttons --}} - {{#if (and showActions item.system.actions.size)}} -
- {{#each item.system.actions as | action |}} -
- {{#if (and (eq action.type 'beastform') @root.beastformActive)}} - - {{else}} - - {{/if}} - {{#if action.uses.max}} -
- - {{/if}} + {{/each}}
- {{/each}} -
- {{/if}} - \ No newline at end of file + {{/if}} + From 9c58f7058e3ba99af32bb6ebfbc4af26b59d91d6 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:22:12 +0200 Subject: [PATCH 68/72] [Fix] V13 Migration Fixes (#2044) * Fixed so that damageParts without an applyTo field is assumed to be hitPoints * Added the applyTo field to the basic attack for Adversaries and Companions * Tentative blindfix to issues of entities being null. Can't replicate it * Added some safety for missing things if someone was on a REALLY old system version and then updated all the way * Moved v13 countdown migration over to a migrateData * . --- module/data/action/baseAction.mjs | 8 +++ module/data/actor/adversary.mjs | 1 + module/data/actor/character.mjs | 1 + module/data/actor/companion.mjs | 1 + module/data/countdowns.mjs | 33 ++++++++++++ module/systemRegistration/migrations.mjs | 68 ++++++------------------ 6 files changed, 61 insertions(+), 51 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f30087046..58be672b0 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -451,7 +451,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel static migrateData(source) { if (source.damage?.parts && Array.isArray(source.damage.parts)) { + let hitPointsExists = source.damage.parts.some(x => x.applyTo === 'hitPoints'); source.damage.parts = source.damage.parts.reduce((acc, part) => { + if (!part.applyTo && hitPointsExists) return acc; + + if (!part.applyTo) { + hitPointsExists = true; + part.applyTo = 'hitPoints'; + } + acc[part.applyTo] = part; return acc; }, {}); diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index d6d0dcdf0..ae17c128a 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -87,6 +87,7 @@ export default class DhpAdversary extends DhCreature { parts: { hitPoints: { type: ['physical'], + applyTo: 'hitPoints', value: { multiplier: 'flat' } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 3b12da6f3..b39c64aa3 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -107,6 +107,7 @@ export default class DhCharacter extends DhCreature { parts: { hitPoints: { type: ['physical'], + applyTo: 'hitPoints', value: { custom: { enabled: true, diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 300bd698d..2ca7fd5bb 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -102,6 +102,7 @@ export default class DhCompanion extends DhCreature { parts: { hitPoints: { type: ['physical'], + applyTo: 'hitPoints', value: { dice: 'd6', multiplier: 'prof' diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 8e55ed317..ffe4d26ba 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -28,6 +28,39 @@ export default class DhCountdowns extends foundry.abstract.DataModel { for (const countdownKey of changedCountdowns) foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey); } + + static migrateData(source) { + const migrateOldCountdowns = (data, type) => { + for (const key of Object.keys(data.countdowns)) { + const countdown = data.countdowns[key]; + source.countdowns[key] = { + ...countdown, + type: type, + ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => { + acc[key] = + countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type; + return acc; + }, {}), + progress: { + ...countdown.progress, + type: countdown.progress.type.value + } + }; + } + + source[type] = null; + }; + + if (source.narrative) { + migrateOldCountdowns(source.narrative, 'narrative'); + } + + if (source.encounter) { + migrateOldCountdowns(source.encounter, 'encounter'); + } + + return super.migrateData(source); + } } export class DhCountdown extends foundry.abstract.DataModel { diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index ec546c925..6971c34c0 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -1,5 +1,4 @@ import { defaultRestOptions } from '../config/generalConfig.mjs'; -import { RefreshType, socketEvent } from './socket.mjs'; export async function runMigrations() { let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion); @@ -153,61 +152,26 @@ export async function runMigrations() { await pack.configure({ locked: true }); } - /* Migrate old countdown structure */ - const countdownSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const getCountdowns = (data, type) => { - return Object.keys(data.countdowns).reduce((acc, key) => { - const countdown = data.countdowns[key]; - acc[key] = { - ...countdown, - type: type, - ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => { - acc[key] = - countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type; - return acc; - }, {}), - progress: { - ...countdown.progress, - type: countdown.progress.type.value - } - }; - - return acc; - }, {}); - }; - - await countdownSettings.updateSource({ - countdowns: { - ...getCountdowns(countdownSettings.narrative, 'narrative'), - ...getCountdowns(countdownSettings.encounter, 'encounter') - } - }); - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSettings); - - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.Countdown } - }); - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); - lastMigrationVersion = '1.2.0'; } if (foundry.utils.isNewerVersion('1.2.7', lastMigrationVersion)) { - const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll'); - const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator); - const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => { - if (!game.actors.some(actor => actor.id === id)) { - acc[id] = _del; - } - return acc; - }, {}); + try { + const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll'); + const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator); + const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => { + if (!game.actors.some(actor => actor.id === id)) { + acc[id] = _del; + } + return acc; + }, {}); - await tagTeam.updateSource({ - initiator: initatorMissing ? null : tagTeam.initiator, - members: missingMembers - }); - await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam); + await tagTeam.updateSource({ + initiator: initatorMissing ? null : tagTeam.initiator, + members: missingMembers + }); + await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam); + } catch { } lastMigrationVersion = '1.2.7'; } @@ -303,6 +267,8 @@ export async function runMigrations() { /* Migrate existing effects modifying armor, creating new Armor Effects instead */ const migrateEffects = async entity => { + if (!entity?.effects) return; + for (const effect of entity.effects) { if (effect.system.changes.every(x => x.key !== 'system.armorScore')) continue; From 8c6a470d84d80ac70966e7b0c985daf396637c70 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 29 Jun 2026 14:25:16 +0200 Subject: [PATCH 69/72] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 4660a1964..0ecc2e75d 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.4.1", + "version": "2.4.2", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.1/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.2/system.zip", "authors": [ { "name": "WBHarry" From 2cc52fae1f0d4358fb4267310c1f44a3af15d210 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 30 Jun 2026 05:18:13 -0400 Subject: [PATCH 70/72] Remove fieldset from top level notes (#2048) --- styles/less/global/global.less | 3 ++ styles/less/global/prose-mirror.less | 4 ++- .../sheets/actors/actor-sheet-shared.less | 31 +++++++++++++++++++ templates/sheets/actors/adversary/notes.hbs | 12 +++---- templates/sheets/actors/environment/notes.hbs | 5 +-- templates/sheets/actors/npc/notes.hbs | 6 ++-- templates/sheets/actors/party/notes.hbs | 11 +++---- 7 files changed, 49 insertions(+), 23 deletions(-) diff --git a/styles/less/global/global.less b/styles/less/global/global.less index c0e7f3fcf..19a9e5193 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -12,6 +12,9 @@ } .daggerheart.dh-style { + /** Not an actual scrollbar width (it can't be configured on all browsers) but actually a compensation value for scrollbar gutter purposes */ + --scrollbar-width: 10px; + * { scrollbar-width: thin; scrollbar-color: light-dark(@dark-blue, @golden) transparent; diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index fc8e49f92..27048ddf1 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -1,5 +1,6 @@ @import '../utils/colors.less'; @import '../utils/fonts.less'; +@import '../utils/mixin.less'; .application.daggerheart { prose-mirror { @@ -12,6 +13,7 @@ background-color: transparent; } .editor-content { + .with-scroll-shadows(); h1 { font-size: var(--font-size-32); } @@ -42,7 +44,7 @@ ul { list-style: disc; } - } + } // Fixes centering and makes it not render over scrollbar &:hover button.toggle:enabled { display: flex; diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 896171036..5eb5b43c0 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -54,6 +54,37 @@ } } + .tab.notes.active { + padding: 0; + margin: 0; + margin-top: -10px; // will be removed once tab-navigation bottom margin is removed on all actor sheets + scrollbar-gutter: unset; + + // Add padding around top level level prosemirrors used for note tabs + > prose-mirror { + @right-padding: calc(16px - var(--scrollbar-width)); + .editor-content { + scrollbar-gutter: stable; + padding-right: @right-padding; + } + &.inactive { + button.toggle { + top: 16px; + } + .editor-content { + padding: 16px @right-padding 4px 16px; + } + } + &.active { + padding: 8px 0 4px 16px; + } + } + + .artist-attribution { + padding-left: 16px; + } + } + .search-section { display: flex; gap: 10px; diff --git a/templates/sheets/actors/adversary/notes.hbs b/templates/sheets/actors/adversary/notes.hbs index a5c3f706d..d329d3180 100644 --- a/templates/sheets/actors/adversary/notes.hbs +++ b/templates/sheets/actors/adversary/notes.hbs @@ -1,13 +1,9 @@
-
- {{localize tabs.notes.label}} - {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} -
- + {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} {{#if (and showAttribution document.system.attribution.artist)}} {{/if}} diff --git a/templates/sheets/actors/environment/notes.hbs b/templates/sheets/actors/environment/notes.hbs index 4f6b131e2..1acf0e930 100644 --- a/templates/sheets/actors/environment/notes.hbs +++ b/templates/sheets/actors/environment/notes.hbs @@ -3,10 +3,7 @@ data-tab='{{tabs.notes.id}}' data-group='{{tabs.notes.group}}' > -
- {{localize tabs.notes.label}} - {{formInput notes.field value=notes.value enriched=notes.value toggled=true}} -
+ {{formInput notes.field value=notes.value enriched=notes.value toggled=true}} {{#if (and showAttribution document.system.attribution.artist)}} diff --git a/templates/sheets/actors/npc/notes.hbs b/templates/sheets/actors/npc/notes.hbs index bc9ac3cf1..7dd5432d6 100644 --- a/templates/sheets/actors/npc/notes.hbs +++ b/templates/sheets/actors/npc/notes.hbs @@ -1,7 +1,7 @@
{{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} diff --git a/templates/sheets/actors/party/notes.hbs b/templates/sheets/actors/party/notes.hbs index 663a484a8..af21b29fb 100644 --- a/templates/sheets/actors/party/notes.hbs +++ b/templates/sheets/actors/party/notes.hbs @@ -1,10 +1,7 @@
-
- {{localize tabs.notes.label}} - {{formInput notes.field value=notes.value enriched=notes.value toggled=true}} -
+ {{formInput notes.field value=notes.value enriched=notes.value toggled=true}}
\ No newline at end of file From 70388dbd736631679fc8c22d17d43f0b8912f7d9 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:05:35 +0200 Subject: [PATCH 71/72] [Fix] SummonAction Actor Choice (#2045) * Fixed logic for picking which actor to summon * Moved getWorldActor function to utils and updated logic * . * Improved logic --- module/data/fields/action/summonField.mjs | 17 ++--------------- module/helpers/utils.mjs | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/module/data/fields/action/summonField.mjs b/module/data/fields/action/summonField.mjs index a2275fa53..6845d2baf 100644 --- a/module/data/fields/action/summonField.mjs +++ b/module/data/fields/action/summonField.mjs @@ -1,4 +1,4 @@ -import { itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs'; +import { getWorldActor, itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs'; import FormulaField from '../formulaField.mjs'; const fields = foundry.data.fields; @@ -42,7 +42,7 @@ export default class DHSummonField extends fields.ArrayField { const count = roll.total; if (!roll.isDeterministic) rolls.push(roll); - const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); + const actor = await getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); /* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */ summon.actor = actor.toObject(); @@ -62,19 +62,6 @@ export default class DHSummonField extends fields.ArrayField { DHSummonField.handleSummon(summonData, this.actor); } - /* Check for any available instances of the actor present in the world if we're missing artwork in the compendium. If none exists, create one. */ - static async getWorldActor(baseActor) { - const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`]; - if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) { - const worldActorCopy = game.actors.find(x => x.name === baseActor.name); - if (worldActorCopy) return worldActorCopy; - - return await game.system.api.documents.DhpActor.create(baseActor.toObject()); - } - - return baseActor; - } - static async handleSummon(summonData, actionActor) { await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation }); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 6467edd72..cb79a76b8 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -889,4 +889,27 @@ export async function triggerChatRollFx(rolls, options = { whisper: false, blind export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) { const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm; +} + +export async function getWorldActor(baseActor) { + if (baseActor.inCompendium) { + const worldActorCopy = game.actors.find(x => + x._stats.compendiumSource === baseActor.uuid && + (!x.prototypeToken.actorLink || x.name === baseActor.name) + ); + + if (worldActorCopy) + return worldActorCopy; + + const baseActorData = baseActor; + return await game.system.api.documents.DhpActor.create({ + ...baseActorData, + _stats: { + ...baseActorData._stats, + compendiumSource: baseActor.uuid + } + }); + } + + return baseActor; } \ No newline at end of file From 8a6d9a6369f608550a9382731837004d03a106c4 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 30 Jun 2026 14:48:52 -0400 Subject: [PATCH 72/72] Lift definition of alt outcomes up one step (#1892) --- .../sheets-configs/action-base-config.mjs | 29 +++++++++---------- .../sheets-configs/action-config.mjs | 11 ++----- module/data/action/altOutcome.mjs | 19 ++++++++++++ module/data/action/damageAction.mjs | 15 ++++++++++ module/data/fields/action/damageField.mjs | 23 +-------------- templates/actionTypes/damage.hbs | 20 ++++++------- .../action-settings/outcomes.hbs | 17 ++++++----- 7 files changed, 71 insertions(+), 63 deletions(-) create mode 100644 module/data/action/altOutcome.mjs diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 33f4d9cb0..5a92435cc 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -1,4 +1,4 @@ -import { AltDamageOutcome } from '../../data/fields/action/damageField.mjs'; +import { AltOutcome } from '../../data/action/altOutcome.mjs'; import { getUnusedDamageTypes } from '../../helpers/utils.mjs'; import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs'; @@ -10,8 +10,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) this.action = action; this.openSection = null; this.openTrigger = this.action.triggers.length > 0 ? 0 : null; - - this.outcomeTabs = DHActionBaseConfig.getOutcomeTabs(action); } get title() { @@ -125,7 +123,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) static getOutcomeTabs(action) { const outcomeKeys = [ 'default', - ...Object.keys(action.damage?.altOutcomes ?? {}).filter(key => action.damage.altOutcomes[key]) + ...Object.keys(action.altOutcomes ?? {}).filter(key => action.altOutcomes[key]) ]; return outcomeKeys.reduce((acc, key, index) => { acc[key] = { @@ -134,7 +132,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) group: 'outcomes', id: key, icon: null, - label: game.i18n.localize(CONFIG.DH.ACTIONS.outcomeTypes[key].label) + label: game.i18n.localize(CONFIG.DH.ACTIONS.outcomeTypes[key].label), + source: key === 'default' ? action._source : action._source.altOutcomes[key], + fields: key === 'default' ? action.schema.fields : action.schema.fields.altOutcomes.fields[key].fields, + getBasePath: path => (key === 'default' ? path : ['altOutcomes', key, path].join('.')) }; return acc; }, {}); @@ -143,7 +144,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) /* Needs to consider effect altOutcomes aswell */ static selectOutcome(action, callback) { const choices = Object.entries(CONFIG.DH.ACTIONS.outcomeTypes).reduce((acc, [key, value]) => { - if (action.damage.altOutcomes[key] === null) acc.push({ id: key, label: game.i18n.localize(value.label) }); + if (action.altOutcomes[key] === null) acc.push({ id: key, label: game.i18n.localize(value.label) }); return acc; }, []); @@ -237,8 +238,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.openSection = this.openSection; context.tabs = this._getTabs(this.constructor.TABS); - context.outcomeTabs = this._getTabs(this.outcomeTabs); - context.allOutcomesAssigned = Object.keys(this.outcomeTabs).length >= 4; + context.outcomeTabs = this._getTabs(DHActionBaseConfig.getOutcomeTabs(this.action)); + context.allOutcomesAssigned = Object.keys(context.outcomeTabs).length >= 4; context.config = CONFIG.DH; if (this.action.damage) { @@ -388,9 +389,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (!this.action.damage.parts) return; const outcome = button.dataset.outcome; - const source = this.action._source; - const outcomeParts = outcome === 'default' ? source.damage.parts : source.damage.altOutcomes[outcome].parts; - const choices = getUnusedDamageTypes(outcomeParts); + const outcomeData = outcome === 'default' ? this.action._source : this.action._source.altOutcomes[outcome]; + const choices = getUnusedDamageTypes(outcomeData.damage.parts); const content = new foundry.data.fields.StringField({ label: game.i18n.localize('Damage Type'), choices, @@ -413,8 +413,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial; } - if (outcome !== 'default') data.damage.altOutcomes[outcome] ??= new AltDamageOutcome(); - (outcome === 'default' ? data.damage : data.damage.altOutcomes[outcome]).parts[type] = part; + if (outcome !== 'default') data.altOutcomes[outcome] ??= new AltOutcome(); + (outcome === 'default' ? data : data.altOutcomes[outcome]).damage.parts[type] = part; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); }; @@ -444,8 +444,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (!this.action.damage.parts) return; const data = this.action.toObject(); const { key, outcome } = button.dataset; - const parts = outcome === 'default' ? data.damage.parts : data.damage.altOutcomes[outcome].parts; - parts[key] = _del; + (outcome === 'default' ? data : data.altOutcomes[outcome]).damage.parts[key] = _del; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } diff --git a/module/applications/sheets-configs/action-config.mjs b/module/applications/sheets-configs/action-config.mjs index a4b0a6c4f..444a60fee 100644 --- a/module/applications/sheets-configs/action-config.mjs +++ b/module/applications/sheets-configs/action-config.mjs @@ -1,4 +1,4 @@ -import { AltDamageOutcome } from '../../data/fields/action/damageField.mjs'; +import { AltOutcome } from '../../data/action/altOutcome.mjs'; import DHActionBaseConfig from './action-base-config.mjs'; export default class DHActionConfig extends DHActionBaseConfig { @@ -27,9 +27,7 @@ export default class DHActionConfig extends DHActionBaseConfig { DHActionBaseConfig.selectOutcome(this.action, key => { if (!key) return; - - data.damage.altOutcomes[key] = new AltDamageOutcome(); - this.outcomeTabs = DHActionBaseConfig.getOutcomeTabs(data); + data.altOutcomes[key] = new AltOutcome(); this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); }); } @@ -37,10 +35,7 @@ export default class DHActionConfig extends DHActionBaseConfig { static onRemoveOutcome(_event, button) { const { outcome } = button.dataset; const data = this.action.toObject(); - - data.damage.altOutcomes[outcome] = null; - this.outcomeTabs = DHActionBaseConfig.getOutcomeTabs(data); - + data.altOutcomes[outcome] = null; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } diff --git a/module/data/action/altOutcome.mjs b/module/data/action/altOutcome.mjs new file mode 100644 index 000000000..95f75f731 --- /dev/null +++ b/module/data/action/altOutcome.mjs @@ -0,0 +1,19 @@ +import { getDamageBaseFields } from '../fields/action/damageField.mjs'; + +const fields = foundry.data.fields; + +export class AltOutcome extends foundry.abstract.DataModel { + static defineSchema() { + return { + damage: new fields.SchemaField(getDamageBaseFields()) + // todo: add effects + }; + } + + get data() { + return { + ...this.parent, + ...this + }; + } +} diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 517355437..f78141816 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -1,8 +1,23 @@ +import { AltOutcome } from './altOutcome.mjs'; import DHBaseAction from './baseAction.mjs'; +const fields = foundry.data.fields; + export default class DHDamageAction extends DHBaseAction { static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects']; + static defineSchema() { + return { + ...super.defineSchema(), + altOutcomes: new fields.SchemaField({ + successHope: new fields.EmbeddedDataField(AltOutcome, { nullable: true, initial: null }), + successFear: new fields.EmbeddedDataField(AltOutcome, { nullable: true, initial: null }), + failureHope: new fields.EmbeddedDataField(AltOutcome, { nullable: true, initial: null }), + failureFear: new fields.EmbeddedDataField(AltOutcome, { nullable: true, initial: null }) + }) + }; + } + /** * Return a display ready damage formula string * @returns Formula string diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index e4d919fe2..b7bd23326 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -4,7 +4,7 @@ import IterableTypedObjectField from '../iterableTypedObjectField.mjs'; const fields = foundry.data.fields; -const getDamageBaseFields = () => ({ +export const getDamageBaseFields = () => ({ parts: new IterableTypedObjectField(DHDamageData), includeBase: new fields.BooleanField({ initial: false, @@ -23,12 +23,6 @@ export default class DamageField extends fields.SchemaField { constructor(options, context = {}) { const damageFields = { ...getDamageBaseFields(), - altOutcomes: new fields.SchemaField({ - successHope: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), - successFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), - failureHope: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }), - failureFear: new fields.EmbeddedDataField(AltDamageOutcome, { nullable: true, initial: null }) - }), groupAttack: new fields.StringField({ choices: CONFIG.DH.GENERAL.groupAttackRange, blank: true, @@ -339,18 +333,3 @@ export class DHDamageData extends DHResourceData { }; } } - -export class AltDamageOutcome extends foundry.abstract.DataModel { - static defineSchema() { - return { - ...getDamageBaseFields() - }; - } - - get data() { - return { - ...this.parent, - ...this - }; - } -} diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index fef6591eb..837155640 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -38,15 +38,15 @@
{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}} - {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path outcomePath=../outcomePath}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path basePath=../basePath}}
{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}} - {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path outcomePath=../outcomePath}} + {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path basePath=../basePath}}
{{else}} - {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path outcomePath=../outcomePath}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path basePath=../basePath}} {{/if}} {{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}} @@ -72,21 +72,21 @@ {{#*inline "formula"}} {{#unless dmg.base}} - {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path outcomePath ".parts." key "." target ".custom.enabled") classes="checkbox" localize=true}} + {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path basePath ".parts." key "." target ".custom.enabled") classes="checkbox" localize=true}} {{/unless}} {{#if source.custom.enabled}} - {{formField fields.custom.fields.formula value=source.custom.formula name=(concat path outcomePath ".parts." key "." target ".custom.formula") localize=true}} + {{formField fields.custom.fields.formula value=source.custom.formula name=(concat path basePath ".parts." key "." target ".custom.formula") localize=true}} {{else}}
{{#unless @root.isNPC}} - {{formField fields.multiplier value=source.multiplier name=(concat path outcomePath ".parts." key "." target ".multiplier") localize=true}} + {{formField fields.multiplier value=source.multiplier name=(concat path basePath ".parts." key "." target ".multiplier") localize=true}} {{/unless}} - {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path outcomePath ".parts." key "." target ".flatMultiplier") localize=true }}{{/if}} - {{formField fields.dice value=source.dice name=(concat path outcomePath ".parts." key "." target ".dice") localize=true}} - {{formField fields.bonus value=source.bonus name=(concat path outcomePath ".parts." key "." target ".bonus") localize=true}} + {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path basePath ".parts." key "." target ".flatMultiplier") localize=true }}{{/if}} + {{formField fields.dice value=source.dice name=(concat path basePath ".parts." key "." target ".dice") localize=true}} + {{formField fields.bonus value=source.bonus name=(concat path basePath ".parts." key "." target ".bonus") localize=true}}
{{/if}} {{#if @root.isNPC}} - + {{/if}} {{/inline}} \ No newline at end of file diff --git a/templates/sheets-settings/action-settings/outcomes.hbs b/templates/sheets-settings/action-settings/outcomes.hbs index 442333a41..fb15bd06d 100644 --- a/templates/sheets-settings/action-settings/outcomes.hbs +++ b/templates/sheets-settings/action-settings/outcomes.hbs @@ -21,14 +21,15 @@ data-group="outcomes" data-tab="{{tab.id}}" > - {{#if ../fields.damage}} - {{#if (eq tab.id 'default')}} - {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=../fields.damage.fields.parts.element.fields source=../source.damage baseFields=../fields.damage.fields outcome=tab.id outcomePath="damage" isDefaultDamage="true" }} - {{else}} - {{#with (lookup ../fields.damage.fields.altOutcomes.fields tab.id) as |field|}} - {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=field.fields.parts.element.fields source=(lookup ../../source.damage.altOutcomes ../id) baseFields=field.fields outcome=../id outcomePath=(concat "damage.altOutcomes." tab.id) }} - {{/with }} - {{/if}} + {{#if tab.fields.damage}} + {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' + outcome=tab.id + fields=tab.fields.damage.fields.parts.element.fields + source=tab.source.damage + baseFields=tab.fields.damage.fields + isDefaultDamage="true" + basePath=(tab.getBasePath "damage") + }} {{/if}}