From effab8db0cc1815ca552a3eafb3247c785c7e037 Mon Sep 17 00:00:00 2001
From: WBHarry <89362246+WBHarry@users.noreply.github.com>
Date: Sun, 19 Jul 2026 03:02:57 +0200
Subject: [PATCH] [Rework] ChatMessage Damage (#2079)

* Initial rework

* Removed unneeded method

* Removed outcommented code

* Added migration

* Fixed DamageActions

* Semi-corrected reroll of one of multiple results on a die

* Code improvement

* Added aseDie.rerollResult method to specifically reroll a specific result die in a grouping

* TagTeamDialog somewhat working

* Added migration for TagTeamData

* Fix for mean tagTeamDialog.finish

* .

* Improved migration

* Internalised ChatDamageData.prepareRolls to its own constructor

* Moved DamageTypes to roll.options

* TagTeamDialog fixes to accomodate new flat damage.types structure

* Changed so all default rolls become BaseRolls to access convenience functions and getters

* .

* Fixed so that party.tagTeam now uses a declared damageRollData(ChatDamageData) field. rollData.options.damage is retired.

* Critical damage fixes

* Corrected TagTeamDialog rerender logic when selecting a roll

* Fixed so that DamageTypes are retained and joined together throughout TagTeamDialog

* Removed some remaining types.<>.roll references

* [Rework] Damage and Damage Resource split (#2094)

* Changed the label of ActionSheet damage.resources to Mark Resources

* Fix assigning includeBase

* Localize add resource

* Maybe simplify API

* Fix onRollSimple

* Handle erroring action update differently

---------

Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
---
 daggerheart.mjs                               |   5 +-
 lang/en.json                                  |   5 +-
 module/applications/dialogs/damageDialog.mjs  |  12 +-
 module/applications/dialogs/tagTeamDialog.mjs | 219 ++++++++--------
 .../sheets-configs/action-base-config.mjs     |  99 ++++---
 .../sheets-configs/adversary-settings.mjs     |  35 ++-
 module/applications/ui/chatLog.mjs            |  58 ++---
 module/config/generalConfig.mjs               |   2 +-
 module/data/action/attackAction.mjs           |  21 +-
 module/data/action/baseAction.mjs             |  34 ++-
 module/data/action/damageAction.mjs           |   7 +-
 module/data/actor/adversary.mjs               |  12 +-
 module/data/actor/character.mjs               |  18 +-
 module/data/actor/tierAdjustment.mjs          |  14 +-
 module/data/chat-message/actorRoll.mjs        |  69 +++--
 module/data/chat-message/chatDamageData.mjs   |  58 +++++
 module/data/fields/action/damageField.mjs     |  78 +++---
 module/data/fields/actionField.mjs            |   1 +
 module/data/item/weapon.mjs                   |  23 +-
 module/data/tagTeamData.mjs                   |   3 +
 module/dice/baseRoll.mjs                      |   4 +
 module/dice/damageRoll.mjs                    | 242 ++++++------------
 module/dice/dhRoll.mjs                        |   9 +-
 module/dice/die/_module.mjs                   |   1 +
 module/dice/die/advantageDie.mjs              |   4 +-
 module/dice/die/baseDie.mjs                   |  12 +
 module/dice/die/disadvantageDie.mjs           |   4 +-
 module/dice/die/dualityDie.mjs                |   3 +-
 module/dice/dualityRoll.mjs                   |   4 -
 module/documents/actor.mjs                    | 130 +++++-----
 module/helpers/handlebarsHelper.mjs           |   5 +-
 module/helpers/utils.mjs                      |  13 -
 .../migration-handlers/2_5_2.mjs              |   3 +-
 .../migration-handlers/2_6_0.mjs              |  20 ++
 .../migration-handlers/base.mjs               |  44 +++-
 module/systemRegistration/migrations.mjs      |   4 +-
 .../less/dialog/damage-selection/sheet.less   |   7 +
 templates/actionTypes/cost.hbs                |   2 +-
 templates/actionTypes/damage.hbs              | 194 +++++++-------
 templates/actionTypes/resource.hbs            |   2 +-
 .../dialogs/dice-roll/damageSelection.hbs     |  69 +++--
 .../parts/tagTeamDamageParts.hbs              |  51 ++--
 templates/dialogs/tagTeamDialog/result.hbs    |   8 +-
 .../dialogs/tagTeamDialog/tagTeamMember.hbs   |  16 +-
 .../action-settings/effect.hbs                |   2 +-
 .../adversary-settings/attack.hbs             |   2 +-
 templates/sheets/items/weapon/settings.hbs    |  40 +--
 templates/ui/chat/parts/button-part.hbs       |   8 +-
 templates/ui/chat/parts/damage-part.hbs       | 115 +++++----
 templates/ui/tooltip/attack.hbs               |   2 +-
 templates/ui/tooltip/weapon.hbs               |   2 +-
 51 files changed, 1007 insertions(+), 788 deletions(-)
 create mode 100644 module/data/chat-message/chatDamageData.mjs
 create mode 100644 module/dice/die/baseDie.mjs
 create mode 100644 module/systemRegistration/migration-handlers/2_6_0.mjs

diff --git a/daggerheart.mjs b/daggerheart.mjs
index f91eedbe..2c51c1f6 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -6,6 +6,7 @@ import * as documents from './module/documents/_module.mjs';
 import { macros } from './module/_module.mjs';
 import * as collections from './module/documents/collections/_module.mjs';
 import * as dice from './module/dice/_module.mjs';
+import * as die from './module/dice/die/_module.mjs';
 import * as fields from './module/data/fields/_module.mjs';
 import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
 import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
@@ -23,7 +24,7 @@ import TokenManager from './module/documents/tokenManager.mjs';
 CONFIG.DH = SYSTEM;
 CONFIG.TextEditor.enrichers.push(...enricherConfig);
 
-CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
+CONFIG.Dice.rolls = [Roll = BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
 CONFIG.Dice.daggerheart = {
     DHRoll: DHRoll,
     DualityRoll: DualityRoll,
@@ -38,6 +39,8 @@ CONFIG.RegionBehavior.dataModels = {
 };
 
 Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
+CONFIG.Dice.terms.d = die.BaseDie;
+CONFIG.Dice.types = [die.BaseDie, CONFIG.Dice.terms.f];
 
 CONFIG.Actor.documentClass = documents.DhpActor;
 CONFIG.Actor.dataModels = models.actors.config;
diff --git a/lang/en.json b/lang/en.json
index 508a771d..b65cade7 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -104,8 +104,10 @@
                     "startCountdown": "Start Countdown"
                 },
                 "damage": {
+                    "addResource": "Add Resource",
                     "multiplier": "Multiplier",
-                    "flatMultiplier": "Flat Multiplier"
+                    "flatMultiplier": "Flat Multiplier",
+                    "markResources": "Mark Resources"
                 },
                 "general": {
                     "customFormula": "Custom Formula",
@@ -2483,7 +2485,6 @@
             "reroll": "Reroll",
             "rerolled": "Rerolled",
             "rerollThing": "Reroll {thing}",
-            "resource": "Resource",
             "result": {
                 "single": "Result",
                 "plural": "Results"
diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs
index 46d3d41f..ce613ade 100644
--- a/module/applications/dialogs/damageDialog.mjs
+++ b/module/applications/dialogs/damageDialog.mjs
@@ -51,7 +51,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
         const context = await super._prepareContext(_options);
         context.config = CONFIG.DH;
         context.title = this.config.title ?? this.title;
-        context.formula = this.roll.constructFormula(this.config);
+
+        const { damageFormula, resourceFormulas } = this.roll.constructFormulas(this.config); 
+        context.damageFormula = damageFormula;
+        context.resourceFormulas = resourceFormulas;
+
         context.hasHealing = this.config.hasHealing;
         context.directDamage = this.config.directDamage;
         context.selectedMessageMode = this.config.selectedMessageMode;
@@ -73,7 +77,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
 
     static updateRollConfiguration(_event, _, formData) {
         const data = foundry.utils.expandObject(formData.object);
-        foundry.utils.mergeObject(this.config.roll, data.roll);
+
+        if (this.config.damageFormula) 
+            foundry.utils.mergeObject(this.config.damageFormula, data.damageFormula);
+        
+        foundry.utils.mergeObject(this.config.resourceFormulas, data.resourceFormulas);
         foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
         this.config.selectedMessageMode = data.selectedMessageMode;
 
diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs
index b33541a3..4c6c5339 100644
--- a/module/applications/dialogs/tagTeamDialog.mjs
+++ b/module/applications/dialogs/tagTeamDialog.mjs
@@ -1,4 +1,5 @@
 import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
+import { ChatDamageData } from '../../data/chat-message/chatDamageData.mjs';
 import { MemberData } from '../../data/tagTeamData.mjs';
 import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
 import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
@@ -140,7 +141,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
             const hasRolled = Boolean(data.rollData);
             if (!hasRolled) return false;
 
-            return !data.rollData.options.hasDamage || Boolean(data.rollData.options.damage);
+            return !data.rollData.options.hasDamage || data.damageRollData.active;
         });
 
         return context;
@@ -181,7 +182,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
                 const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
                 const critSelected = !selectedRoll
                     ? undefined
-                    : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
+                    : (selectedRoll?.roll?.isCritical ?? false);
 
                 partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members);
                 partContext.joinedRoll = await this.getJoinedRoll({
@@ -235,8 +236,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
         }
 
         const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
-        const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
-        const damage = data.rollData?.options?.damage;
+        const critSelected = !selectedRoll ? undefined : (selectedRoll?.roll?.isCritical ?? false);
 
         return {
             ...data,
@@ -247,9 +247,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
             hasRolled: Boolean(data.rollData),
             rollOptions,
             damageRollOptions,
-            damage: damage,
-            critDamage: await this.getCriticalDamage(damage),
-            useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
+            damage: data.damageRollData,
+            critDamage: await this.getCriticalDamage(data.damageRollData),
+            useCritDamage: critSelected || (critSelected === undefined && data.roll?.isCritical)
         };
     }
 
@@ -379,7 +379,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
         let rollIsSelected = false;
         for (const member of Object.values(members)) {
             const rollFinished = Boolean(member.rollData);
-            const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true;
+            const damageFinished = member.rollData?.options?.hasDamage ? member.damageRollData.active : true;
 
             rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
             rollIsSelected = rollIsSelected || member.selected;
@@ -540,17 +540,10 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
 
         await action.workflow.get('damage').execute(config, null, true);
         if (!config.damage) return;
-
-        const current = this.party.system.tagTeam.members[memberKey].rollData;
+        
         await this.updatePartyData(
             {
-                [`system.tagTeam.members.${memberKey}.rollData`]: {
-                    ...current,
-                    options: {
-                        ...current.options,
-                        damage: config.damage
-                    }
-                }
+                [`system.tagTeam.members.${memberKey}.damageRollData`]: config.damage
             },
             this.getUpdatingParts(button)
         );
@@ -558,15 +551,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
 
     static async #removeDamageRoll(_, button) {
         const { memberKey } = button.dataset;
-        const current = this.party.system.tagTeam.members[memberKey].rollData;
         this.updatePartyData(
             {
-                [`system.tagTeam.members.${memberKey}.rollData`]: {
-                    ...current,
-                    options: {
-                        ...current.options,
-                        damage: null
-                    }
+                [`system.tagTeam.members.${memberKey}.damageRollData`]: {
+                    main: null,
+                    resources: _replace({})
                 }
             },
             this.getUpdatingParts(button)
@@ -574,89 +563,44 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
     }
 
     static async #rerollDamageDice(_, button) {
-        const { memberKey, damageKey, part, dice } = button.dataset;
+        const { isResource, memberKey, damageKey, diceIndex, resultIndex } = button.dataset;
         const memberData = this.party.system.tagTeam.members[memberKey];
-        const partData = memberData.rollData.options.damage[damageKey].parts[part];
-        const activeDiceResultKey = Object.keys(partData.dice[dice].results).find(
-            index => partData.dice[dice].results[index].active
-        );
-        const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(
-            partData,
-            dice,
-            activeDiceResultKey
-        );
-
-        const rollData = this.party.system.tagTeam.members[memberKey].rollData;
-        rollData.options.damage[damageKey].parts = rollData.options.damage[damageKey].parts.map((damagePart, index) => {
-            if (index !== Number.parseInt(part)) return damagePart;
-
-            return {
-                ...damagePart,
-                total: parsedRoll.total,
-                dice: rerolledDice
-            };
-        });
-        rollData.options.damage[damageKey].total = rollData.options.damage[damageKey].parts.reduce((acc, part) => {
-            acc += part.total;
-            return acc;
-        }, 0);
+        await memberData.damageRollData.rerollDamageDie(isResource, damageKey, diceIndex, resultIndex);
 
+        const basePath = `system.tagTeam.members.${memberKey}.damageRollData`;
+        const updatePath = isResource ? `${basePath}.resources.${damageKey}` : `${basePath}.main`;
+        const updateValue = isResource ? 
+            memberData.damageRollData.resources[damageKey] : memberData.damageRollData.main;
         this.updatePartyData(
             {
-                [`system.tagTeam.members.${memberKey}.rollData`]: rollData
+                [updatePath]: updateValue.toJSON()
             },
             this.getUpdatingParts(button)
         );
     }
 
-    async getCriticalDamage(damage) {
-        const newDamage = foundry.utils.deepClone(damage);
-        for (let key in newDamage) {
-            var damage = newDamage[key];
-            damage.formula = '';
-            damage.total = 0;
-
-            for (let part of damage.parts) {
-                const criticalDamage = await getCritDamageBonus(part.formula);
-                if (criticalDamage) {
-                    part.modifierTotal += criticalDamage;
-                    part.total += criticalDamage;
-                    part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
-                    part.roll = new Roll(part.formula);
-                }
-
-                damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
-                damage.total += part.total;
+    async getCriticalDamage(origDamage) {
+        const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null;
+        if (newDamage?.main) {
+            const criticalDamage = await getCritDamageBonus(newDamage.main.formula);
+            if (criticalDamage) {
+                const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
+                criticalTerm.evaluate();
+                newDamage.main = await Roll.fromTerms([
+                    ...origDamage.main.terms,
+                    new foundry.dice.terms.OperatorTerm({ operator: '+' }),
+                    criticalTerm
+                ]);
+                newDamage.main.options = foundry.utils.deepClone(origDamage.main.options);
             }
-        }
-
-        return newDamage;
-    }
-
-    async getNonCriticalDamage(config) {
-        const newDamage = foundry.utils.deepClone(config.damage);
-        for (let key in newDamage) {
-            var damage = newDamage[key];
-            damage.formula = '';
-            damage.total = 0;
-
-            for (let part of damage.parts) {
-                const critDamageBonus = await getCritDamageBonus(part.formula);
-                part.modifierTotal -= critDamageBonus;
-                part.total -= critDamageBonus;
-                part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
-                part.roll = new Roll(part.formula);
-
-                damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
-                damage.total += part.total;
-            }
-        }
+        } 
 
         return newDamage;
     }
 
     static async #selectRoll(_, button) {
         const { memberKey } = button.dataset;
+        
         this.updatePartyData(
             {
                 [`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce(
@@ -667,7 +611,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
                     {}
                 )
             },
-            this.getUpdatingParts(button)
+            /* Selecting a roll must update all member sections hbs to display the correct damage information incase of a critical */
+            [ 
+                ...Object.keys(this.party.system.tagTeam.members),
+                this.constructor.PARTS.rollSelection.id,
+                this.constructor.PARTS.result.id
+            ]
         );
     }
 
@@ -685,29 +634,65 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
             if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
 
             const mainRoll = new MemberData(baseMainRoll.toObject());
-            const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
-            const systemData = mainRoll.rollData.options;
-            const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
-            if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
+            mainRoll.damageRollData = baseMainRoll.damageRollData ? 
+                ChatDamageData.fromJSON(JSON.stringify(baseMainRoll.damageRollData)) : null;
+            const secondaryRoll = new MemberData(baseSecondaryRoll.toObject());
+            secondaryRoll.damageRollData = baseSecondaryRoll.damageRollData ? 
+                ChatDamageData.fromJSON(JSON.stringify(baseSecondaryRoll.damageRollData)) : null;
 
-            if (secondaryRollData?.options.hasDamage) {
+            const isCritical = overrideIsCritical ?? mainRoll.roll.isCritical;
+            if (isCritical) mainRoll.damageRollData = await this.getCriticalDamage(mainRoll.damageRollData);
+
+            if (secondaryRoll.damageRollData) {
                 const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
-                    ? await this.getCriticalDamage(secondaryRollData.options.damage)
-                    : secondaryRollData.options.damage;
-                if (systemData.damage) {
-                    for (const [key, damage] of Object.entries(secondaryDamage ?? {})) {
-                        if (key in systemData.damage) {
-                            systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula]
-                                .filter(x => x)
-                                .join(' + ');
-                            systemData.damage[key].total += damage.total;
-                            systemData.damage[key].parts.push(...damage.parts);
+                    ? await this.getCriticalDamage(secondaryRoll.damageRollData)
+                    : secondaryRoll.damageRollData;
+                if (mainRoll.damageRollData) {
+                    if (secondaryDamage.main) {
+                        if (mainRoll.damageRollData.main) {
+                            mainRoll.damageRollData.main = Roll.fromTerms([
+                                ...baseMainRoll.damageRollData.main.terms,
+                                new foundry.dice.terms.OperatorTerm({ operator: '+' }),
+                                ...baseSecondaryRoll.damageRollData.main.terms
+                            ]);
+
+                            /* Joining the roll.options of both rolls */
+                            const joinedDamageTypes = new Set([
+                                ...baseMainRoll.damageRollData.main.options.damageTypes,
+                                ...baseSecondaryRoll.damageRollData.main.options.damageTypes
+                            ]);
+                            mainRoll.damageRollData.main.options = {
+                                ...baseMainRoll.damageRollData.main.options,
+                                damageTypes: [...joinedDamageTypes]
+                            };
                         } else {
-                            systemData.damage[key] = damage;
+                            mainRoll.damageRollData.main = secondaryDamage.main;
+                        }
+                    }
+
+                    for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) {
+                        if (key in mainRoll.damageRollData.resources) {
+                            mainRoll.damageRollData.resources[key] = Roll.fromTerms([
+                                ...baseMainRoll.damageRollData.resources[key].terms,
+                                new foundry.dice.terms.OperatorTerm({ operator: '+' }),
+                                ...baseSecondaryRoll.damageRollData.resources[key].terms
+                            ]);
+
+                            /* Joining the roll.options of both rolls */
+                            const joinedDamageTypes = new Set([
+                                ...baseMainRoll.damageRollData.resources[key].options.damageTypes,
+                                ...baseSecondaryRoll.damageRollData.resources[key].options.damageTypes
+                            ]);
+                            mainRoll.damageRollData.resources[key].options = {
+                                ...baseMainRoll.damageRollData.resources[key].options,
+                                damageTypes: [...joinedDamageTypes]
+                            };
+                        } else {
+                            mainRoll.damageRollData.resources[key] = damage;
                         }
                     }
                 } else {
-                    systemData.damage = secondaryDamage;
+                    mainRoll.damageRollData = secondaryDamage;
                 }
             }
 
@@ -762,13 +747,27 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
 
         const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor);
         mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
+        
+        /* This could assumably be done better. For some reason rolls don't get correctly done through rollData.toJSON */
+        const systemData = {
+            ...mainRoll.options,
+            damage: joinedRoll.damageRollData?.toJSON()
+        };
+
+        if (joinedRoll.damageRollData.main) {
+            systemData.damage.main = joinedRoll.damageRollData.toJSON();
+        }
+        for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) {
+            systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON();
+        }
+
         const cls = getDocumentClass('ChatMessage'),
             msgData = {
                 type: 'dualityRoll',
                 user: game.user.id,
                 title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
                 speaker: cls.getSpeaker({ actor: mainActor }),
-                system: mainRoll.options,
+                system: systemData,
                 rolls: [JSON.stringify(joinedRoll.roll)],
                 sound: null,
                 flags: { core: { RollTable: true } }
diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs
index b65e1cdf..e675fb3f 100644
--- a/module/applications/sheets-configs/action-base-config.mjs
+++ b/module/applications/sheets-configs/action-base-config.mjs
@@ -1,4 +1,4 @@
-import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
+import { DHDamageData } from '../../data/fields/action/damageField.mjs';
 import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
 
 const { ApplicationV2 } = foundry.applications.api;
@@ -31,8 +31,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
             removeElement: this.removeElement,
             removeTransformActor: this.removeTransformActor,
             editEffect: this.editEffect,
-            addDamage: this.addDamage,
-            removeDamage: this.removeDamage,
+            addDamage: this.#onAddDamage,
+            removeDamage: this.#onRemoveDamage,
+            addDamageResource: this.#onAddDamageResource,
+            removeDamageResource: this.#onRemoveDamageResource,
             editDoc: this.editDoc,
             addTrigger: this.addTrigger,
             removeTrigger: this.removeTrigger,
@@ -157,9 +159,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
         context.tabs = this._getTabs(this.constructor.TABS);
         context.config = CONFIG.DH;
         if (this.action.damage) {
-            context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
-
-            if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
+            const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
+            context.allDamageTypesUsed = allKeys.every(k => k in this.action._source.damage.resources);
+            if (this.action.damage?.main?.hasOwnProperty('includeBase') && this.action.type === 'attack')
                 context.hasBaseDamage = !!this.action.parent.attack;
         }
 
@@ -253,7 +255,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
         const submitData = this._prepareSubmitData(event, formData);
 
         const data = foundry.utils.mergeObject(this.action.toObject(), submitData);
-        this.action = await this.action.update(data);
+        this.action = (await this.action.update(data)) ?? this.action;
 
         this.sheetUpdate?.(this.action);
         this.render();
@@ -299,53 +301,70 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
         this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
     }
 
-    static addDamage(_event) {
-        if (!this.action.damage.parts) return;
+    /** @this DHActionBaseConfig */
+    static #onAddDamage() {
+        if (!this.action.damage || this.action.damage?.main) return;
 
-        const choices = getUnusedDamageTypes(this.action._source.damage.parts);
+        const data = this.action.toObject();
+        data.damage.main = {
+            ...DHDamageData.schema.getInitialValue(),
+            applyTo: 'hitPoints',
+            type: 'physical'
+        };
+        this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
+    }
+
+    /** @this DHActionBaseConfig */
+    static #onRemoveDamage() {
+        if (!this.action.damage?.main) return;
+        const data = this.action.toObject();
+        data.damage.main = null;
+        this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
+    }
+
+    /** @this DHActionBaseConfig */
+    static #onAddDamageResource(_event) {
+        if (!this.action.damage) return;
+
+        const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
+        const unused = allKeys.filter(k => !(k in this.action._source.damage.resources));
+        const choices = unused.map(k => ({ value: k, label: _loc(CONFIG.DH.GENERAL.healingTypes[k].label) }));
         const content = new foundry.data.fields.StringField({
-            label: game.i18n.localize('Damage Type'),
+            label: _loc('DAGGERHEART.GENERAL.Resource.single'),
             choices,
             required: true
-        }).toFormGroup(
-            {},
-            {
-                name: 'type',
-                localize: true,
-                nameAttr: 'value',
-                labelAttr: 'label'
-            }
-        ).outerHTML;
+        }).toFormGroup({}, {
+            name: 'type',
+            localize: true,
+            nameAttr: 'value',
+            labelAttr: 'label'
+        }).outerHTML;
 
         const callback = (_, button) => {
             const data = this.action.toObject();
             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)
-                part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial;
-
-            data.damage.parts[type] = part;
+            data.damage.resources[type] = {
+                ...this.action.schema.fields.damage.fields.resources.element.getInitialValue(),
+                applyTo: type
+            };
             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,
             modal: false,
             window: {
-                title: game.i18n.localize('Add Damage')
+                title: _loc('DAGGERHEART.ACTIONS.Config.damage.addResource')
             },
             position: { width: 300 }
         });
@@ -353,12 +372,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
         typeDialog.render(true);
     }
 
-    static removeDamage(_event, button) {
-        if (!this.action.damage.parts) return;
+    /** @this DHActionBaseConfig */
+    static #onRemoveDamageResource(_event, button) {
+        if (!this.action.damage?.resources) return;
         const data = this.action.toObject();
         const key = button.dataset.key;
-        delete data.damage.parts[key];
-        data.damage.parts[`${key}`] = _del;
+        data.damage.resources[key] = _del;
         this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
     }
 
diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs
index ff3f3039..0bf18ee6 100644
--- a/module/applications/sheets-configs/adversary-settings.mjs
+++ b/module/applications/sheets-configs/adversary-settings.mjs
@@ -1,3 +1,4 @@
+import { DHDamageData } from '../../data/fields/action/damageField.mjs';
 import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
 
 /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@@ -8,8 +9,10 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
         classes: ['adversary-settings'],
         position: { width: 455, height: 'auto' },
         actions: {
-            addExperience: DHAdversarySettings.#addExperience,
-            removeExperience: DHAdversarySettings.#removeExperience
+            addExperience: this.#onAddExperience,
+            removeExperience: this.#onRemoveExperience,
+            addDamage: this.#onAddDamage,
+            removeDamage: this.#onRemoveDamage
         }
     };
 
@@ -71,7 +74,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
      * Adds a new experience entry to the actor.
      * @type {ApplicationClickAction}
      */
-    static async #addExperience() {
+    static async #onAddExperience() {
         const newExperience = {
             name: 'Experience',
             modifier: 0
@@ -83,7 +86,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
      * Removes an experience entry from the actor.
      * @type {ApplicationClickAction}
      */
-    static async #removeExperience(_, target) {
+    static async #onRemoveExperience(_, target) {
         const experience = this.actor.system.experiences[target.dataset.experience];
         const confirmed = await foundry.applications.api.DialogV2.confirm({
             window: {
@@ -98,4 +101,28 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
 
         await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
     }
+
+    /**
+     * @this DHAdversarySettings 
+     * @type {ApplicationClickAction}
+     */
+    static #onAddDamage() {
+        this.actor.update({
+            'system.attack.damage.main': {
+                ...DHDamageData.schema.getInitialValue(),
+                applyTo: 'hitPoints',
+                type: 'physical'
+            }
+        });
+    }
+    
+    /**
+     * @this DHAdversarySettings 
+     * @type {ApplicationClickAction}
+     */
+    static #onRemoveDamage() {
+        this.actor.update({
+            'system.attack.damage.main': null
+        });
+    }
 }
diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs
index 199ee87d..c457a6a6 100644
--- a/module/applications/ui/chatLog.mjs
+++ b/module/applications/ui/chatLog.mjs
@@ -110,7 +110,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
                     const message = game.messages.get(li.dataset.messageId);
                     return message.system.hasRoll && (game.user.isGM || message.isAuthor);
                 },
-                callback: async li => {
+                onClick: async (_event, li) => {
                     const message = game.messages.get(li.dataset.messageId);
                     const reroll = await message.rolls[0].reroll({ liveRoll: true });
                     message.update({ rolls: [reroll] });
@@ -126,7 +126,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
                         : false;
                     return (game.user.isGM || message.isAuthor) && hasRolledDamage;
                 },
-                callback: async li => {
+                onClick: async (_event, li) => {
                     const message = game.messages.get(li.dataset.messageId);
                     const update = await message.system.getRerolledDamage();
                     message.update(update);
@@ -179,28 +179,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
     }
 
     async onRollSimple(event, message) {
-        const buttonType = event.target.dataset.type ?? 'damage',
-            total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
-            damages = {
-                hitPoints: {
-                    parts: [
-                        {
-                            applyTo: 'hitPoints',
-                            damageTypes: [],
-                            total
-                        }
-                    ]
-                }
-            },
-            targets = Array.from(game.user.targets);
-
+        const buttonType = event.target.dataset.type ?? 'damage';
+        const total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0);
+        const targets = Array.from(game.user.targets);
         if (targets.length === 0)
             return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
-
-        targets.forEach(target => {
-            if (buttonType === 'healing') target.actor.takeHealing(damages);
-            else target.actor.takeDamage(damages);
-        });
+        for (const target of targets) {
+            if (buttonType === 'healing') target.actor.takeHealing({ hitPoints: total });
+            else target.actor.takeDamage({ total });
+        }
     }
 
     async abilityUseButton(event, message) {
@@ -256,26 +243,17 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
         }
 
         const message = game.messages.get(messageData._id);
-        const target = event.target.closest('[data-die-index]');
+        const target = event.target.closest('[data-result]');
 
         if (target.dataset.type === 'damage') {
-            const { damageType, part, dice, result } = target.dataset;
-            const damagePart = message.system.damage[damageType].parts[part];
-            const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result);
-            const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => {
-                if (index !== Number(part)) return damagePart;
-                return {
-                    ...damagePart,
-                    total: parsedRoll.total,
-                    dice: rerolledDice
-                };
-            });
-            const updateMessage = game.messages.get(message._id);
-            await updateMessage.update({
-                [`system.damage.${damageType}`]: {
-                    total: parsedRoll.total,
-                    parts: damageParts
-                }
+            const { isResource, damageType, dice, result } = target.dataset;
+            await message.system.damage.rerollDamageDie(isResource, damageType, dice, result);
+
+            const updatePath = isResource ? `system.damage.resources.${damageType}` : 'system.damage.main';
+            const updateValue = isResource ? 
+                message.system.damage.resources[damageType] : message.system.damage.main;
+            await message.update({
+                [updatePath]: updateValue.toJSON()
             });
         } else {
             const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs
index 188efafb..802f5907 100644
--- a/module/config/generalConfig.mjs
+++ b/module/config/generalConfig.mjs
@@ -825,7 +825,7 @@ export const refreshTypes = {
 export const itemAbilityCosts = {
     resource: {
         id: 'resource',
-        label: 'DAGGERHEART.GENERAL.resource',
+        label: 'DAGGERHEART.GENERAL.Resource.single',
         group: 'Global'
     },
     quantity: {
diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs
index 1988b1d8..dadc85f0 100644
--- a/module/data/action/attackAction.mjs
+++ b/module/data/action/attackAction.mjs
@@ -13,18 +13,19 @@ export default class DHAttackAction extends DHDamageAction {
             if (this.damage.includeBase) {
                 const baseDamage = this.getParentHitPointDamage();
                 if (baseDamage) {
-                    if (!this.damage.parts.hitPoints) {
-                        this.damage.parts.hitPoints = baseDamage;
+                    if (!this.damage.main) {
+                        this.damage.main = baseDamage;
                     } else {
-                        for (const type of baseDamage.type) this.damage.parts.hitPoints.type.add(type);
+                        for (const type of baseDamage.type) this.damage.main.type.add(type);
 
-                        this.damage.parts.hitPoints.value.custom = {
+                        this.damage.main.value.custom = {
                             enabled: true,
-                            formula: `${baseDamage.value.getFormula()} + ${this.damage.parts.hitPoints.value.getFormula()}`
+                            formula: `${baseDamage.value.getFormula()} + ${this.damage.main.value.getFormula()}`
                         };
                     }
                 }
             }
+            
             if (this.roll.useDefault) {
                 this.roll.trait = this.item.system.attack.roll.trait;
                 this.roll.type = 'attack';
@@ -33,18 +34,18 @@ export default class DHAttackAction extends DHDamageAction {
     }
 
     getParentHitPointDamage() {
-        return this.item?.system?.attack.damage.parts.hitPoints;
+        return this.item?.system?.attack.damage.main;
     }
 
     get damageFormula() {
-        const hitPointsPart = this.damage.parts.hitPoints;
+        const hitPointsPart = this.damage.main;
         if (!hitPointsPart) return '0';
 
         return hitPointsPart.value.getFormula();
     }
 
     get altDamageFormula() {
-        const hitPointsPart = this.damage.parts.hitPoints;
+        const hitPointsPart = this.damage.main;
         if (!hitPointsPart) return '0';
 
         return hitPointsPart.valueAlt.getFormula();
@@ -73,7 +74,7 @@ export default class DHAttackAction extends DHDamageAction {
         if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
 
         const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active;
-        for (const { value, valueAlt, type } of damage.parts) {
+        for (const { value, valueAlt, type } of [damage.main, ...damage.resources].filter(d => !!d)) {
             const usedValue = useAltDamage ? valueAlt : value;
             const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
             const str = damageString
@@ -82,7 +83,7 @@ export default class DHAttackAction extends DHDamageAction {
                     x: game.i18n.localize('DAGGERHEART.GENERAL.damage')
                 });
 
-            const icons = Array.from(type)
+            const icons = Array.from(type ?? [])
                 .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
                 .filter(Boolean);
 
diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs
index 5b871c9a..05890335 100644
--- a/module/data/action/baseAction.mjs
+++ b/module/data/action/baseAction.mjs
@@ -289,7 +289,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
             hasEffect: this.hasEffect,
             hasSave: this.hasSave,
             onSave: this.save?.damageMod,
-            isDirect: !!this.damage?.direct,
             selectedMessageMode: game.settings.get('core', 'messageMode'),
             data: this.getRollData(),
             evaluate: this.hasRoll,
@@ -307,20 +306,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
         };
 
         if (this.damage) {
-            config.isDirect = this.damage.direct;
+            config.isDirect = !!this.damage.main?.direct;
 
-            const groupAttackTokens = this.damage.groupAttack
+            const groupAttackTokens = this.damage.main?.groupAttack
                 ? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
                     this.actor.id,
-                    this.damage.groupAttack
+                    this.damage.main.groupAttack
                 )
                 : null;
 
             config.damageOptions = {
-                groupAttack: this.damage.groupAttack
+                groupAttack: this.damage.main?.groupAttack
                     ? {
                         numAttackers: Math.max(groupAttackTokens.length, 1),
-                        range: this.damage.groupAttack
+                        range: this.damage.main.groupAttack
                     }
                     : null
             };
@@ -430,11 +429,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
     }
 
     get hasDamage() {
-        return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type !== 'healing';
+        return this.type !== 'healing' && (Boolean(this.damage.main) || !foundry.utils.isEmpty(this.damage.resources));
     }
 
     get hasHealing() {
-        return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type === 'healing';
+        return this.type === 'healing' && !foundry.utils.isEmpty(this.damage.resources);
     }
 
     get hasSave() {
@@ -470,6 +469,25 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
                 return acc;
             }, {});
         }
+
+        if (source.damage?.parts && !source.damage.resources && !source.damage.main) {
+            source.damage.main = null;
+            source.damage.resources = {};
+            for (const [partKey, part] of Object.entries(source.damage.parts)) {
+                if (partKey === 'hitPoints' && source.type !== 'healing') {
+                    source.damage.main = {
+                        ...part,
+                        includeBase: source.damage.includeBase,
+                        direct: source.damage.direct,
+                        groupAttack: source.damage.groupAttack
+                    };
+                } else {
+                    source.damage.resources[partKey] = part;
+                }
+            }
+
+            delete source.damage.parts;
+        }
     }
 }
 
diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs
index 51735543..15135e0d 100644
--- a/module/data/action/damageAction.mjs
+++ b/module/data/action/damageAction.mjs
@@ -8,11 +8,8 @@ export default class DHDamageAction extends DHBaseAction {
      * @returns Formula string
      */
     getDamageFormula() {
-        const strings = [];
-        for (const { value } of this.damage.parts) {
-            strings.push(Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {}));
-        }
+        if (!this.damage.main) return '';
 
-        return strings.join(' + ');
+        return Roll.replaceFormulaData(this.damage.main.value.getFormula(), this.actor?.getRollData() ?? {});
     }
 }
diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs
index ae17c128..2640211f 100644
--- a/module/data/actor/adversary.mjs
+++ b/module/data/actor/adversary.mjs
@@ -84,13 +84,11 @@ export default class DhpAdversary extends DhCreature {
                         type: 'attack'
                     },
                     damage: {
-                        parts: {
-                            hitPoints: {
-                                type: ['physical'],
-                                applyTo: 'hitPoints',
-                                value: {
-                                    multiplier: 'flat'
-                                }
+                        main: {
+                            type: ['physical'],
+                            applyTo: 'hitPoints',
+                            value: {
+                                multiplier: 'flat'
                             }
                         }
                     }
diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs
index b39c64aa..8ff50a5d 100644
--- a/module/data/actor/character.mjs
+++ b/module/data/actor/character.mjs
@@ -104,15 +104,13 @@ export default class DhCharacter extends DhCreature {
                         trait: 'strength'
                     },
                     damage: {
-                        parts: {
-                            hitPoints: {
-                                type: ['physical'],
-                                applyTo: 'hitPoints',
-                                value: {
-                                    custom: {
-                                        enabled: true,
-                                        formula: '@profd4'
-                                    }
+                        main: {
+                            type: ['physical'],
+                            applyTo: 'hitPoints',
+                            value: {
+                                custom: {
+                                    enabled: true,
+                                    formula: '@profd4'
                                 }
                             }
                         }
@@ -838,7 +836,7 @@ export default class DhCharacter extends DhCreature {
             isReversed: true
         };
 
-        this.attack.damage.parts.hitPoints.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
+        this.attack.damage.main.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
 
         // Clamp resources (must be done last to ensure all updates occur)
         this.resources.clamp();
diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs
index 8b9e5bdc..4bf74a45 100644
--- a/module/data/actor/tierAdjustment.mjs
+++ b/module/data/actor/tierAdjustment.mjs
@@ -40,17 +40,17 @@ export function getTierAdjustedAdversary(source, tier) {
 
     // Store initial attack damage for abilities that have you deal a "standard attack"
     const initialAttack = {
-        type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(),
-        value: getFormula(source.system.attack.damage?.parts.hitPoints?.value)
+        type: source.system.attack.damage?.main?.type?.toSorted(),
+        value: getFormula(source.system.attack.damage?.main?.value)
     };
 
     // Update damage of base attack.
     try {
         const damage = source.system.attack.damage;
-        if (!damage?.parts.hitPoints) throw new Error('Unexpected missing attack in adversary');
+        if (!damage?.main) throw new Error('Unexpected missing damage in adversary');
 
         for (const property of ['value', 'valueAlt']) {
-            const data = damage.parts.hitPoints[property];
+            const data = damage.main[property];
             const previousFormula = getFormula(data);
             const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta);
             applyAdjustedDamage(data, value);
@@ -82,12 +82,12 @@ export function getTierAdjustedAdversary(source, tier) {
 
         // Update damage in item actions and convert all formula matches in the descriptions to the new damage
         for (const action of Object.values(item.system.actions)) {
-            if (!action.damage?.parts.hitPoints) continue;
+            if (!action.damage?.main) continue;
             try {
                 // Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead
                 const result = [];
                 for (const property of ['value', 'valueAlt']) {
-                    const { [property]: data, type: damageType } = action.damage.parts.hitPoints;
+                    const { [property]: data, type: damageType } = action.damage.main;
                     const previousFormula = getFormula(data);
                     const isActuallyAttack =
                         previousFormula === initialAttack.value &&
@@ -199,7 +199,7 @@ function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageR
 }
 
 /** 
- * Get formula from either damage parts *or* a simple formula object.
+ * Get formula from either damage data *or* a simple formula object.
  * @returns {string} the new formula data
  */
 function getFormula(data) {
diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs
index ccfe25ea..dd81952b 100644
--- a/module/data/chat-message/actorRoll.mjs
+++ b/module/data/chat-message/actorRoll.mjs
@@ -1,4 +1,5 @@
 import { triggerChatRollFx } from '../../helpers/utils.mjs';
+import { ChatDamageData } from './chatDamageData.mjs';
 
 const fields = foundry.data.fields;
 
@@ -49,7 +50,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
                 originItem: originItemField(),
                 action: new fields.StringField()
             }),
-            damage: new fields.ObjectField(),
+            damage: new fields.EmbeddedDataField(ChatDamageData),
             damageOptions: new fields.ObjectField(),
             costs: new fields.ArrayField(new fields.ObjectField()),
             successConsumed: new fields.BooleanField({ initial: false })
@@ -132,28 +133,21 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
         });
     }
 
-    /* TODO: Change how damage data is stored somehow to enable better rerolling */
     async getRerolledDamage() {
-        if (!this.damage) return;
+        if (!this.damage.active) return;
 
         const rerolls = [];
-        const update = { system: { damage: {} } };
-        for (const partKey in this.damage) {
-            const part = this.damage[partKey];
-            const testRoll = Roll.fromData(part.parts[0].roll);
-            const rerolled = await testRoll.reroll();
-            rerolls.push(rerolled);
+        const update = { system: { damage: { main: null, resources: _replace({}) } } };
+        if (this.damage.main) {
+            const reroll = await this.damage.main.reroll();
+            rerolls.push(reroll);
+            update.system.damage.main = reroll.toJSON();
+        }
 
-            if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] };
-            const partData = update.system.damage[partKey].parts[0];
-            update.system.damage[partKey].total = rerolled.total;
-            partData.modifierTotal = rerolled.terms.reduce((acc, x) => {
-                if (x.isDeterministic && !x.operator) acc += x.total;
-                return acc;
-            }, 0);
-            partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination }));
-            partData.total = rerolled.total;
-            partData.roll = rerolled.toJSON();
+        for (const key of Object.keys(this.damage.resources)) {
+            const reroll = await this.damage.resources[key].reroll();
+            rerolls.push(reroll);
+            update.system.damage.resources[key] = reroll.toJSON();
         }
 
         await triggerChatRollFx(rerolls);
@@ -198,6 +192,43 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
         this.isGM = game.user.isGM; //temp
     }
 
+    static migrateData(source) {
+        const { main, resources, ...flatDamageKeys } = source.damage ?? {};
+        if (source.damage && !main && !resources) {
+            source.damage.main = null;
+            source.damage.resources = {};
+
+            const getRoll = key => {
+                const damageData = source.damage[key];
+                const oldRoll = damageData.parts[0]?.roll;
+                return oldRoll ? JSON.stringify({
+                    ...oldRoll,
+                    class: 'BaseRoll',
+                    options: {
+                        ...oldRoll.options,
+                        damageTypes: damageData.parts[0].damageTypes ?? []
+                    }
+                }) : null;
+            };
+
+            for (const key of Object.keys(flatDamageKeys)) {
+                if (key === 'hitPoints' && source.hasDamage && !source.hasHealing) {
+                    source.damage.main = getRoll('hitPoints');
+                } 
+                else {
+                    source.damage.resources[key] = getRoll(key);
+                }
+            }
+        }
+
+        for (const key of Object.keys(flatDamageKeys)) {
+            delete source.damage[key];
+        }
+        
+        return source;
+    }
+
+
     getTargetList() {
         const targets =
                 this.targetMode && this.parent.isAuthor
diff --git a/module/data/chat-message/chatDamageData.mjs b/module/data/chat-message/chatDamageData.mjs
new file mode 100644
index 00000000..404cda43
--- /dev/null
+++ b/module/data/chat-message/chatDamageData.mjs
@@ -0,0 +1,58 @@
+import { triggerChatRollFx } from '../../helpers/utils.mjs';
+
+export class ChatDamageData extends foundry.abstract.DataModel {
+    constructor(data = {}, options = {}) {
+        super(data, options);
+        
+        this._prepareRolls();
+    }
+
+    static defineSchema() {
+        const fields = foundry.data.fields;
+        
+        return {
+            main: new fields.JSONField({ nullable: true, validate: ChatDamageData.#validateRoll}),
+            resources: new fields.TypedObjectField(new fields.JSONField({validate: ChatDamageData.#validateRoll}))
+        };
+    }
+
+    get active() {
+        return !!this.main || Boolean(Object.keys(this.resources).length);
+    }
+
+    static #validateRoll(rollJSON) {
+        if (rollJSON) {
+            const roll = JSON.parse(rollJSON);
+            if (!roll.evaluated) throw new Error('Roll objects added to ChatMessage documents must be evaluated');
+        }
+    }
+
+    _prepareRolls() {
+        this.main &&= Roll.fromData(this.main);
+        for (const key of Object.keys(this.resources)) {
+            this.resources[key] = Roll.fromData(this.resources[key]);
+        }
+    }
+
+    async rerollDamageDie(isResource, damageType, dice, resultIndex) {
+        const reroll = isResource ? this.resources[damageType] : this.main;
+        const rerollDice = reroll.dice[dice];
+        await rerollDice.rerollResult(resultIndex);
+        await reroll._evaluate();
+    
+        const rerolledResult = rerollDice.results[rerollDice.results.length - 1];
+        if (rerolledResult) {
+            const fakeRoll = {
+                _evaluated: true,
+                dice: [new foundry.dice.terms.Die({
+                    ...rerollDice,
+                    results: [rerolledResult],
+                    total: rerolledResult.value,
+                    faces: rerollDice.faces
+                })],
+                options: { appearance: {} }
+            };
+            await triggerChatRollFx([fakeRoll]);
+        }
+    }
+}
\ No newline at end of file
diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs
index 2c6c7b30..25b3ba97 100644
--- a/module/data/fields/action/damageField.mjs
+++ b/module/data/fields/action/damageField.mjs
@@ -12,20 +12,10 @@ 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'
-            }),
-            direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
-            groupAttack: new fields.StringField({
-                choices: CONFIG.DH.GENERAL.groupAttackRange,
-                blank: true,
-                label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
-            })
-        };
-        super(damageFields, options, context);
+        super({
+            main: new fields.EmbeddedDataField(DHDamageData, { nullable: true }),
+            resources: new IterableTypedObjectField(DHResourceData)
+        }, options, context);
     }
 
     /**
@@ -41,25 +31,23 @@ export default class DamageField extends fields.SchemaField {
             this.hasRoll &&
             DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id &&
             !force
-        )
+        ) {
             return;
+        }
 
-        let formulas = this.damage.parts.map(p => ({
-            formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor),
-            damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
-            applyTo: p.applyTo
-        }));
+        const damageFormula = this.damage.main ? 
+            DamageField.formatFormulas.call(this, [this.damage.main], config)[0] : null;
+        const resourceFormulas = DamageField.formatFormulas.call(this, this.damage.resources, config);
 
-        if (!formulas.length) return false;
-
-        formulas = DamageField.formatFormulas.call(this, formulas, config);
+        if (!damageFormula && !resourceFormulas.length) return false;
 
         messageId = config.message?._id ?? messageId;
         const message = game.messages.get(messageId);
         const damageConfig = {
             dialog: {},
             ...config,
-            roll: formulas,
+            damageFormula,
+            resourceFormulas, 
             data: this.getRollData(),
             isCritical: Boolean(message?.system.roll?.isCritical)
         };
@@ -92,7 +80,7 @@ export default class DamageField extends fields.SchemaField {
 
         const targetDamage = [];
         const damagePromises = [];
-        for (let target of targets) {
+        for (const target of targets) {
             const actor = foundry.utils.fromUuidSync(target.actorId);
             if (!actor) continue;
             if (!config.hasHealing && config.onSave && target.saved?.success === true) {
@@ -114,13 +102,12 @@ export default class DamageField extends fields.SchemaField {
                     actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
                 );
             else {
-                const configDamage = foundry.utils.deepClone(config.damage);
-                const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
-                const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
-                if (configDamage.hitPoints) {
-                    for (const part of configDamage.hitPoints.parts) {
-                        part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
-                    }
+                const configDamage = config.damage.clone();
+                configDamage.main &&= configDamage.main.toJSON();
+                if (configDamage.main) {
+                    const multiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
+                    const takenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
+                    configDamage.main.total = Math.ceil(configDamage.main.total * multiplier * takenMultiplier);
                 }
 
                 damagePromises.push(
@@ -183,13 +170,19 @@ export default class DamageField extends fields.SchemaField {
     /**
      * Prepare formulas for Damage Roll
      * Must be called within Action context or similar.
-     * @param {object[]} formulas   Array of formatted formulas object
+     * @param {DHResourceData[]} damageData  Array of DHResourceData
      * @param {object} data         Action getRollData
      * @returns
      */
-    static formatFormulas(formulas, data) {
+    static formatFormulas(damageData, data) {
+        const formulas = damageData.map(x => ({
+            formula: DamageField.getFormulaValue.call(this, x, data).getFormula(this.actor),
+            damageTypes: x.type ?? new Set(),
+            applyTo: x.applyTo
+        }));
+
         const formattedFormulas = [];
-        formulas.forEach(formula => {
+        for (const formula of formulas) {
             if (isNaN(formula.formula))
                 formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
             const same = formattedFormulas.find(
@@ -197,7 +190,8 @@ export default class DamageField extends fields.SchemaField {
             );
             if (same) same.formula += ` + ${formula.formula}`;
             else formattedFormulas.push(formula);
-        });
+        }
+
         return formattedFormulas;
     }
 
@@ -294,6 +288,7 @@ export class DHResourceData extends foundry.abstract.DataModel {
     /** @override */
     static defineSchema() {
         return {
+            base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
             applyTo: new fields.StringField({
                 choices: CONFIG.DH.GENERAL.healingTypes,
                 required: true,
@@ -316,7 +311,16 @@ export class DHDamageData extends DHResourceData {
     static defineSchema() {
         return {
             ...super.defineSchema(),
-            base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
+            includeBase: new fields.BooleanField({
+                initial: false,
+                label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
+            }),
+            direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
+            groupAttack: new fields.StringField({
+                choices: CONFIG.DH.GENERAL.groupAttackRange,
+                blank: true,
+                label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
+            }),
             type: new fields.SetField(
                 new fields.StringField({
                     choices: CONFIG.DH.GENERAL.damageTypes,
diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs
index ba2fa37e..f3bdce8a 100644
--- a/module/data/fields/actionField.mjs
+++ b/module/data/fields/actionField.mjs
@@ -238,6 +238,7 @@ export function ActionMixin(Base) {
                 result = this.parent;
             } else {
                 result = await this.item.update({ [path]: updates }, options);
+                if (!result) return result;
             }
 
             return this.inCollection
diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs
index 42326f93..5ff2d8d1 100644
--- a/module/data/item/weapon.mjs
+++ b/module/data/item/weapon.mjs
@@ -67,13 +67,11 @@ export default class DHWeapon extends AttachableItem {
                         type: 'attack'
                     },
                     damage: {
-                        parts: {
-                            hitPoints: {
-                                type: ['physical'],
-                                value: {
-                                    multiplier: 'prof',
-                                    dice: 'd8'
-                                }
+                        main: {
+                            type: ['physical'],
+                            value: {
+                                multiplier: 'prof',
+                                dice: 'd8'
                             }
                         }
                     }
@@ -230,11 +228,12 @@ export default class DHWeapon extends AttachableItem {
             game.i18n.localize(`DAGGERHEART.CONFIG.Burden.${burden}`)
         ];
 
-        for (const { value, type } of attack.damage.parts) {
+        if (attack.damage.main) {
+            const { value, type } = attack.damage.main;
             const parts = value.custom.enabled ? [game.i18n.localize('DAGGERHEART.GENERAL.custom')] : [value.dice];
             if (!value.custom.enabled && value.bonus) parts.push(value.bonus.signedString());
 
-            if (type.size > 0) {
+            if (type?.size) {
                 const typeTags = Array.from(type)
                     .map(t => game.i18n.localize(`DAGGERHEART.CONFIG.DamageType.${t}.abbreviation`))
                     .join(' | ');
@@ -243,7 +242,7 @@ export default class DHWeapon extends AttachableItem {
 
             tags.push(parts.join(''));
         }
-
+        
         return tags;
     }
 
@@ -258,10 +257,10 @@ export default class DHWeapon extends AttachableItem {
         if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`));
         if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
 
-        for (const { value, type } of damage.parts) {
+        for (const { value, type } of [damage.main, ...damage.resources].filter(d => !!d)) {
             const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
 
-            const icons = Array.from(type)
+            const icons = Array.from(type ?? [])
                 .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
                 .filter(Boolean);
 
diff --git a/module/data/tagTeamData.mjs b/module/data/tagTeamData.mjs
index 640c2f6c..ef6536f1 100644
--- a/module/data/tagTeamData.mjs
+++ b/module/data/tagTeamData.mjs
@@ -1,3 +1,5 @@
+import { ChatDamageData } from './chat-message/chatDamageData.mjs';
+
 export default class TagTeamData extends foundry.abstract.DataModel {
     static defineSchema() {
         const fields = foundry.data.fields;
@@ -37,6 +39,7 @@ export class MemberData extends foundry.abstract.DataModel {
             }),
             rollChoice: new fields.StringField({ nullable: true, initial: null }),
             rollData: new fields.JSONField({ nullable: true, initial: null }),
+            damageRollData: new fields.EmbeddedDataField(ChatDamageData),
             selected: new fields.BooleanField({ initial: false })
         };
     }
diff --git a/module/dice/baseRoll.mjs b/module/dice/baseRoll.mjs
index 4d065fff..2c42d38a 100644
--- a/module/dice/baseRoll.mjs
+++ b/module/dice/baseRoll.mjs
@@ -4,4 +4,8 @@ export default class BaseRoll extends Roll {
 
     /** @inheritdoc */
     static TOOLTIP_TEMPLATE = 'systems/daggerheart/templates/ui/chat/foundryRollTooltip.hbs';
+
+    get modifierTotal() {
+        return this.total - this.dice.reduce((acc, dice) => acc + dice.total, 0);
+    }
 }
diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs
index 3f2f79e0..683d14d8 100644
--- a/module/dice/damageRoll.mjs
+++ b/module/dice/damageRoll.mjs
@@ -13,36 +13,34 @@ export default class DamageRoll extends DHRoll {
 
     static DefaultDialog = DamageDialog;
 
+    static createRollInstance(config) {
+        return new this(undefined, config.data, config);
+    }
+
     /** @inheritdoc */
-    static async buildEvaluate(roll, config = {}, message = {}) {
-        if (config.dialog.configure === false) roll.constructFormula(config);
-        for (const roll of config.roll) await roll.roll.evaluate();
-
-        roll._evaluated = true;
-
-        const parts = [];
-        for (const rollData of config.roll) {
-            const roll = rollData.roll;
-            parts.push({
-                ...rollData,
-                ...roll.options.roll,
-                total: roll.total,
-                formula: roll.formula,
-                dice: roll.dice.map(d => ({
-                    dice: d.denomination,
-                    total: d.total,
-                    formula: d.formula,
-                    results: d.results
-                })),
-                damageTypes: [...(rollData.damageTypes ?? [])],
-                roll,
-                type: config.type,
-                modifierTotal: this.calculateTotalModifiers(roll)
-            });
-            rollData.roll = JSON.stringify(roll.toJSON());
+    static async buildEvaluate(roll, config = {}) {
+        if (config.dialog.configure === false) roll.constructFormulas(config);
+        
+        const evaluateRoll = async roll => {
+            await roll.roll.evaluate();
+            roll.roll.options = { damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] };
+            return roll.roll;
         }
 
-        config.damage = this.unifyDamageRoll(parts);
+        if (!config.damage) config.damage = { main: null, resources: {} };
+
+        if (config.damageFormula) {
+            config.damage.main = await evaluateRoll(config.damageFormula);
+            config.damage.main.options = { damageTypes: 
+                config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : []
+            };
+        }
+        
+        for (const roll of config.resourceFormulas) {
+            config.damage.resources[roll.applyTo] = await evaluateRoll(roll);
+        }
+
+        roll._evaluated = true;
     }
 
     static async buildPost(roll, config, message) {
@@ -53,9 +51,10 @@ export default class DamageRoll extends DHRoll {
         const diceRolls = [];
         if (game.modules.get('dice-so-nice')?.active) {
             config.mute = true;
-            const pool = foundry.dice.terms.PoolTerm.fromRolls(
-                Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
-            );
+            const pool = foundry.dice.terms.PoolTerm.fromRolls([
+                ...(config.damage.main ? [config.damage.main] : []),
+                ...Object.values(config.damage.resources)
+            ]);
             diceRolls.push(Roll.fromTerms([pool]));
         }
 
@@ -66,22 +65,14 @@ export default class DamageRoll extends DHRoll {
         await super.buildPost(roll, config, message);
 
         if (config.source?.message) {
-            chatMessage.update({ 'system.damage': config.damage });
+            chatMessage.update({ 'system.damage': {
+                ...config.damage.toObject(),
+                main: config.damage.main,
+                resources: config.damage.resources
+            }});
         }
     }
 
-    static unifyDamageRoll(rolls) {
-        const unified = {};
-        rolls.forEach(r => {
-            const resource = unified[r.applyTo] ?? { formula: '', total: 0, parts: [] };
-            resource.formula += `${resource.formula !== '' ? ' + ' : ''}${r.formula}`;
-            resource.total += r.total;
-            resource.parts.push(r);
-            unified[r.applyTo] = resource;
-        });
-        return unified;
-    }
-
     static formatGlobal(rolls) {
         let formula, total;
         const applyTo = new Set(rolls.flatMap(r => r.applyTo));
@@ -130,12 +121,10 @@ export default class DamageRoll extends DHRoll {
         const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage');
         const changeKeys = [];
 
-        for (const roll of this.options.roll) {
-            for (const damageType of roll.damageTypes?.values?.() ?? []) {
-                changeKeys.push(`system.bonuses.${type}.${damageType}`);
-            }
+        for (const damageType of this.options.damageFormula?.damageTypes?.values?.() ?? []) {
+            changeKeys.push(`system.bonuses.${type}.${damageType}`);
         }
-
+        
         const item = this.data.parent?.items?.get(this.options.source.item);
         if (item) {
             switch (item.type) {
@@ -151,62 +140,69 @@ export default class DamageRoll extends DHRoll {
         return changeKeys;
     }
 
-    constructFormula(config) {
+    constructFormulas(config) {
+        return {
+            damageFormula: this.constructFormula(this.options.damageFormula, config, true),
+            resourceFormulas: this.options.resourceFormulas.map(x => this.constructFormula(x, config))
+        };
+    }
+
+    constructFormula(formulaData, config, isDamage) {
+        if (!formulaData) return null;
         this.options.isCritical = config.isCritical;
-        for (const [index, part] of this.options.roll.entries()) {
-            const isHitpointPart = part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id;
-            part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
-            part.roll.terms = Roll.parse(part.roll.formula, config.data);
-            if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
-                part.modifiers = this.applyBaseBonus(part);
-                this.addModifiers(part);
-                part.modifiers?.forEach(m => {
-                    part.roll.terms.push(...this.formatModifier(m.value));
-                });
+
+        formulaData.roll = new Roll(Roll.replaceFormulaData(formulaData.formula, config.data));
+        formulaData.roll.terms = Roll.parse(formulaData.roll.formula, config.data);
+
+        if (formulaData.extraFormula) {
+            formulaData.roll.terms.push(
+                new foundry.dice.terms.OperatorTerm({ operator: '+' }),
+                ...this.constructor.parse(formulaData.extraFormula, this.options.data)
+            );
+        }
+
+        if (isDamage && formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
+            formulaData.modifiers = this.applyBaseBonus(formulaData);
+            this.addModifiers(formulaData);
+            formulaData.modifiers?.forEach(m => {
+                formulaData.roll.terms.push(...this.formatModifier(m.value));
+            });
+
+            /* To Remove When Reaction System */
+            for (const mod in config.modifiers) {
+                const modifier = config.modifiers[mod];
+                if (
+                    modifier.beforeCrit === true && 
+                    (modifier.enabled || modifier.value)
+                ) modifier.callback(formulaData);
             }
 
             /* To Remove When Reaction System */
-            if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
-                for (const mod in config.modifiers) {
-                    const modifier = config.modifiers[mod];
-                    if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part);
-                }
+            for (const mod in config.modifiers) {
+                const modifier = config.modifiers[mod];
+                if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(formulaData);
             }
 
-            if (part.extraFormula) {
-                part.roll.terms.push(
-                    new foundry.dice.terms.OperatorTerm({ operator: '+' }),
-                    ...this.constructor.parse(part.extraFormula, this.options.data)
-                );
-            }
-
-            if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) {
+            if (config.damageOptions.groupAttack?.numAttackers > 1) {
                 const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm];
-                for (const term of part.roll.terms) {
+                for (const term of formulaData.roll.terms) {
                     if (damageTypes.some(type => term instanceof type)) {
                         term.number *= config.damageOptions.groupAttack.numAttackers;
                     }
                 }
             }
 
-            if (config.isCritical && isHitpointPart) {
-                const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
+            if (config.isCritical) {
+                const total = formulaData.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
                 if (total > 0) {
-                    part.roll.terms.push(...this.formatModifier(total));
+                    formulaData.roll.terms.push(...this.formatModifier(total));
                 }
             }
-
-            /* To Remove When Reaction System */
-            if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
-                for (const mod in config.modifiers) {
-                    const modifier = config.modifiers[mod];
-                    if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(part);
-                }
-            }
-
-            part.roll._formula = this.constructor.getFormula(part.roll.terms);
         }
-        return this.options.roll;
+
+        formulaData.roll._formula = this.constructor.getFormula(formulaData.roll.terms);
+        
+        return formulaData;
     }
 
     /* To Remove When Reaction System */
@@ -299,76 +295,4 @@ export default class DamageRoll extends DHRoll {
         config.modifiers = mods;
         return mods;
     }
-
-    static async reroll(rollPart, dice, result) {
-        let diceIndex = 0;
-        let parsedRoll = game.system.api.dice.DamageRoll.fromData({
-            ...rollPart.roll,
-            terms: rollPart.roll.terms.map(term => {
-                const isDie = term.class === 'Die';
-                const fixedTerm = {
-                    ...term,
-                    ...(isDie ? { results: rollPart.dice[diceIndex].results } : {})
-                };
-
-                if (isDie) diceIndex++;
-                return fixedTerm;
-            }),
-            class: 'DamageRoll',
-            evaluated: false
-        });
-
-        const parsedDiceTerms = Object.keys(parsedRoll.terms).reduce((acc, key) => {
-            const term = parsedRoll.terms[key];
-            if (term instanceof CONFIG.Dice.termTypes.DiceTerm) acc[Object.keys(acc).length] = term;
-            return acc;
-        }, {});
-        const term = parsedDiceTerms[dice];
-        const termResult = parsedDiceTerms[dice].results[result];
-
-        const newIndex = parsedDiceTerms[dice].results.length;
-        await term.reroll(`/r1=${termResult.result}`);
-
-        const diceRolls = [];
-        if (game.modules.get('dice-so-nice')?.active) {
-            const newResult = parsedDiceTerms[dice].results[newIndex];
-            diceRolls.push({
-                _evaluated: true,
-                dice: [
-                    new foundry.dice.terms.Die({
-                        ...term,
-                        total: newResult.result,
-                        faces: term._faces,
-                        results: [newResult]
-                    })
-                ],
-                options: { appearance: {} }
-            });
-        }
-
-        await triggerChatRollFx(diceRolls);
-        await parsedRoll.evaluate();
-
-        const results = parsedRoll.dice[dice].results.map(result => ({
-            ...result,
-            discarded: !result.active
-        }));
-        const newResult = results.splice(results.length - 1, 1);
-        results.splice(Number(result) + 1, 0, newResult[0]);
-
-        const rerolledDice = parsedRoll.dice.map((x, index) => {
-            const isRerollDice = index === Number(dice);
-            if (!isRerollDice) return { ...x, dice: x.denomination };
-            return {
-                dice: parsedRoll.dice[dice].denomination,
-                total: parsedRoll.dice[dice].total,
-                results: results.map(result => ({
-                    ...result,
-                    hasRerolls: result.hasRerolls || isRerollDice
-                }))
-            };
-        });
-
-        return { parsedRoll, rerolledDice };
-    }
 }
diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs
index 16472fea..c78caa4f 100644
--- a/module/dice/dhRoll.mjs
+++ b/module/dice/dhRoll.mjs
@@ -1,7 +1,8 @@
 import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
 import { triggerChatRollFx } from '../helpers/utils.mjs';
+import BaseRoll from './baseRoll.mjs';
 
-export default class DHRoll extends Roll {
+export default class DHRoll extends BaseRoll {
     baseTerms = [];
     constructor(formula, data = {}, options = {}) {
         super(formula, data, foundry.utils.mergeObject(options, { roll: [] }, { overwrite: false }));
@@ -40,6 +41,10 @@ export default class DHRoll extends Roll {
         return config;
     }
 
+    static createRollInstance(config) {
+        return new this(config.roll.formula, config.data, config);
+    }
+
     /** 
      * @param {Partial<RollConfig>} config 
      * @returns {Promise<RollConfig>}
@@ -57,7 +62,7 @@ export default class DHRoll extends Roll {
 
         this.temporaryModifierBuilder(config);
 
-        let roll = new this(config.roll.formula, config.data, config);
+        let roll = this.createRollInstance(config);
         if (config.dialog.configure !== false) {
             // Open Roll Dialog
             const DialogClass = config.dialog?.class ?? this.DefaultDialog;
diff --git a/module/dice/die/_module.mjs b/module/dice/die/_module.mjs
index 19ca951a..b84cef86 100644
--- a/module/dice/die/_module.mjs
+++ b/module/dice/die/_module.mjs
@@ -3,6 +3,7 @@ import HopeDie from './hopeDie.mjs';
 import FearDie from './fearDie.mjs';
 import AdvantageDie from './advantageDie.mjs';
 import DisadvantageDie from './disadvantageDie.mjs';
+export { default as BaseDie } from './baseDie.mjs';
 
 export const diceTypes = {
     DualityDie,
diff --git a/module/dice/die/advantageDie.mjs b/module/dice/die/advantageDie.mjs
index 9c2f0b03..66b6f124 100644
--- a/module/dice/die/advantageDie.mjs
+++ b/module/dice/die/advantageDie.mjs
@@ -1,4 +1,6 @@
-export default class AdvantageDie extends foundry.dice.terms.Die {
+import BaseDie from './baseDie.mjs';
+
+export default class AdvantageDie extends BaseDie {
     constructor(options) {
         super(options);
 
diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs
new file mode 100644
index 00000000..cc88f753
--- /dev/null
+++ b/module/dice/die/baseDie.mjs
@@ -0,0 +1,12 @@
+export default class BaseDie extends foundry.dice.terms.Die {
+    async rerollResult(resultIndex) {
+        const result = this.results[resultIndex];
+        result.rerolled = true;
+        result.active = false;
+        await this.roll({ reroll: true });
+
+        const rerolledResult = this.results[this.results.length - 1];
+        this.results.splice(this.results.length - 1, 1);
+        this.results.splice(resultIndex, 0, rerolledResult);
+    }
+}
\ No newline at end of file
diff --git a/module/dice/die/disadvantageDie.mjs b/module/dice/die/disadvantageDie.mjs
index f56ebe96..e79845f3 100644
--- a/module/dice/die/disadvantageDie.mjs
+++ b/module/dice/die/disadvantageDie.mjs
@@ -1,4 +1,6 @@
-export default class DisadvantageDie extends foundry.dice.terms.Die {
+import BaseDie from './baseDie.mjs';
+
+export default class DisadvantageDie extends BaseDie {
     constructor(options) {
         super(options);
 
diff --git a/module/dice/die/dualityDie.mjs b/module/dice/die/dualityDie.mjs
index cc7ee75e..2f4dae77 100644
--- a/module/dice/die/dualityDie.mjs
+++ b/module/dice/die/dualityDie.mjs
@@ -1,6 +1,7 @@
+import BaseDie from './baseDie.mjs';
 import { updateResourcesForDualityReroll } from '../helpers.mjs';
 
-export default class DualityDie extends foundry.dice.terms.Die {
+export default class DualityDie extends BaseDie {
     constructor(options) {
         super(options);
 
diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs
index 38d9315f..6503efb1 100644
--- a/module/dice/dualityRoll.mjs
+++ b/module/dice/dualityRoll.mjs
@@ -409,8 +409,4 @@ export default class DualityRoll extends D20Roll {
 
         return rerolled;
     }
-
-    fromJSON(json) {
-        return super.fromJSON(json);
-    }
 }
diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs
index f8880597..666d9ac1 100644
--- a/module/documents/actor.mjs
+++ b/module/documents/actor.mjs
@@ -622,6 +622,11 @@ export default class DhpActor extends Actor {
         return rollData;
     }
 
+    /** 
+     * Checks to see if damage can be reduced in one way or another.
+     * @param {number} hpDamage the amount of marked hp that will be marked
+     * @param {Set<string>} types a list of damage types
+     */
     #canReduceDamage(hpDamage, types) {
         const { stressDamageReduction, disabledArmor, reduceSeverity, thresholdImmunities } = 
             this.system.rules.damageReduction;
@@ -648,35 +653,32 @@ export default class DhpActor extends Actor {
         return canUseArmor || canUseStress || hasReduceSeverity || hasThresholdImmunity;
     }
 
-    async takeDamage(damages, isDirect = false) {
-        if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damages) === false) return null;
+    async takeDamage(args, isDirect = false) {
+        args = this.#parseDamageArgs(args);
+        if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, args) === false) return null;
 
         if (this.type === 'companion') {
             await this.modifyResource([{ value: 1, key: 'stress' }]);
             return;
         }
 
-        const updates = [];
+        const updates = args.resourceUpdates;
+        if (args.main) {
+            // todo: avoid side effects, but hook currently requires it
+            args.main.value = this.calculateDamage(args.main.value, args.main.damageTypes);
+        }
 
-        Object.entries(damages).forEach(([key, damage]) => {
-            damage.parts.forEach(part => {
-                if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
-                    part.total = this.calculateDamage(part.total, part.damageTypes);
-                const update = updates.find(u => u.key === key);
-                if (update) {
-                    update.value += part.total;
-                    update.damageTypes.add(...new Set(part.damageTypes));
-                } else updates.push({ value: part.total, key, damageTypes: new Set(part.damageTypes) });
-            });
-        });
+        if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, args) === false) return null;
 
-        if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null;
+        // Convert deducted resources to a record of updates. Return if nothing to do.
+        if (!updates.some(u => u.value) && !args.main) return; 
 
-        if (!updates.length) return;
-
-        const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
-        if (hpDamage?.value) {
-            hpDamage.value = this.convertDamageToThreshold(hpDamage.value);
+        if (args.main) {
+            const hpDamage = { 
+                value: this.convertDamageToThreshold(args.main.value),
+                damageTypes: new Set(args.main.damageTypes), 
+                key: CONFIG.DH.GENERAL.healingTypes.hitPoints.id
+            };
             if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
                 const armorSlotResult = await this.owner.query(
                     'armorSlot',
@@ -691,7 +693,7 @@ export default class DhpActor extends Actor {
                 );
                 if (armorSlotResult) {
                     const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult;
-                    updates.find(u => u.key === 'hitPoints').value = modifiedDamage;
+                    hpDamage.value = modifiedDamage;
                     for (const armorChange of armorChanges) {
                         updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid });
                     }
@@ -701,20 +703,24 @@ export default class DhpActor extends Actor {
                         else updates.push({ value: stressSpent, key: 'stress' });
                     }
                 }
-            }
-            if (this.type === 'adversary') {
+            } else if (this.type === 'adversary') {
                 const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
                     return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
                 }, 0);
                 hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
-
-                if (
-                    hpDamage.value &&
-                    this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]
-                ) {
-                    hpDamage.value -= 1;
+                if (this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]) {
+                    hpDamage.value = Math.max(0, hpDamage.value - 1);
                 }
             }
+
+            // Merge existing hitPoint deduction with finalised damage deduction
+            const existing = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
+            if (existing) {
+                existing.value += hpDamage.value;
+                existing.damageTypes = hpDamage.damageTypes;
+            } else {
+                updates.push(hpDamage);
+            }
         }
 
         const results = await game.system.registeredTriggers.runTrigger(
@@ -729,12 +735,10 @@ export default class DhpActor extends Actor {
             for (var result of results) resourceMap.addResources(result.updates);
             resourceMap.updateResources();
         }
-
-        updates.forEach(
-            u =>
-                (u.value =
-                    u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value)
-        );
+        
+        for (const u of updates) {
+            u.value = u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value;
+        }
 
         await this.modifyResource(updates);
 
@@ -743,6 +747,36 @@ export default class DhpActor extends Actor {
         return updates;
     }
 
+    async takeHealing(args) {
+        args = this.#parseDamageArgs({ resources: 'resources' in args ? args.resources : args });
+        if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, args) === false) return null;
+
+        const updates = args.resourceUpdates;
+        for (const u of updates) {
+            const shouldFlip = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false);
+            u.value = shouldFlip ? u.value * -1 : u.value;
+        }
+        await this.modifyResource(updates);
+
+        if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null;
+
+        return updates;
+    }
+
+    /** Parse damage args that may be coming from takeHealing or takeDamage. Used to simplify macro usage and roll vs non-roll usage */
+    #parseDamageArgs(args = {}) {
+        const damageRoll = 'total' in args ? args : (args.main ?? args.damage);
+        const damageValue = typeof damageRoll === 'number' ? damageRoll : damageRoll?.total;
+        const damageTypes = Array.from(damageRoll?.options?.damageTypes ?? damageRoll?.damageTypes ?? []);
+        const main = typeof damageValue === 'number' ? { key: 'damage', value: damageValue, damageTypes } : null;
+        const resourceUpdates = Object.entries(args.resources ?? {}).map(([key, damage]) => ({ 
+            key, 
+            value: typeof damage === 'number' ? damage : damage?.total ?? 0 
+        }));
+
+        return { main, resourceUpdates };
+    }
+
     calculateDamage(baseDamage, type) {
         if (this.canResist(type, 'immunity')) return 0;
         if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
@@ -767,32 +801,6 @@ export default class DhpActor extends Actor {
         return reduction === Infinity ? 0 : reduction;
     }
 
-    async takeHealing(healings) {
-        if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, healings) === false) return null;
-
-        const updates = [];
-        Object.entries(healings).forEach(([key, healing]) => {
-            healing.parts.forEach(part => {
-                const update = updates.find(u => u.key === key);
-                if (update) update.value += part.total;
-                else updates.push({ value: part.total, key });
-            });
-        });
-
-        updates.forEach(
-            u =>
-                (u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false)
-                    ? u.value * -1
-                    : u.value)
-        );
-
-        await this.modifyResource(updates);
-
-        if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null;
-
-        return updates;
-    }
-
     /**
      * Resources are modified asynchronously, so be careful not to update the same resource in
      * quick succession.
diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs
index 7f30d970..dbcc50dc 100644
--- a/module/helpers/handlebarsHelper.mjs
+++ b/module/helpers/handlebarsHelper.mjs
@@ -48,9 +48,8 @@ export default class RegisterHandlebarsHelpers {
         return formula;
     }
 
-    static damageSymbols(damageParts) {
-        const allTypes = [...new Set([...damageParts].flatMap(x => Array.from(x.type)))];
-        const symbols = allTypes.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon);
+    static damageSymbols(damageData) {
+        const symbols = damageData.type.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon);
         return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
     }
 
diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs
index 84bcacf2..f600eae6 100644
--- a/module/helpers/utils.mjs
+++ b/module/helpers/utils.mjs
@@ -700,19 +700,6 @@ export async function RefreshFeatures(
     return refreshedActors;
 }
 
-export function getUnusedDamageTypes(parts) {
-    const usedKeys = Object.keys(parts);
-    return Object.keys(CONFIG.DH.GENERAL.healingTypes).reduce((acc, key) => {
-        if (!usedKeys.includes(key))
-            acc.push({
-                value: key,
-                label: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label)
-            });
-
-        return acc;
-    }, []);
-}
-
 /** Returns resolved armor sources ordered by application order */
 export function getArmorSources(actor) {
     const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData);
diff --git a/module/systemRegistration/migration-handlers/2_5_2.mjs b/module/systemRegistration/migration-handlers/2_5_2.mjs
index 944f0eec..096f4bb7 100644
--- a/module/systemRegistration/migration-handlers/2_5_2.mjs
+++ b/module/systemRegistration/migration-handlers/2_5_2.mjs
@@ -7,7 +7,8 @@ export class Migration_2_5_2 extends MigrationHandlerBase {
     async updateActiveEffectSource(effectSource, item) {
         let shouldUpdate = false;
         const newChanges = [];
-        const srdItem = item?._stats.compendiumSource ? 
+
+        const srdItem = item?._stats?.compendiumSource ? 
             await foundry.utils.fromUuid(item?._stats.compendiumSource) : 
             null;
         for (let i = 0; i < effectSource.system.changes.length; i++) {
diff --git a/module/systemRegistration/migration-handlers/2_6_0.mjs b/module/systemRegistration/migration-handlers/2_6_0.mjs
new file mode 100644
index 00000000..9edda9a2
--- /dev/null
+++ b/module/systemRegistration/migration-handlers/2_6_0.mjs
@@ -0,0 +1,20 @@
+import { MigrationHandlerBase } from './base.mjs';
+
+export class Migration_2_6_0 extends MigrationHandlerBase {
+    version = '2.6.0';
+
+    /** @inheritdoc */
+    async updateActorSource(actor) {
+        if (actor.type === 'party' && Object.keys(actor.system.tagTeam.members).length) {
+            return {
+                _id: actor._id,
+                system: {
+                    tagTeam: {
+                        initiator: null,
+                        members: _replace({})
+                    }
+                }
+            };
+        }
+    }
+}
\ No newline at end of file
diff --git a/module/systemRegistration/migration-handlers/base.mjs b/module/systemRegistration/migration-handlers/base.mjs
index 7426570d..29c181ce 100644
--- a/module/systemRegistration/migration-handlers/base.mjs
+++ b/module/systemRegistration/migration-handlers/base.mjs
@@ -1,8 +1,9 @@
 /** 
  * @import DHItem from "../../documents/item.mjs";
+* @import DhActor from "../../documents/actor.mjs";
  */
-
 /** 
+
  * The base class of an async migration. 
  * These are generally run between versions for things that require compendiums or must be done in post.
  * The migrate() functions calls the various updateXSource() functions.
@@ -22,6 +23,16 @@ export class MigrationHandlerBase {
         return null;
     }
 
+    /**
+     * Update a world actor
+     * @param {DhActor} actor 
+     * @returns {Promise<object>}
+     * @protected
+     */
+    async updateActorSource(actor) {
+        return null;
+    }
+
     async migrate() {
         // todo: handle more than just migrating effects. Right now this can only migrate effects
         // NOTE: the preload is hardcoded, we should not hardcode it
@@ -60,10 +71,39 @@ export class MigrationHandlerBase {
             }
         };
 
-        for (const actor of game.actors) {
+        const updateActor = async actor => {
+            const actorUpdate = await this.updateActorSource(actor);
+            if (actorUpdate) {
+                batch.push({
+                    action: 'update',
+                    documentName: 'Actor',
+                    updates: [actorUpdate]
+                });
+            }
+
+            const aeUpdates = [];
             for (const item of actor.items) {
                 await updateItem(item);
             }
+            
+            for (const effect of actor.effects) {
+                const changes = await this.updateActiveEffectSource(effect.toObject(), { parent: actor });
+                if (changes) aeUpdates.push(changes);
+            }
+            if (aeUpdates.length) {
+                batch.push({
+                    action: 'update',
+                    documentName: 'ActiveEffect',
+                    updates: aeUpdates,
+                    parent: actor
+                });
+            }
+        }
+
+
+        for (const actor of game.actors) {
+            await updateActor(actor);
+  
             progress.advance();
         }
         for (const item of game.items) {
diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs
index fef97b8f..af11cd2c 100644
--- a/module/systemRegistration/migrations.mjs
+++ b/module/systemRegistration/migrations.mjs
@@ -1,5 +1,6 @@
 import { defaultRestOptions } from '../config/generalConfig.mjs';
 import { Migration_2_5_2 } from './migration-handlers/2_5_2.mjs';
+import { Migration_2_6_0 } from './migration-handlers/2_6_0.mjs';
 
 export async function runMigrations() {
     let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
@@ -329,7 +330,8 @@ export async function runMigrations() {
     /* -------------------------------------------- */
 
     const migrations = [
-        new Migration_2_5_2()
+        new Migration_2_5_2(),
+        new Migration_2_6_0()
     ].filter(m => m.version && foundry.utils.isNewerVersion(m.version, lastMigrationVersion));
 
     for (const handler of migrations) {
diff --git a/styles/less/dialog/damage-selection/sheet.less b/styles/less/dialog/damage-selection/sheet.less
index 9f8cfc8a..0bb0f2be 100644
--- a/styles/less/dialog/damage-selection/sheet.less
+++ b/styles/less/dialog/damage-selection/sheet.less
@@ -17,6 +17,13 @@
             }
         }
 
+        .section-header {
+            font-size: var(--font-size-20);
+            color: light-dark(@dark, @beige);
+            text-align: center;
+            margin-bottom: -12px;
+        }
+
         .bonuses {
             gap: 4px;
             .critical-chip {
diff --git a/templates/actionTypes/cost.hbs b/templates/actionTypes/cost.hbs
index 7a9f33d9..b8661b5b 100644
--- a/templates/actionTypes/cost.hbs
+++ b/templates/actionTypes/cost.hbs
@@ -9,7 +9,7 @@
         {{/if}}
         <div class="nest-inputs">
             {{formField ../fields.scalable label="DAGGERHEART.GENERAL.scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox" localize=true}}
-            {{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="DAGGERHEART.GENERAL.resource" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}}
+            {{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="DAGGERHEART.GENERAL.Resource.single" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}}
             {{formField ../fields.value label="DAGGERHEART.GENERAL.amount" value=cost.value name=(concat "cost." index ".value") localize=true}}
             {{formField ../fields.step label="DAGGERHEART.GENERAL.step" value=cost.step name=(concat "cost." index ".step") disabled=(not cost.scalable) localize=true}}
             <a class="btn" data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeElement" data-index="{{index}}"><i class="fas fa-trash"></i></a>
diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs
index 03300840..d430b51f 100644
--- a/templates/actionTypes/damage.hbs
+++ b/templates/actionTypes/damage.hbs
@@ -1,92 +1,114 @@
-
-<fieldset class="one-column">
-    <legend class="with-icon">
-        {{#if (eq @root.source.type 'healing')}}
-            {{localize "DAGGERHEART.GENERAL.healing"}}
-        {{else}}
+{{#unless (eq @root.source.type 'healing')}}
+    <fieldset class="one-column">
+        <legend class="with-icon">
             {{localize "DAGGERHEART.GENERAL.damage"}}
+            {{#if source.main}}
+                <a data-action="removeDamage"><i class="fa-solid fa-trash icon-button"></i></a>
+            {{else}}
+                <a data-action="addDamage"><i class="fa-solid fa-plus icon-button"></i></a>
+            {{/if}}
+        </legend>
+
+        {{#if source.main}}
+            <div class="nest-inputs">
+                {{#if @root.hasBaseDamage}}
+                    {{formField @root.fields.damage.fields.main.fields.includeBase value=source.main.includeBase name="damage.main.includeBase" classes="checkbox" localize=true }}
+                {{/if}}
+                {{#unless (eq @root.source.type 'healing')}}
+                    {{formField baseFields.main.fields.direct value=source.main.direct name=(concat path "damage.main.direct") localize=true classes="checkbox"}}
+                {{/unless}}
+                {{#if (and @root.isNPC (not (eq path 'system.attack.')))}}
+                    {{formField baseFields.main.fields.groupAttack value=source.main.groupAttack name=(concat path "damage.main.groupAttack") localize=true classes="select"}}
+                {{/if}}
+            </div>
+            {{> damageData damage=source.main fields=fields.main.fields basePath=(concat path "damage.main")}}
+            {{#if horde}}
+                {{> hordeDamage source=source.main fields=fields.main.fields basePath=(concat path "damage.main")}}
+            {{/if}}
+            {{#if (ne @root.source.type 'healing')}}
+                {{formField fields.main.fields.type value=source.main.type name=(concat path "damage.main.type") localize=true}}
+            {{/if}}
         {{/if}}
-        {{#unless (eq path 'system.attack.')}}<a data-action="addDamage" {{#if @root.allDamageTypesUsed}}disabled{{/if}}><i class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
-    </legend>
-    <div class="nest-inputs">
-        {{#if @root.hasBaseDamage}}
-            {{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.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"}}
-        {{/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"}}
-        {{/if}}
-    </div>
+    </fieldset>
+{{/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 --}}
-    {{#each source.parts as |dmg key|}}
-        <div class="nest-inputs">
-            <fieldset{{#if dmg.base}} disabled{{/if}} class="one-column{{#if ../path}} no-style{{/if}}">
-                <legend class="with-icon">
-                    {{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}}
-                    {{#unless (or dmg.base ../path)}}
-                        <a data-action="removeDamage" data-key="{{dmg.applyTo}}"><i class="fas fa-trash"></i></a>
-                    {{/unless}}
-                </legend>
-
-                {{#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"}}
-                {{/if}}
-                {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
-                    <div class="nest-inputs">
-                        <fieldset class="one-column">
-                            <legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}</legend>
-                            {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}}
-                        </fieldset>
-                        <fieldset class="one-column">
-                            <legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}</legend>
-                            {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path}}
-                        </fieldset>
-                    </div>
-                {{else}}
-                    {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}}
-                {{/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}}
-                {{/if}}
-
-                {{#if ../horde}}
-                    <fieldset class="one-column">
-                        <legend>{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}</legend>
-                        <div class="nest-inputs">
-                            <input type="hidden" name="{{../path}}damage.parts.{{dmg.applyTo}}.valueAlt.multiplier" value="flat">
-                            {{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"}}
-                        </div>
-                    </fieldset>
-                {{/if}}
-                <input type="hidden" name="{{concat ../path "damage.parts." dmg.applyTo ".base"}}" value="{{dmg.base}}">
-            </fieldset>
-        </div>
-    {{/each}}
-</fieldset>
+{{#unless (eq path 'system.attack.')}}
+    {{! In the future, consider allowing this even on NPCs}}
+    <fieldset class="one-column">
+        <legend class="with-icon">
+            {{#if (eq @root.source.type 'healing')}}
+                {{localize "DAGGERHEART.GENERAL.healing"}}
+            {{else}}
+                {{localize "DAGGERHEART.ACTIONS.Config.damage.markResources"}}
+            {{/if}}
+            {{#unless @root.allDamageTypesUsed}}<a data-action="addDamageResource"><i class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
+        </legend>
+        {{#each source.resources as |dmg key|}}
+            <div class="nest-inputs">
+                <fieldset{{#if dmg.base}} disabled{{/if}} class="one-column">
+                    <legend class="with-icon">
+                        {{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}}
+                        {{#unless (or dmg.base ../path)}}
+                            <a data-action="removeDamageResource" data-key="{{key}}"><i class="fas fa-trash"></i></a>
+                        {{/unless}}
+                    </legend>
+                    {{> damageData damage=dmg fields=../fields.resources.element.fields basePath=(concat ../path "damage.resources." dmg.applyTo)}}
+                </fieldset>
+            </div>
+        {{/each}}
+    </fieldset>
+{{/unless}}
 
 {{#*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}}
-        {{/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}}
-        {{else}}
-            <div class="nest-inputs">
-                {{#unless @root.isNPC}}
-                    {{formField fields.multiplier value=source.multiplier name=(concat path "damage.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}}
-            </div>
-        {{/if}}
-        {{#if @root.isNPC}}
-            <input type="hidden" name="{{path}}damage.parts.{{key}}.{{target}}.multiplier" value="flat">
-        {{/if}}
+    {{#unless isBase}}
+        {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat basePath ".custom.enabled") classes="checkbox" localize=true}}
+    {{/unless}}
+    {{#if source.custom.enabled}}
+        {{formField fields.custom.fields.formula value=source.custom.formula name=(concat basePath ".custom.formula") localize=true}}
+    {{else}}
+        <div class="nest-inputs">
+            {{#unless @root.isNPC}}
+                {{formField fields.multiplier value=source.multiplier name=(concat basePath ".multiplier") localize=true}}
+            {{/unless}}
+            {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat basePath ".flatMultiplier") localize=true }}{{/if}}
+            {{formField fields.dice value=source.dice name=(concat basePath ".dice") localize=true}}
+            {{formField fields.bonus value=source.bonus name=(concat basePath ".bonus") localize=true}}
+        </div>
+    {{/if}}
+    {{#if @root.isNPC}}
+        <input type="hidden" name="{{basePath}}.{{target}}.multiplier" value="flat">
+    {{/if}}
+{{/inline}}
+
+{{#*inline "damageData"}}
+    {{#if (and (not @root.isNPC) @root.hasRoll (not damage.base))}}
+        {{formField fields.resultBased value=damage.resultBased name=(concat basePath ".resultBased") localize=true classes="checkbox"}}
+    {{/if}}
+    {{#if (and (not @root.isNPC) @root.hasRoll (not damage.base) damage.resultBased)}}
+        <div class="nest-inputs">
+            <fieldset class="one-column">
+                <legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}</legend>
+                {{> formula key=damage.applyTo fields=fields.value.fields type=fields.type isBase=damage.base source=damage.value basePath=(concat basePath ".value")}}
+            </fieldset>
+            <fieldset class="one-column">
+                <legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}</legend>
+                {{> formula key=damage.applyTo fields=fields.valueAlt.fields type=fields.type isBase=damage.base source=damage.valueAlt basePath=(concat basePath ".valueAlt")}}
+            </fieldset>
+        </div>
+    {{else}}
+        {{> formula key=damage.applyTo fields=fields.value.fields type=fields.type isBase=damage.base source=damage.value basePath=(concat basePath ".value")}}
+    {{/if}}
+    <input type="hidden" name="{{concat basePath ".base"}}" value="{{damage.base}}">
+{{/inline}}
+
+{{#*inline "hordeDamage"}}
+    <fieldset class="one-column">
+        <legend>{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}</legend>
+        <div class="nest-inputs">
+            <input type="hidden" name="{{basePath}}.valueAlt.multiplier" value="flat">
+            {{formField fields.valueAlt.fields.flatMultiplier value=source.valueAlt.flatMultiplier name=(concat basePath ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }}
+            {{formField fields.valueAlt.fields.dice value=source.valueAlt.dice name=(concat basePath ".valueAlt.dice") classes="inline-child" localize=true}}
+            {{formField fields.valueAlt.fields.bonus value=source.valueAlt.bonus name=(concat basePath ".valueAlt.bonus") localize=true classes="inline-child"}}
+        </div>
+    </fieldset>
 {{/inline}}
\ No newline at end of file
diff --git a/templates/actionTypes/resource.hbs b/templates/actionTypes/resource.hbs
index 9c8fc965..97304b9a 100644
--- a/templates/actionTypes/resource.hbs
+++ b/templates/actionTypes/resource.hbs
@@ -1,7 +1,7 @@
 
 <fieldset class="action-category">
     <legend class="action-category-label" data-action="toggleSection" data-section="effects">
-        <div>{{localize "DAGGERHEART.GENERAL.resource"}}</div>
+        <div>{{localize "DAGGERHEART.GENERAL.Resource.single"}}</div>
     </legend>
     <div class="action-category-data open">
         <fieldset>
diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs
index 7bcd7063..e865ebe6 100644
--- a/templates/dialogs/dice-roll/damageSelection.hbs
+++ b/templates/dialogs/dice-roll/damageSelection.hbs
@@ -15,34 +15,7 @@
             {{/each}}
         </fieldset>
     {{/if}}
-
-    {{#each @root.formula}}
-        <div class="damage-formula">
-            <span class="damage-resource"><b>{{localize "DAGGERHEART.GENERAL.formula"}}:</b> {{roll.formula}}</span>
-            <span class="damage-details">
-                {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}}
-                    {{localize label}}
-                {{/with}}
-                {{#unless @root.hasHealing}}
-                    {{#if damageTypes}}
-                        {{#each damageTypes as | type | }}
-                            {{#with (lookup @root.config.GENERAL.damageTypes type)}}
-                                <i class="fa-solid {{icon}}"></i>
-                            {{/with}}
-                        {{/each}}
-                    {{/if}}
-                {{/unless}}
-            </span>
-        </div>
-        <div class="bonuses form-group flexrow">
-            <input type="text" value="{{extraFormula}}" name="roll.{{ @index }}.extraFormula" placeholder="{{localize "DAGGERHEART.GENERAL.situationalBonus"}}">
-            <button class="critical-chip" data-action="toggleCritical">
-                <span><i class="{{#if @root.isCritical}}fa-solid{{else}}fa-regular{{/if}} fa-circle"></i></span>
-                <span class="label">{{localize "DAGGERHEART.GENERAL.criticalShort"}}</span>
-            </button>
-        </div>
-    {{/each}}
-
+    
     {{#if damageOptions.groupAttack}}
         <fieldset class="group-attack-container">
             <legend>{{localize "DAGGERHEART.ACTIONS.Settings.groupAttack.label"}}</legend>
@@ -59,6 +32,45 @@
             </div>
         </fieldset>
     {{/if}}
+    
+    {{#if @root.damageFormula}}
+        {{#with @root.damageFormula}}
+            <div class="damage-formula">
+                <span class="damage-resource"><b>{{localize "DAGGERHEART.GENERAL.formula"}}:</b> {{roll.formula}}</span>
+                <span class="damage-details">
+                    {{localize "DAGGERHEART.GENERAL.damage"}}
+                    {{#if damageTypes}}
+                        {{#each damageTypes as | type | }}
+                            {{#with (lookup @root.config.GENERAL.damageTypes type)}}
+                                <i class="fa-solid {{icon}}"></i>
+                            {{/with}}
+                        {{/each}}
+                    {{/if}}
+                </span>
+            </div>
+            <div class="bonuses form-group flexrow">
+                <input type="text" value="{{extraFormula}}" name="damageFormula.extraFormula" placeholder="{{localize "DAGGERHEART.GENERAL.situationalBonus"}}">
+                <button class="critical-chip" data-action="toggleCritical">
+                    <span><i class="{{#if @root.isCritical}}fa-solid{{else}}fa-regular{{/if}} fa-circle"></i></span>
+                    <span class="label">{{localize "DAGGERHEART.GENERAL.criticalShort"}}</span>
+                </button>
+            </div>
+        {{/with}}
+    {{/if}}
+
+    {{#each @root.resourceFormulas}}
+        <div class="damage-formula">
+            <span class="damage-resource"><b>{{localize "DAGGERHEART.GENERAL.formula"}}:</b> {{roll.formula}}</span>
+            <span class="damage-details">
+                {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}}
+                    {{localize label}}
+                {{/with}}
+            </span>
+        </div>
+        <div class="bonuses form-group flexrow">
+            <input type="text" value="{{extraFormula}}" name={{concat "resourceFormulas." @key ".extraFormula"}} placeholder="{{localize "DAGGERHEART.GENERAL.situationalBonus"}}">
+        </div>
+    {{/each}}
 
     {{#unless (empty @root.modifiers)}}
         <fieldset class="modifier-container two-columns">
@@ -76,6 +88,7 @@
             {{/each}}
         </fieldset>
     {{/unless}}
+
     <div class="damage-section-controls">
         {{#if directDamage}}
             <select class="roll-mode-select" name="selectedMessageMode">
diff --git a/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs b/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs
index 2a366269..e8bec150 100644
--- a/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs
+++ b/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs
@@ -1,25 +1,36 @@
-{{#each damage as |damage key|}}
+{{#if damage.main}}
+    {{> damage roll=damage.main label=(localize "DAGGERHEART.GENERAL.damage") memberKey=key}}
+{{/if}}
+
+{{#each damage.resources as |roll key|}}
+    {{> damage roll=roll label=(localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")) memberKey=../key isResource="true" }}
+{{/each}}
+
+{{#*inline "damage"}}
     <div class="roll-data {{#if isCritical}}critical{{/if}}">
         <div class="duality-label">
-            <span>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}:</span>
-            <span>{{damage.total}}</span>
+            <span>{{label}}:</span>
+            <span>{{roll.total}}</span>
         </div>
-        {{#each damage.parts as |part|}}
-            <div class="roll-dice-container">
-                {{#each part.dice as |dice index|}}
-                    <a class="roll-dice" data-action="rerollDamageDice" data-member-key="{{../../../key}}" data-damage-key="{{@../../key}}" data-part="{{@../index}}" data-dice="{{index}}">
-                        <span class="dice-label">{{dice.total}}</span>
-                        <img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" dice.dice ".svg"}}" />
-                    </a>
-                    {{#unless @last}}
-                        <span class="roll-operator">+</span>
-                    {{/unless}}
+
+        <div class="roll-dice-container">
+            {{#each roll.dice}}
+                {{#each results as |result index|}}
+                    {{#if result.active}}
+                        <a class="roll-dice" data-action="rerollDamageDice" data-member-key="{{../../memberKey}}" data-damage-key="{{@../../key}}" {{#if ../../isResource}}data-is-resource="true"{{/if}} data-dice-index="{{@../key}}" data-result-index="{{index}}">
+                            <span class="dice-label">{{result.result}}</span>
+                            <img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" ../denomination ".svg"}}" />
+                        </a>
+                    {{/if}}
                 {{/each}}
-                {{#if part.modifierTotal}}
-                    {{#if part.dice.length}}<span class="roll-operator">{{#if (gte part.modifierTotal 0)}}+{{else}}-{{/if}}</span>{{/if}}
-                    <span class="roll-value">{{positive part.modifierTotal}}</span>
-                {{/if}}
-            </div>
-        {{/each}}
+                {{#unless @last}}
+                    <span class="roll-operator">+</span>
+                {{/unless}}
+            {{/each}}
+            {{#if roll.modifierTotal}}
+                {{#if roll.dice.length}}<span class="roll-operator">{{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}}</span>{{/if}}
+                <span class="roll-value">{{positive roll.modifierTotal}}</span>
+            {{/if}}
+        </div>
     </div>
-{{/each}}
\ No newline at end of file
+{{/inline}}
\ No newline at end of file
diff --git a/templates/dialogs/tagTeamDialog/result.hbs b/templates/dialogs/tagTeamDialog/result.hbs
index 151c8c87..88f579a6 100644
--- a/templates/dialogs/tagTeamDialog/result.hbs
+++ b/templates/dialogs/tagTeamDialog/result.hbs
@@ -6,19 +6,19 @@
                 {{#if hintText}}
                     <div class="hint">{{localize hintText}}</div>
                 {{else}}
-                    {{#if joinedRoll.roll}}
+                    {{#if joinedRoll.rollData}}
                         <div class="result-container">
                             <span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
                             <div class="result-info">
-                                <div class="damage-info">{{joinedRoll.roll.total}}</div>
+                                <div class="damage-info">{{joinedRoll.rollData.total}}</div>
                                 <div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
                             </div>
                         </div>
                     {{/if}}
-                    {{#if joinedRoll.rollData.options.hasDamage}}
+                    {{#if joinedRoll.damageRollData}}
                         <div class="result-container">
                             <span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span>
-                            {{#each joinedRoll.rollData.options.damage as |damage key|}}
+                            {{#each joinedRoll.damageRollData.types as |damage key|}}
                                 <div class="result-info">
                                     <div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div>
                                     <div class="damage-info">{{damage.total}}</div>
diff --git a/templates/dialogs/tagTeamDialog/tagTeamMember.hbs b/templates/dialogs/tagTeamDialog/tagTeamMember.hbs
index 7510422f..7758979b 100644
--- a/templates/dialogs/tagTeamDialog/tagTeamMember.hbs
+++ b/templates/dialogs/tagTeamDialog/tagTeamMember.hbs
@@ -64,7 +64,10 @@
 
                         {{#if roll}}
                             <div class="roll-data {{#if roll.withHope}}hope{{else if roll.withFear}}fear{{else}}critical{{/if}}">
-                                <div class="duality-label">{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}</div>
+                                <div class="duality-label">
+                                    {{roll.total}} 
+                                    {{#if roll.isCritical}}{{roll.totalLabel}}{{else}}{{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}{{/if}}
+                                </div>
                                 <div class="roll-dice-container">
                                     <a class="roll-dice" data-action="rerollDice" data-member="{{@root.partId}}" data-dice-type="hope">
                                         <span class="dice-label">{{roll.dHope.total}}</span>
@@ -100,15 +103,12 @@
                                 <a class="roll-button" data-action="makeDamageRoll" data-member-key="{{@root.partId}}" {{#unless readyToRoll}}disabled{{/unless}}>
                                     <img src="systems/daggerheart/assets/icons/dice/hope/d20.svg" />
                                 </a>
-
-                                {{#if damage}}
-                                    <a class="delete-button" data-action="removeDamageRoll" data-member-key="{{@root.partId}}" {{#unless rollData.options.damage}}disabled{{/unless}}>
-                                        <i class="fa-solid fa-trash"></i>
-                                    </a>
-                                {{/if}}
+                                <a class="delete-button" data-action="removeDamageRoll" data-member-key="{{@root.partId}}" {{#unless damage.active}}disabled{{/unless}}>
+                                    <i class="fa-solid fa-trash"></i>
+                                </a>
                             </div>
                         </span>
-                        {{#if damage}}
+                        {{#if damage.active}}
                             {{#if useCritDamage}}
                                 {{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=critDamage isCritical=true }}
                             {{else}}
diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs
index 567cb81c..67017a5a 100644
--- a/templates/sheets-settings/action-settings/effect.hbs
+++ b/templates/sheets-settings/action-settings/effect.hbs
@@ -5,7 +5,7 @@
 >
     {{#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 source=source.damage 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/adversary-settings/attack.hbs b/templates/sheets-settings/adversary-settings/attack.hbs
index 41960032..f81f0477 100644
--- a/templates/sheets-settings/adversary-settings/attack.hbs
+++ b/templates/sheets-settings/adversary-settings/attack.hbs
@@ -22,5 +22,5 @@
         </div>
         {{formGroup systemFields.criticalThreshold value=document._source.system.criticalThreshold label="DAGGERHEART.ACTIONS.Settings.criticalThreshold" name="system.criticalThreshold" localize=true}}
     </fieldset>
-    {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." baseFields=systemFields.attack.fields.damage.fields horde=(eq document._source.system.type 'horde')}}
+    {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields source=document.system.attack.damage path="system.attack." baseFields=systemFields.attack.fields.damage.fields horde=(eq document._source.system.type 'horde')}}
 </section>
\ No newline at end of file
diff --git a/templates/sheets/items/weapon/settings.hbs b/templates/sheets/items/weapon/settings.hbs
index c9805e7c..fbdc1e44 100644
--- a/templates/sheets/items/weapon/settings.hbs
+++ b/templates/sheets/items/weapon/settings.hbs
@@ -18,26 +18,26 @@
     </fieldset>
 
     <fieldset class="two-columns">
-        {{#with systemFields.attack.fields.damage.fields.parts.element.fields as | fields | }}
-        {{#with ../document.system.attack.damage.parts.hitPoints as | source | }}
-            <legend>{{localize "DAGGERHEART.GENERAL.damage"}}</legend>
-            <span>{{localize "DAGGERHEART.ACTIONS.Config.general.customFormula"}}</span>
-            {{formInput fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="system.attack.damage.parts.hitPoints.value.custom.enabled"}}
-            {{#if source.value.custom.enabled}}
-                <span>{{localize "DAGGERHEART.ACTIONS.Config.general.formula"}}</span>
-                {{formInput fields.value.fields.custom.fields.formula value=source.value.custom.formula name="system.attack.damage.parts.hitPoints.value.custom.formula"}}
-            {{else}}
-                <span>{{localize "DAGGERHEART.GENERAL.Dice.single"}}</span>
-                {{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.parts.hitPoints.value.dice"}}
-                <span>{{localize "DAGGERHEART.GENERAL.bonus"}}</span>
-                {{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.parts.hitPoints.value.bonus" localize=true}}
-            {{/if}}
-            <span>{{localize "DAGGERHEART.GENERAL.type"}}</span>
-            {{formInput fields.type value=source.type name="system.attack.damage.parts.hitPoints.type" localize=true}}
-            <span>{{localize "DAGGERHEART.CONFIG.DamageType.direct.name"}}</span>
-            {{formInput @root.systemFields.attack.fields.damage.fields.direct value=@root.document.system.attack.damage.direct name="system.attack.damage.direct" localize=true}}
-            <input type="hidden" name="system.attack.damage.parts.hitPoints.value.multiplier" value="{{source.value.multiplier}}">
-        {{/with}}
+        {{#with systemFields.attack.fields.damage.fields.main.fields as | fields | }}
+            {{#with ../document.system.attack.damage.main as | source | }}
+                <legend>{{localize "DAGGERHEART.GENERAL.damage"}}</legend>
+                <span>{{localize "DAGGERHEART.ACTIONS.Config.general.customFormula"}}</span>
+                {{formInput fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="system.attack.damage.main.value.custom.enabled"}}
+                {{#if source.value.custom.enabled}}
+                    <span>{{localize "DAGGERHEART.ACTIONS.Config.general.formula"}}</span>
+                    {{formInput fields.value.fields.custom.fields.formula value=source.value.custom.formula name="system.attack.damage.main.value.custom.formula"}}
+                {{else}}
+                    <span>{{localize "DAGGERHEART.GENERAL.Dice.single"}}</span>
+                    {{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.main.value.dice"}}
+                    <span>{{localize "DAGGERHEART.GENERAL.bonus"}}</span>
+                    {{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.main.value.bonus" localize=true}}
+                {{/if}}
+                <span>{{localize "DAGGERHEART.GENERAL.type"}}</span>
+                {{formInput fields.type value=source.type name="system.attack.damage.main.type" localize=true}}
+                <span>{{localize "DAGGERHEART.CONFIG.DamageType.direct.name"}}</span>
+                {{formInput @root.systemFields.attack.fields.damage.fields.main.fields.direct value=@root.document.system.attack.damage.main.direct name="system.attack.damage.main.direct" localize=true}}
+                <input type="hidden" name="system.attack.damage.main.value.multiplier" value="{{source.value.multiplier}}">
+            {{/with}}
         {{/with}}
     </fieldset>
     <fieldset class="two-columns">
diff --git a/templates/ui/chat/parts/button-part.hbs b/templates/ui/chat/parts/button-part.hbs
index 6bc5f372..a0075b5f 100644
--- a/templates/ui/chat/parts/button-part.hbs
+++ b/templates/ui/chat/parts/button-part.hbs
@@ -1,18 +1,18 @@
 <div class="roll-buttons">
     {{#if areas.length}}<button class="action-areas end-button"><i class="fa-solid fa-crosshairs"></i></button>{{/if}}
     {{#if hasDamage}}
-        {{#unless (empty damage)}}
+        {{#if damage.active}}
             <button class="duality-action damage-button">{{localize "DAGGERHEART.UI.Chat.damageRoll.dealDamage"}}</button>
         {{else}}
             <button class="duality-action duality-action-damage">{{localize "DAGGERHEART.UI.Chat.attackRoll.rollDamage"}}</button>
-        {{/unless}}
+        {{/if}}
     {{/if}}
     {{#if hasHealing}}
-        {{#unless (empty damage)}}
+        {{#if damage.active}}
             <button class="duality-action damage-button">{{localize "DAGGERHEART.UI.Chat.healingRoll.applyHealing"}}</button>
         {{else}}
             <button class="duality-action duality-action-damage">{{localize "DAGGERHEART.UI.Chat.attackRoll.rollHealing"}}</button>
-        {{/unless}}
+        {{/if}}
     {{/if}}
     {{#if (and hasEffect)}}<button class="duality-action-effect">{{localize "DAGGERHEART.UI.Chat.attackRoll.applyEffect"}}</button>{{/if}}
 </div>
\ No newline at end of file
diff --git a/templates/ui/chat/parts/damage-part.hbs b/templates/ui/chat/parts/damage-part.hbs
index 45b09b72..7d6d983d 100644
--- a/templates/ui/chat/parts/damage-part.hbs
+++ b/templates/ui/chat/parts/damage-part.hbs
@@ -1,4 +1,4 @@
-<div class="roll-part damage-section dice-roll" data-action="expandRoll"{{#if (empty damage)}} hidden{{/if}}>
+<div class="roll-part damage-section dice-roll" data-action="expandRoll"{{#unless damage.active}} hidden{{/unless}}>
     <div class="roll-part-header">
         <div>
             {{#if hasHealing}}
@@ -10,60 +10,79 @@
     </div>
     <div class="roll-part-extra on-reduced">
         <div class="wrapper">
-            {{#each damage as | roll index | }}
-                <div class="roll-formula">{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}: {{total}}</div>
+            {{#if damage.main}}
+                {{> formula roll=damage.main label=(localize "DAGGERHEART.GENERAL.damage") }}
+            {{/if}}
+            {{#each damage.resources as | roll index | }}
+                {{> formula roll=roll label=(ifThen ../hasHealing (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')) (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll'))) }}
             {{/each}}
         </div>
     </div>
     <div class="roll-part-content dice-result">
         <div class="dice-tooltip">
             <div class="wrapper">
-                {{#each damage as | roll index | }}
-                    <fieldset>
-                        <legend>
-                            {{#if ../hasHealing}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}{{else}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}{{/if}} <div class="roll-formula">{{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}</div>{{#if (and (eq index "hitPoints") ../isDirect)}} <div class="roll-formula">{{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}</div>{{/if}}
-                        </legend>
-                        {{#each roll.parts}}
-                            {{#if (and (not @root.hasHealing) damageTypes.length)}}
-                                <label class="roll-part-header"><span>
-                                    {{#each damageTypes}}
-                                        {{localize (concat 'DAGGERHEART.CONFIG.ArmorFeature.' this '.name')}}
-                                        {{#unless @last}}/{{/unless}}
-                                    {{/each}}
-                                <div class="roll-formula">{{total}}</div></span></label>
-                            {{/if}}
-                            <div class="roll-dice">
-                                {{#if dice.length}}
-                                    {{#each dice}}
-                                        {{#each results}}
-                                            {{#unless discarded}}
-                                                <div class="roll-die{{#unless @../first}} has-plus{{/unless}}">
-                                                    <div 
-                                                        class="dice reroll-button {{../dice}}" 
-                                                        data-die-index="0" data-type="damage" data-damage-type="{{@../../../key}}" data-part="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}"
-                                                    >
-                                                        {{#if hasRerolls}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerolled"}}"></i>{{/if}}
-                                                        {{result}}
-                                                    </div>
-                                                </div>
-                                            {{/unless}}
-                                        {{/each}}
-                                    {{/each}}
-                                    {{#if modifierTotal}}
-                                        <div class="roll-die{{#if (gt modifierTotal 0)}} has-plus{{/if}}">
-                                            <div class="font-20">{{modifierTotal}}</div>
-                                        </div>
-                                    {{/if}}
-                                {{else}}
-                                    <div class="roll-die">
-                                        <div class="font-20">{{total}}</div>
-                                    </div>
-                                {{/if}}
-                            </div>
-                        {{/each}}
-                    </fieldset>
+                {{#if damage.main}}
+                    {{> damage label=(localize "DAGGERHEART.GENERAL.damage") roll=damage.main isDirect=isDirect }}
+                {{/if}}
+
+                {{#each damage.resources as | roll index | }}
+                    {{> damage
+                        label=(ifThen ../hasHealing (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')) (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll'))) 
+                        roll=roll
+                        isResource=true 
+                    }}
                 {{/each}}
             </div>
         </div>
     </div>
-</div>
\ No newline at end of file
+</div>
+
+{{#*inline "formula"}}
+    <div class="roll-formula">{{label}}: {{roll.total}}</div>
+{{/inline}}
+
+{{#*inline "damage"}}
+    <fieldset>
+        <legend>
+            {{label}}
+            <div class="roll-formula">{{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}</div>{{#if isDirect}} <div class="roll-formula">{{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}</div>{{/if}}
+        </legend>
+        {{#if (and (not @root.hasHealing) roll.options.damageTypes.length)}}
+            <label class="roll-part-header"><span>
+                {{#each roll.options.damageTypes}}
+                    {{localize (concat 'DAGGERHEART.CONFIG.ArmorFeature.' this '.name')}}
+                    {{#unless @last}}/{{/unless}}
+                {{/each}}
+            <div class="roll-formula">{{roll.total}}</div></span></label>
+        {{/if}}
+        <div class="roll-dice">
+            {{#if roll.dice.length}}
+                {{#each roll.dice}}
+                    {{#each results}}
+                        {{#if active}}
+                            <div class="roll-die{{#unless @../first}} has-plus{{/unless}}">
+                                <div 
+                                    class="dice reroll-button {{../denomination}}" 
+                                    data-type="damage" data-damage-type="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}" {{#if ../../isResource}}data-is-resource="true"{{/if}}
+                                >
+                                    {{#if hasRerolls}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerolled"}}"></i>{{/if}}
+                                    {{result}}
+                                </div>
+                            </div>
+                        {{/if}}
+                    {{/each}}
+                {{/each}}
+                
+                {{#if roll.modifierTotal}}
+                    <div class="roll-die{{#if (gt roll.modifierTotal 0)}} has-plus{{/if}}">
+                        <div class="font-20">{{roll.modifierTotal}}</div>
+                    </div>
+                {{/if}}
+            {{else}}
+                <div class="roll-die">
+                    <div class="font-20">{{roll.total}}</div>
+                </div>
+            {{/if}}
+        </div>
+    </fieldset>
+{{/inline}}
\ No newline at end of file
diff --git a/templates/ui/tooltip/attack.hbs b/templates/ui/tooltip/attack.hbs
index 8e4a1bb0..903c13a5 100644
--- a/templates/ui/tooltip/attack.hbs
+++ b/templates/ui/tooltip/attack.hbs
@@ -23,7 +23,7 @@
             </div>
         {{/if}}
         <div class="tag">
-            <span>{{{damageFormula attack}}} {{{damageSymbols attack.damage.parts}}}</span>
+            <span>{{{damageFormula attack}}} {{{damageSymbols attack.damage.main}}}</span>
         </div>
     </div>
     {{#if description}}
diff --git a/templates/ui/tooltip/weapon.hbs b/templates/ui/tooltip/weapon.hbs
index 4adb9c46..10889015 100644
--- a/templates/ui/tooltip/weapon.hbs
+++ b/templates/ui/tooltip/weapon.hbs
@@ -23,7 +23,7 @@
             {{/with}}
         </div>
         <div class="tag">
-          <span>{{{damageFormula item.system.attack}}} {{{damageSymbols item.system.attack.damage.parts}}}</span>
+          <span>{{{damageFormula item.system.attack}}} {{{damageSymbols item.system.attack.damage.main}}}</span>
         </div>
     </div>
     {{#if description}}
