From b64e600a6b667620b4165c56f2fe8291a0dd51e6 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 20 Jun 2026 13:44:39 -0400 Subject: [PATCH 01/10] Fix old armorscore AEs clobbering data (#2018) --- module/data/actor/character.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 8ae78ff8..3b12da6f 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -315,7 +315,12 @@ export default class DhCharacter extends DhCreature { label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice' }) }) - }) + }), + /** Accumulated armor score from all sources */ + armorScore: new fields.SchemaField({ + value: new fields.NumberField(), + max: new fields.NumberField() + }, { persisted: false }) }; } From 6f1da427352a55f4eedc7642b6155bd4f8035431 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 20 Jun 2026 13:48:12 -0400 Subject: [PATCH 02/10] Make dragging features work more seamlessly (#2016) --- .../sheets-configs/adversary-settings.mjs | 2 +- .../applications/sheets/actors/adversary.mjs | 2 +- .../sheets/api/application-mixin.mjs | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index 57405675..cd627f1c 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -54,7 +54,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings { async _prepareContext(options) { const context = await super._prepareContext(options); - const featureForms = ['passive', 'action', 'reaction']; + const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); context.features = context.document.system.features.sort((a, b) => a.system.featureForm !== b.system.featureForm ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index f39bec0c..85380392 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -103,7 +103,7 @@ export default class AdversarySheet extends DHBaseActorSheet { context.resources.stress.emptyPips = context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; - const featureForms = ['passive', 'action', 'reaction']; + const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); context.features = this.document.system.features.sort((a, b) => a.system.featureForm !== b.system.featureForm ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 752dc80b..876278fa 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -387,6 +387,30 @@ export default function DHApplicationMixin(Base) { return super._onDrop?.(event); } + /** @inheritdoc */ + _onSortItem(event, item) { + // If we are dragging a feature past its allowed feature form, put it in the front or in the back + const doc = this.actor.items.get(item.id); + const dropTargetEl = event.target.closest('[data-item-id]'); + const dropTarget = this.actor.items.get(dropTargetEl?.dataset.itemId); + if (doc?.type === 'feature' && dropTarget?.type === 'feature' && doc.system.featureForm !== dropTarget.system.featureForm) { + const siblings = this.actor.itemTypes.feature + .filter(f => f.system.featureForm === doc.system.featureForm) + .sort((a, b) => a.sort - b.sort); + if (siblings.length > 1) { + const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); + const thisFeatureIdx = featureForms.indexOf(doc.system.featureForm); + const targetFeatureIdx = featureForms.indexOf(dropTarget.system.featureForm); + const target = targetFeatureIdx < thisFeatureIdx ? siblings[0] : siblings.at(-1); + const sortUpdates = foundry.utils.performIntegerSort(doc, { target, siblings }); + const updateData = sortUpdates.map(u => ({ ...u.update, _id: u.target._id })); + return this.actor.updateEmbeddedDocuments('Item', updateData); + } + } + + return super._onSortItem?.(event, item); + } + /* -------------------------------------------- */ /* Context Menu */ /* -------------------------------------------- */ From 6b80a6243c81f2d550353ae57fe7552c14ddcd95 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 20 Jun 2026 13:55:51 -0400 Subject: [PATCH 03/10] Support drag resort on features in actor setting sheets (#2023) --- .../sheets-configs/adversary-settings.mjs | 28 -------------- .../sheets-configs/environment-settings.mjs | 35 ++++++++---------- .../sheets-configs/npc-settings.mjs | 30 --------------- .../sheets/actors/environment.mjs | 1 - .../applications/sheets/api/actor-setting.mjs | 37 ++++++++++++++++--- .../sheets/api/application-mixin.mjs | 3 +- .../adversary-settings/features.hbs | 2 +- .../environment-settings/features.hbs | 2 +- .../sheets-settings/npc-settings/features.hbs | 2 +- 9 files changed, 52 insertions(+), 88 deletions(-) diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index cd627f1c..583f37b7 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -97,32 +97,4 @@ export default class DHAdversarySettings extends DHBaseActorSettings { await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del }); } - - async _onDragStart(event) { - const featureItem = event.currentTarget.closest('.feature-item'); - - if (featureItem) { - const feature = this.actor.items.get(featureItem.id); - const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true }; - event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); - event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); - } - } - - async _onDrop(event) { - event.stopPropagation(); - const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); - - const item = await fromUuid(data.uuid); - if (item?.type === 'feature') { - if (data.fromInternal && item.parent?.uuid === this.actor.uuid) { - return; - } - - const itemData = item.toObject(); - delete itemData._id; - - await this.actor.createEmbeddedDocuments('Item', [itemData]); - } - } } diff --git a/module/applications/sheets-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index 6d74f9c6..d6744eb8 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -15,7 +15,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { dragDrop: [ { dragSelector: null, dropSelector: '.category-container' }, { dragSelector: null, dropSelector: '.tab.features' }, - { dragSelector: '.feature-item', dropSelector: null } + { dragSelector: '.feature-item, .inventory-item[data-type="adversary"]', dropSelector: null } ] }; @@ -110,33 +110,30 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { } async _onDragStart(event) { - const featureItem = event.currentTarget.closest('.feature-item'); - - if (featureItem) { - const feature = this.actor.items.get(featureItem.id); - const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true }; - event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); - event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); + const element = event.currentTarget.closest('.inventory-item[data-type=adversary]'); + if (element) { + const adversaryData = { type: 'Actor', uuid: element.dataset.itemUuid }; + event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData)); + event.dataTransfer.setDragImage(element, 60, 0); + } else { + return super._onDragStart(event); } } async _onDrop(event) { event.stopPropagation(); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); - const item = await fromUuid(data.uuid); - if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return; - - if (item.type === 'adversary' && event.target.closest('.category-container')) { + const doc = await fromUuid(data.uuid); + if (doc?.type === 'adversary' && event.target.closest('.category-container')) { const target = event.target.closest('.category-container'); const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; const current = foundry.utils.getProperty(this.actor, path).map(x => x.uuid); - await this.actor.update({ - [path]: [...current, item.uuid] - }); - this.render(); - } else if (item.type === 'feature' && event.target.closest('.tab.features')) { - await this.actor.createEmbeddedDocuments('Item', [item]); - this.render(); + if (!current.includes(doc.uuid)) { + await this.actor.update({ [path]: [...current, doc.uuid] }); + } + return; } + + return super._onDrop(event); } } diff --git a/module/applications/sheets-configs/npc-settings.mjs b/module/applications/sheets-configs/npc-settings.mjs index c187877c..d2132a91 100644 --- a/module/applications/sheets-configs/npc-settings.mjs +++ b/module/applications/sheets-configs/npc-settings.mjs @@ -52,34 +52,4 @@ export default class DHNPCSettings extends DHBaseActorSettings { return context; } - - /* -------------------------------------------- */ - - async _onDragStart(event) { - const featureItem = event.currentTarget.closest('.feature-item'); - - if (featureItem) { - const feature = this.actor.items.get(featureItem.id); - const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true }; - event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); - event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); - } - } - - async _onDrop(event) { - event.stopPropagation(); - const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); - - const item = await fromUuid(data.uuid); - if (item?.type === 'feature') { - if (data.fromInternal && item.parent?.uuid === this.actor.uuid) { - return; - } - - const itemData = item.toObject(); - delete itemData._id; - - await this.actor.createEmbeddedDocuments('Item', [itemData]); - } - } } diff --git a/module/applications/sheets/actors/environment.mjs b/module/applications/sheets/actors/environment.mjs index f8ff74a6..9a88dba6 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -78,7 +78,6 @@ export default class DhpEnvironment extends DHBaseActorSheet { switch (partId) { case 'header': await this._prepareHeaderContext(context, options); - break; case 'features': await this._prepareFeaturesContext(context, options); diff --git a/module/applications/sheets/api/actor-setting.mjs b/module/applications/sheets/api/actor-setting.mjs index 738f7002..65497cec 100644 --- a/module/applications/sheets/api/actor-setting.mjs +++ b/module/applications/sheets/api/actor-setting.mjs @@ -1,13 +1,15 @@ import DHApplicationMixin from './application-mixin.mjs'; -const { DocumentSheetV2 } = foundry.applications.api; +const { ActorSheetV2 } = foundry.applications.sheets; -/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ +/** + * @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction + */ /** * Base settings sheet for Daggerheart actors. - * @extends {DHApplicationMixin} + * @extends {DHApplicationMixin} */ -export default class DHBaseActorSettings extends DHApplicationMixin(DocumentSheetV2) { +export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2) { /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['dialog'], @@ -34,7 +36,7 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee return options; } - /**@returns {foundry.documents.Actor} */ + /** @returns {foundry.documents.Actor} */ get actor() { return this.document; } @@ -73,4 +75,29 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee return context; } + + async _onDragStart(event) { + const featureItemEl = event.currentTarget.closest('.feature-item'); + const feature = this.actor.items.get(featureItemEl?.dataset.itemId); + if (feature && event.target.closest('.tab.features')) { + const featureData = { ...feature.toDragData(), fromInternal: true }; + event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); + event.dataTransfer.setDragImage(featureItemEl.querySelector('img'), 60, 0); + } + } + + async _onDrop(event) { + event.stopPropagation(); + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); + const item = await fromUuid(data.uuid); + if (item?.type === 'feature') { + if (data.fromInternal && item.parent?.uuid === this.actor.uuid) { + return super._onDrop(event); + } + + const itemData = item.toObject(); + delete itemData._id; + await this.actor.createEmbeddedDocuments('Item', [itemData]); + } + } } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 876278fa..f3d612e9 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -381,8 +381,7 @@ export default function DHApplicationMixin(Base) { * @protected */ _onDrop(event) { - // Fallback to super, but note that config sheets don't have this option - // We still need this to avoid setting apps having issues + // Potentially handle subclasses that dont descend from actor/item sheet. event.stopPropagation(); return super._onDrop?.(event); } diff --git a/templates/sheets-settings/adversary-settings/features.hbs b/templates/sheets-settings/adversary-settings/features.hbs index 2f2f5f47..3e0ed654 100644 --- a/templates/sheets-settings/adversary-settings/features.hbs +++ b/templates/sheets-settings/adversary-settings/features.hbs @@ -10,7 +10,7 @@ {{localize tabs.features.label}}
    {{#each @root.features as |feature|}} -
  • +
  • {{feature.name}} diff --git a/templates/sheets-settings/environment-settings/features.hbs b/templates/sheets-settings/environment-settings/features.hbs index 579fe74e..ecda0e6b 100644 --- a/templates/sheets-settings/environment-settings/features.hbs +++ b/templates/sheets-settings/environment-settings/features.hbs @@ -10,7 +10,7 @@ {{localize tabs.features.label}}
      {{#each @root.features as |feature|}} -
    • +
    • {{feature.name}} diff --git a/templates/sheets-settings/npc-settings/features.hbs b/templates/sheets-settings/npc-settings/features.hbs index 2f2f5f47..3e0ed654 100644 --- a/templates/sheets-settings/npc-settings/features.hbs +++ b/templates/sheets-settings/npc-settings/features.hbs @@ -10,7 +10,7 @@ {{localize tabs.features.label}}
        {{#each @root.features as |feature|}} -
      • +
      • {{feature.name}} From de7c08ed64417c10f2ee2662cad2db91dfab4a06 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 20 Jun 2026 23:19:26 +0200 Subject: [PATCH 04/10] Added an enable/disable checkbox for TagTeamRoll hope cost --- module/applications/dialogs/tagTeamDialog.mjs | 37 +++++++++++++++---- .../tag-team-dialog/initialization.less | 14 ++++++- .../dialogs/tagTeamDialog/initialization.hbs | 13 +++++-- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 92a92369..0a206ace 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -9,6 +9,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio constructor(party) { super({ id: `TagTeamDialog-${party.id}` }); + this.usesTagTeamHopeCost = true; this.party = party; this.partyMembers = party.system.partyMembers .filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) @@ -20,7 +21,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) })); - this.initiator = { cost: 3 }; + this.initiator = { cost: + this.party.system.schema.fields.tagTeam.fields.initiator.fields.cost.initial + }; this.openForAllPlayers = true; this.tabGroups.application = Object.keys(party.system.tagTeam.members).length @@ -94,9 +97,13 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ?.addEventListener('input', this.updateInitiatorMemberField.bind(this)); htmlElement - .querySelector('.initiator-cost-field') + .querySelector('.initiator-cost-input') ?.addEventListener('input', this.updateInitiatorCostField.bind(this)); + htmlElement + .querySelector('.initiator-cost-enabled-checkbox') + ?.addEventListener('change', this.toggleInitiatorCostEnabled.bind(this)); + htmlElement .querySelector('.openforall-field') ?.addEventListener('change', this.updateOpenForAllField.bind(this)); @@ -156,6 +163,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio .map(x => ({ value: x.id, label: x.name })); partContext.initiatorDisabled = !selectedMembers.length; partContext.openForAllPlayers = this.openForAllPlayers; + partContext.usesTagTeamHopeCost = this.usesTagTeamHopeCost; break; case 'tagTeamRoll': @@ -397,6 +405,13 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio this.render(); } + toggleInitiatorCostEnabled(_event) { + this.usesTagTeamHopeCost = !this.usesTagTeamHopeCost; + this.initiator.cost = this.usesTagTeamHopeCost ? + this.party.system.schema.fields.tagTeam.fields.initiator.fields.cost.initial : 0; + this.render(); + } + updateOpenForAllField(event) { this.openForAllPlayers = event.target.checked; this.render(); @@ -751,9 +766,10 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio await cls.create(msgData); /* Handle resource updates from the finished TagTeamRoll */ + const tagTeamData = this.party.system.tagTeam; + const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; + if (shouldUseHopeFearAutomation({ gmAsPlayer: true })) { - const tagTeamData = this.party.system.tagTeam; - const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; for (let memberId in tagTeamData.members) { const resourceUpdates = []; const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; @@ -775,12 +791,17 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio game.actors.get(memberId).modifyResource(resourceUpdates); } - - if (fearUpdate.value) { - mainActor.modifyResource([fearUpdate]); - } + } + /* Even with Hope/Fear automation off, the hope cost of performing the TagTeamRoll can still optionally be subtracted */ + else if (tagTeamData.initiator.cost) { + const initiatingActor = game.actors.get(tagTeamData.initiator.memberId); + initiatingActor.modifyResource([{ key: 'hope', value: -tagTeamData.initiator.cost, total: tagTeamData.initiator.cost, enabled: true }]); } + if (fearUpdate.value) { + mainActor.modifyResource([fearUpdate]); + } + /* Fin */ this.cancelRoll({ confirm: false }); } diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index d6f7ad29..e79ed65c 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -88,9 +88,21 @@ grid-template-columns: 1fr 1fr; gap: 8px; - &.inactive { + .inactive { opacity: 0.4; } + + .initiator-cost-fields { + display: flex; + flex-direction: column; + align-items: flex-start; + + .initiator-cost-inputs { + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + } + } } footer { diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs index 0b92e68e..40491e1b 100644 --- a/templates/dialogs/tagTeamDialog/initialization.hbs +++ b/templates/dialogs/tagTeamDialog/initialization.hbs @@ -26,11 +26,16 @@
      -
      +
      -
      - -
      +
      + + +
      From 29be8c139538a3f543535c390ed7b18c227e8d65 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 21 Jun 2026 00:13:45 +0200 Subject: [PATCH 05/10] Removed the unused ResourceMap total property (#2024) --- module/applications/dialogs/groupRollDialog.mjs | 8 ++++---- module/applications/dialogs/tagTeamDialog.mjs | 8 ++++---- module/applications/sheets/actors/character.mjs | 2 +- module/data/action/baseAction.mjs | 3 +-- module/dice/dualityRoll.mjs | 12 ++++++------ module/dice/helpers.mjs | 6 +++--- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index 7196d848..58ed03b4 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -483,13 +483,13 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat const resourceMap = new ResourceUpdateMap(actor); if (totalRoll.isCritical) { resourceMap.addResources([ - { key: 'stress', value: -1, total: 1 }, - { key: 'hope', value: 1, total: 1 } + { key: 'stress', value: -1 }, + { key: 'hope', value: 1 } ]); } else if (totalRoll.withHope) { - resourceMap.addResources([{ key: 'hope', value: 1, total: 1 }]); + resourceMap.addResources([{ key: 'hope', value: 1 }]); } else { - resourceMap.addResources([{ key: 'fear', value: 1, total: 1 }]); + resourceMap.addResources([{ key: 'fear', value: 1 }]); } resourceMap.updateResources(); diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index b2ce0258..5c83f075 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -752,7 +752,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio /* Handle resource updates from the finished TagTeamRoll */ const tagTeamData = this.party.system.tagTeam; - const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; + const fearUpdate = { key: 'fear', value: null, enabled: true }; for (let memberId in tagTeamData.members) { const resourceUpdates = []; const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; @@ -762,11 +762,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ? 1 - tagTeamData.initiator.cost : -tagTeamData.initiator.cost : 1; - resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true }); + resourceUpdates.push({ key: 'hope', value: value, enabled: true }); } else if (rollGivesHope) { - resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true }); + resourceUpdates.push({ key: 'hope', value: 1, enabled: true }); } - if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); + if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, enabled: true }); if (finalRoll.withFear) { fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index f0f8326f..3a60e7ca 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -809,7 +809,7 @@ export default class CharacterSheet extends DHBaseActorSheet { /* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */ const costResources = - result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) || + result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value })) || {}; result.resourceUpdates.addResources(costResources); await result.resourceUpdates.updateResources(); diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index c71f5ef9..ea4361b9 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -459,8 +459,7 @@ export class ResourceUpdateMap extends Map { } else if (!existing?.clear) { this.set(resource.key, { ...existing, - value: existing.value + (resource.value ?? 0), - total: existing.total + (resource.total ?? 0) + value: existing.value + (resource.value ?? 0) }); } } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 1cfed094..70e98242 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -334,15 +334,15 @@ export default class DualityRoll extends D20Roll { const fear = (config.roll.result.duality === -1 ? 1 : 0) - (config.rerolledRoll.result.duality === -1 ? 1 : 0); - if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true }); - if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true }); - if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true }); + 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 }); } } else { if (config.roll.isCritical || config.roll.result.duality === 1) - updates.push({ key: 'hope', value: 1, total: -1, enabled: true }); - if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true }); - if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true }); + updates.push({ key: 'hope', value: 1, enabled: true }); + if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, enabled: true }); + if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, enabled: true }); } if (updates.length) { diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs index 35adb8b7..5f8a7bbb 100644 --- a/module/dice/helpers.mjs +++ b/module/dice/helpers.mjs @@ -9,9 +9,9 @@ export function updateResourcesForDualityReroll(oldDuality, newDuality, actor) { 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, total: -1 * hope, enabled: true }); - if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true }); - if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true }); + 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); From 5299beed8b94b5a5a91f11c66250ffbf7ef80139 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 21 Jun 2026 00:32:07 +0200 Subject: [PATCH 06/10] Changed to use ResourceUpdateMap in TagTeamDialog --- module/applications/dialogs/tagTeamDialog.mjs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 03673b4c..31f71abd 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -1,3 +1,4 @@ +import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; import { MemberData } from '../../data/tagTeamData.mjs'; import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; @@ -767,11 +768,13 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio /* Handle resource updates from the finished TagTeamRoll */ const tagTeamData = this.party.system.tagTeam; - const fearUpdate = { key: 'fear', value: null, enabled: true }; + const fearResourceMap = new ResourceUpdateMap(mainActor); if (shouldUseHopeFearAutomation({ gmAsPlayer: true })) { - for (let memberId in tagTeamData.members) { - const resourceUpdates = []; + for (const memberId in tagTeamData.members) { + const actor = game.actors.get(memberId); + const resourceMap = new ResourceUpdateMap(actor); + const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; if (memberId === tagTeamData.initiator.memberId) { const value = tagTeamData.initiator.cost @@ -779,16 +782,20 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ? 1 - tagTeamData.initiator.cost : -tagTeamData.initiator.cost : 1; - resourceUpdates.push({ key: 'hope', value: value, enabled: true }); + resourceMap.addResources([{ key: 'hope', value: value, enabled: true }]); } else if (rollGivesHope) { - resourceUpdates.push({ key: 'hope', value: 1, enabled: true }); + resourceMap.addResources([{ key: 'hope', value: 1, enabled: true }]); } - if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, enabled: true }); + if (finalRoll.isCritical) resourceMap.addResources([{ key: 'stress', value: -1, enabled: true }]); if (finalRoll.withFear) { - fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; + fearResourceMap.addResources([{ + key: 'fear', + value: 1, + enabled: true + }]); } - game.actors.get(memberId).modifyResource(resourceUpdates); + resourceMap.updateResources(); } } /* Even with Hope/Fear automation off, the hope cost of performing the TagTeamRoll can still optionally be subtracted */ @@ -797,10 +804,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio initiatingActor.modifyResource([{ key: 'hope', value: -tagTeamData.initiator.cost, enabled: true }]); } - if (fearUpdate.value) { - mainActor.modifyResource([fearUpdate]); - } - + fearResourceMap.updateResources(); + /* Fin */ this.cancelRoll({ confirm: false }); } From 4459ee00e017bb55fe9130dd21842968e6c34fc0 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 21 Jun 2026 00:50:04 +0200 Subject: [PATCH 07/10] Better TagTeamDialog finalise solution --- module/applications/dialogs/tagTeamDialog.mjs | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 31f71abd..b05e667b 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -768,25 +768,21 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio /* Handle resource updates from the finished TagTeamRoll */ const tagTeamData = this.party.system.tagTeam; - const fearResourceMap = new ResourceUpdateMap(mainActor); + + const actorResourceMaps = Object.keys(tagTeamData.members).reduce((acc, key) => { + acc[key] = new ResourceUpdateMap(game.actors.get(key)); + return acc; + }, {}); if (shouldUseHopeFearAutomation({ gmAsPlayer: true })) { + const fearResourceMap = actorResourceMaps[tagTeamData.initiator.memberId]; for (const memberId in tagTeamData.members) { - const actor = game.actors.get(memberId); - const resourceMap = new ResourceUpdateMap(actor); - - const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; - if (memberId === tagTeamData.initiator.memberId) { - const value = tagTeamData.initiator.cost - ? rollGivesHope - ? 1 - tagTeamData.initiator.cost - : -tagTeamData.initiator.cost - : 1; - resourceMap.addResources([{ key: 'hope', value: value, enabled: true }]); - } else if (rollGivesHope) { + const resourceMap = actorResourceMaps[memberId]; + if (finalRoll.isCritical || finalRoll.withHope) { resourceMap.addResources([{ key: 'hope', value: 1, enabled: true }]); } - if (finalRoll.isCritical) resourceMap.addResources([{ key: 'stress', value: -1, enabled: true }]); + if (finalRoll.isCritical) + resourceMap.addResources([{ key: 'stress', value: -1, enabled: true }]); if (finalRoll.withFear) { fearResourceMap.addResources([{ key: 'fear', @@ -794,17 +790,17 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio enabled: true }]); } - - resourceMap.updateResources(); } } + /* Even with Hope/Fear automation off, the hope cost of performing the TagTeamRoll can still optionally be subtracted */ - else if (tagTeamData.initiator.cost) { - const initiatingActor = game.actors.get(tagTeamData.initiator.memberId); - initiatingActor.modifyResource([{ key: 'hope', value: -tagTeamData.initiator.cost, enabled: true }]); + if (tagTeamData.initiator.cost) { + const resourceMap = actorResourceMaps[tagTeamData.initiator.memberId]; + resourceMap.addResources([{ key: 'hope', value: -tagTeamData.initiator.cost, enabled: true }]); } - fearResourceMap.updateResources(); + for (const resourceMap of Object.values(actorResourceMaps)) + resourceMap.updateResources(); /* Fin */ this.cancelRoll({ confirm: false }); From 1c6c3e7736a7ab395219ac32f91697501188eec4 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 21 Jun 2026 01:00:08 +0200 Subject: [PATCH 08/10] Neater code for critical --- module/applications/dialogs/tagTeamDialog.mjs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index b05e667b..f728a327 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -778,17 +778,17 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const fearResourceMap = actorResourceMaps[tagTeamData.initiator.memberId]; for (const memberId in tagTeamData.members) { const resourceMap = actorResourceMaps[memberId]; - if (finalRoll.isCritical || finalRoll.withHope) { + if (finalRoll.withHope) { resourceMap.addResources([{ key: 'hope', value: 1, enabled: true }]); } - if (finalRoll.isCritical) - resourceMap.addResources([{ key: 'stress', value: -1, enabled: true }]); - if (finalRoll.withFear) { - fearResourceMap.addResources([{ - key: 'fear', - value: 1, - enabled: true - }]); + if (finalRoll.isCritical) { + resourceMap.addResources([ + { key: 'stress', value: -1, enabled: true }, + { key: 'hope', value: 1, enabled: true } + ]); + } + else if (finalRoll.withFear) { + fearResourceMap.addResources([{ key: 'fear', value: 1, enabled: true }]); } } } From 629268caf922cfaa0a78d613ccf35583ea4c717a Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 21 Jun 2026 01:00:47 +0200 Subject: [PATCH 09/10] . --- module/applications/dialogs/tagTeamDialog.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index f728a327..6080da17 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -781,7 +781,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio if (finalRoll.withHope) { resourceMap.addResources([{ key: 'hope', value: 1, enabled: true }]); } - if (finalRoll.isCritical) { + else if (finalRoll.isCritical) { resourceMap.addResources([ { key: 'stress', value: -1, enabled: true }, { key: 'hope', value: 1, enabled: true } From 3dc8379ff5bfb388351baa7fa806fa6bb94bc6e0 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 21 Jun 2026 01:05:25 +0200 Subject: [PATCH 10/10] Critical first :eyes: --- module/applications/dialogs/tagTeamDialog.mjs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 6080da17..19869a00 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -777,17 +777,15 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio if (shouldUseHopeFearAutomation({ gmAsPlayer: true })) { const fearResourceMap = actorResourceMaps[tagTeamData.initiator.memberId]; for (const memberId in tagTeamData.members) { - const resourceMap = actorResourceMaps[memberId]; - if (finalRoll.withHope) { - resourceMap.addResources([{ key: 'hope', value: 1, enabled: true }]); - } - else if (finalRoll.isCritical) { + const resourceMap = actorResourceMaps[memberId]; + if (finalRoll.isCritical) { resourceMap.addResources([ { key: 'stress', value: -1, enabled: true }, { key: 'hope', value: 1, enabled: true } ]); - } - else if (finalRoll.withFear) { + } else if (finalRoll.withHope) { + resourceMap.addResources([{ key: 'hope', value: 1, enabled: true }]); + } else if (finalRoll.withFear) { fearResourceMap.addResources([{ key: 'fear', value: 1, enabled: true }]); } }