From a67a5e138467e9efb9ee808100c5b1bcf4152194 Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Mon, 16 Mar 2026 12:08:10 -0700 Subject: [PATCH 1/5] [#6261] Add modifiers to `DamageData` Adds a new modifiers field for damage that is applied automatically to the formula. The `DamageData` model now automatically adds these modifiers to the formula, attaching to the first roll (if one can be found when using a custom formula). The `DamageData#scaledFormula` supports passing in additional modifiers that are also appended. This is to support a future Rule active effect that adds modifiers to certain rolls at evaluation time (such as Elemental Adept, which would add `min2` to all damage rolls from spells that deal a certain damage type). Closes #6261 --- lang/en.json | 5 +++ less/v2/forms.less | 15 +++++---- module/data/shared/_types.mjs | 1 + module/data/shared/damage-field.mjs | 42 ++++++++++++++++++------ templates/activity/parts/damage-part.hbs | 8 +++-- templates/shared/fields/field-damage.hbs | 4 +++ 6 files changed, 57 insertions(+), 18 deletions(-) diff --git a/lang/en.json b/lang/en.json index 686a964050..a4a6853804 100644 --- a/lang/en.json +++ b/lang/en.json @@ -2161,6 +2161,10 @@ "label": "Die Denomination", "hint": "Denomination of the dice to roll." }, + "modifiers": { + "label": "Modifiers", + "hint": "Roll modifiers added to the base die." + }, "number": { "label": "Die Number", "hint": "Number of dice to roll." @@ -3914,6 +3918,7 @@ }, "DND5E.RollExample": "e.g. 1d4", "DND5E.RollMode": "Roll Mode", +"DND5E.RollModifiers": "Modifiers", "DND5E.RollSituationalBonus": "Situational Bonus?", "DND5E.Rule": { "Tooltip": "Tooltip", diff --git a/less/v2/forms.less b/less/v2/forms.less index cd57de4621..08a5f7c6cc 100644 --- a/less/v2/forms.less +++ b/less/v2/forms.less @@ -336,10 +336,13 @@ &.multi-select { position: relative; - :is(multi-select, string-tags) .tags.input-element-tags { - min-height: 16px; - padding-left: 40%; - &:empty { display: flex; } + :is(multi-select, string-tags) { + &:has(.input-element-tags:empty) { margin-block-start: 8px; } + .tags.input-element-tags { + min-height: 16px; + padding-left: 40%; + &:empty { display: flex; } + } } > label { @@ -559,7 +562,7 @@ &.hidden-tags .tags.input-element-tags { display: none; } } - :is(file-picker, document-tags) > button { + :is(file-picker, document-tags, string-tags) > button { height: var(--form-field-height); border: var(--dnd5e-border-gold); } @@ -745,7 +748,7 @@ &:not(.copy-button, .unbutton, .reveal) { line-height: 28px; } } - :is(document-tags, multi-select) .tags.input-element-tags .tag { + :is(document-tags, multi-select, string-tags) .tags.input-element-tags .tag { padding: 1px 4px; color: var(--color-text-primary); height: unset; diff --git a/module/data/shared/_types.mjs b/module/data/shared/_types.mjs index 0c8b625820..b1f172cb6b 100644 --- a/module/data/shared/_types.mjs +++ b/module/data/shared/_types.mjs @@ -32,6 +32,7 @@ * @property {object} custom * @property {boolean} custom.enabled Should the custom formula be used? * @property {string} custom.formula Custom damage formula. + * @property {Set} modifiers Modifiers to apply to damage roll. * @property {object} scaling * @property {string} scaling.mode How the damage scales in relation with levels. * @property {number} scaling.number Number of dice to add per scaling level. diff --git a/module/data/shared/damage-field.mjs b/module/data/shared/damage-field.mjs index 0b80fd9652..429cd8d3ac 100644 --- a/module/data/shared/damage-field.mjs +++ b/module/data/shared/damage-field.mjs @@ -34,6 +34,7 @@ export class DamageData extends foundry.abstract.DataModel { enabled: new BooleanField(), formula: new FormulaField() }), + modifiers: new SetField(new StringField()), scaling: new SchemaField({ mode: new StringField(), number: new NumberField({ initial: 1, min: 0, integer: true }), @@ -51,7 +52,7 @@ export class DamageData extends foundry.abstract.DataModel { * @type {string} */ get formula() { - if ( this.custom.enabled ) return this.custom.formula ?? ""; + if ( this.custom.enabled ) return this._manualFormula(); return this._automaticFormula(); } @@ -60,27 +61,48 @@ export class DamageData extends foundry.abstract.DataModel { /* -------------------------------------------- */ /** - * Produce the auto-generated formula from the `number`, `denomination`, and `bonus`. - * @param {number} [increase=0] Amount to increase the die count. + * Produce the auto-generated formula from the `number`, `denomination`, `modifiers`, and `bonus`. + * @param {number} [increase=0] Amount to increase the die count. + * @param {object} [options={}] + * @param {Set} [options.modifiers] Additional modifiers to apply to the formula, if possible. * @returns {string} * @protected */ - _automaticFormula(increase=0) { + _automaticFormula(increase=0, { modifiers }={}) { let formula; const number = (this.number ?? 0) + increase; - if ( number && this.denomination ) formula = `${number}d${this.denomination}`; + if ( number && this.denomination ) { + formula = `${number}d${this.denomination}${Array.from(this.modifiers).concat(modifiers ?? []).join("")}`; + } if ( this.bonus ) formula = formula ? `${formula} + ${this.bonus}` : this.bonus; return formula ?? ""; } /* -------------------------------------------- */ + /** + * Produce the manual formula from the `custom.formula` and `modifiers` (if possible). + * @param {object} [options={}] + * @param {Set} [options.modifiers] Additional modifiers to apply to the formula, if possible. + * @returns {string} + * @protected + */ + _manualFormula({ modifiers }={}) { + if ( !this.custom.formula ) return ""; + modifiers = Array.from(this.modifiers).concat(modifiers ?? []).join(""); + return this.custom.formula.replace(/(?:\d|\))d(?:\d+\w*|\(.+\)\d*\w*)/, `$&${modifiers}`); + } + + /* -------------------------------------------- */ + /** * Scale the damage by a number of steps using its configured scaling configuration. - * @param {number|Scaling} increase Number of steps above base damage to scaling. + * @param {number|Scaling} increase Number of steps above base damage to scaling. + * @param {object} [options={}] + * @param {Set} [options.modifiers] Additional modifiers to apply to the formula, if possible. * @returns {string} */ - scaledFormula(increase) { + scaledFormula(increase, { modifiers }={}) { if ( increase instanceof Scaling ) increase = increase.increase; switch ( this.scaling.mode ) { @@ -88,16 +110,16 @@ export class DamageData extends foundry.abstract.DataModel { case "half": increase = Math.floor(increase * .5); break; default: increase = 0; break; } - if ( !increase ) return this.formula; + if ( !increase ) return this.custom.enabled ? this._manualFormula() : this._automaticFormula(0, { modifiers }); let formula; // If dice count scaling, increase the count on the first die rolled const dieIncrease = (this.scaling.number ?? 0) * increase; if ( this.custom.enabled ) { - formula = this.custom.formula; + formula = this._manualFormula({ modifiers }); formula = formula.replace(/^(\d)+d/, (match, number) => `${Number(number) + dieIncrease}d`); } else { - formula = this._automaticFormula(dieIncrease); + formula = this._automaticFormula(dieIncrease, { modifiers }); } // If custom scaling included, modify to match increase and append for formula diff --git a/templates/activity/parts/damage-part.hbs b/templates/activity/parts/damage-part.hbs index ac71fafc1e..56d101b3aa 100644 --- a/templates/activity/parts/damage-part.hbs +++ b/templates/activity/parts/damage-part.hbs @@ -34,10 +34,14 @@ {{/unless}} +
+ {{ formField fields.modifiers name=(concat prefix "modifiers") value=source.modifiers label="DND5E.RollModifiers" + localize=true classes="label-top multi-select" rootId=@root.partId }} +
+
{{ formField fields.types name=(concat prefix "types") value=source.types options=typeOptions hint=false - label="DND5E.Type" localize=true classes="label-top multi-select" - rootId=@root.partId }} + label="DND5E.Type" localize=true classes="label-top multi-select" rootId=@root.partId }}
{{#if canScale}} diff --git a/templates/shared/fields/field-damage.hbs b/templates/shared/fields/field-damage.hbs index 23a62a367f..6d80c3183f 100644 --- a/templates/shared/fields/field-damage.hbs +++ b/templates/shared/fields/field-damage.hbs @@ -35,6 +35,10 @@ {{/unless}} +{{!-- Modifiers --}} +{{ formField fields.modifiers name=(concat prefix "modifiers") value=source.modifiers label="DND5E.RollModifiers" + localize=true rootId=@root.partId }} + {{!-- Types --}} {{#if types}} {{ formField fields.types name=(concat prefix "types") value=source.types options=types label="DND5E.Type" From 02bb9131023a4a698a68feb9ea51692d28677c21 Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Fri, 5 Jun 2026 13:56:22 -0700 Subject: [PATCH 2/5] [#6261] Tweak manual formula regex, pass modifiers everywhere --- module/data/shared/damage-field.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module/data/shared/damage-field.mjs b/module/data/shared/damage-field.mjs index 429cd8d3ac..33a429697c 100644 --- a/module/data/shared/damage-field.mjs +++ b/module/data/shared/damage-field.mjs @@ -90,7 +90,7 @@ export class DamageData extends foundry.abstract.DataModel { _manualFormula({ modifiers }={}) { if ( !this.custom.formula ) return ""; modifiers = Array.from(this.modifiers).concat(modifiers ?? []).join(""); - return this.custom.formula.replace(/(?:\d|\))d(?:\d+\w*|\(.+\)\d*\w*)/, `$&${modifiers}`); + return this.custom.formula.replace(/(?:\d|\))d(?:\d+\w*|\([^)]+\)\d*\w*)/, `$&${modifiers}`); } /* -------------------------------------------- */ @@ -110,7 +110,8 @@ export class DamageData extends foundry.abstract.DataModel { case "half": increase = Math.floor(increase * .5); break; default: increase = 0; break; } - if ( !increase ) return this.custom.enabled ? this._manualFormula() : this._automaticFormula(0, { modifiers }); + if ( !increase ) return this.custom.enabled + ? this._manualFormula({ modifiers }) : this._automaticFormula(0, { modifiers }); let formula; // If dice count scaling, increase the count on the first die rolled From 97c48b7338080cf294eb95486b3e803d17e5b032 Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Fri, 5 Jun 2026 14:48:48 -0700 Subject: [PATCH 3/5] [#6261] Use simpler damage formula for labels w/o modifiers --- module/data/activity/attack-data.mjs | 24 +++++++++---------- module/data/activity/base-activity.mjs | 13 +++++++---- module/data/activity/damage-data.mjs | 4 ++-- module/data/activity/heal-data.mjs | 8 ++++--- module/data/activity/save-data.mjs | 4 ++-- module/data/shared/_types.mjs | 6 +++++ module/data/shared/damage-field.mjs | 32 +++++++++++++++----------- 7 files changed, 53 insertions(+), 38 deletions(-) diff --git a/module/data/activity/attack-data.mjs b/module/data/activity/attack-data.mjs index 1cd91d9973..23861bf255 100644 --- a/module/data/activity/attack-data.mjs +++ b/module/data/activity/attack-data.mjs @@ -280,13 +280,9 @@ export default class BaseAttackActivityData extends BaseActivityData { /* -------------------------------------------- */ - /** - * Get the roll parts used to create the damage rolls. - * @param {Partial} [config={}] - * @returns {AttackDamageRollProcessConfiguration} - */ - getDamageConfig(config={}) { - const rollConfig = super.getDamageConfig(config); + /** @override */ + getDamageConfig(config={}, options={}) { + const rollConfig = super.getDamageConfig(config, options); // Handle ammunition const ammo = config.ammunition?.system; @@ -310,14 +306,18 @@ export default class BaseAttackActivityData extends BaseActivityData { // If mode is "replace" and base part is present, replace the base part if ( ammo.damage.replace & (basePartIndex !== -1) ) { damage.base = true; - rollConfig.rolls.splice(basePartIndex, 1, this._processDamagePart(damage, config, rollData, basePartIndex)); + rollConfig.rolls.splice( + basePartIndex, 1, + this._processDamagePart(damage, config, rollData, basePartIndex, options.formulaOptions) + ); } // Otherwise stick the ammo damage after base part (or as first part) else { damage.ammo = true; rollConfig.rolls.splice( - basePartIndex + 1, 0, this._processDamagePart(damage, rollConfig, rollData, basePartIndex + 1) + basePartIndex + 1, 0, + this._processDamagePart(damage, rollConfig, rollData, basePartIndex + 1, options.formulaOptions) ); } } @@ -368,8 +368,8 @@ export default class BaseAttackActivityData extends BaseActivityData { /* -------------------------------------------- */ /** @inheritDoc */ - _processDamagePart(damage, rollConfig, rollData, index=0) { - if ( !damage.base ) return super._processDamagePart(damage, rollConfig, rollData, index); + _processDamagePart(damage, rollConfig, rollData, index=0, options={}) { + if ( !damage.base ) return super._processDamagePart(damage, rollConfig, rollData, index, options); // Swap base damage for versatile if two-handed attack is made on versatile weapon if ( this.item.system.isVersatile && (rollConfig.attackMode === "twoHanded") ) { @@ -381,7 +381,7 @@ export default class BaseAttackActivityData extends BaseActivityData { damage = versatile; } - const roll = super._processDamagePart(damage, rollConfig, rollData, index); + const roll = super._processDamagePart(damage, rollConfig, rollData, index, options); roll.base = true; if ( this.item.type === "weapon" ) { diff --git a/module/data/activity/base-activity.mjs b/module/data/activity/base-activity.mjs index e7af81a688..42a60114dd 100644 --- a/module/data/activity/base-activity.mjs +++ b/module/data/activity/base-activity.mjs @@ -19,6 +19,7 @@ const { /** * @import { DamageRollConfiguration, DamageRollProcessConfiguration } from "../../dice/_types.mjs"; * @import { ActivityRollData } from "../../documents/_types.mjs"; + * @import { DamageFormulaOptions } from "../shared/_types.mjs"; * @import { ActivityData } from "./_types.mjs"; */ @@ -636,7 +637,7 @@ export default class BaseActivityData extends foundry.abstract.DataModel { * @param {ActivityRollData} rollData Deterministic roll data from the item. */ prepareDamageLabel(rollData) { - const config = this.getDamageConfig({}, { rollData }); + const config = this.getDamageConfig({}, { formulaOptions: { modifiers: false }, rollData }); const rolls = aggregateDamageRolls(config.rolls.map(({ base, data, options, parts }) => { const formula = parts.join(" + "); try { @@ -728,16 +729,17 @@ export default class BaseActivityData extends foundry.abstract.DataModel { * Get the roll parts used to create the damage rolls. * @param {Partial} [config={}] Existing damage configuration to merge into this one. * @param {object} [options] Damage configuration options. + * @param {DamageFormulaOptions} [options.formulaOptions] Options to configure the formula. * @param {ActivityRollData} [options.rollData] Use pre-existing roll data. * @returns {DamageRollProcessConfiguration} */ - getDamageConfig(config={}, { rollData }={}) { + getDamageConfig(config={}, { formulaOptions, rollData }={}) { if ( !this.damage?.parts ) return foundry.utils.mergeObject({ rolls: [] }, config); const rollConfig = foundry.utils.deepClone(config); rollData ??= this.getRollData(); rollConfig.rolls = this.damage.parts - .map((d, index) => this._processDamagePart(d, rollConfig, rollData, index)) + .map((d, index) => this._processDamagePart(d, rollConfig, rollData, index, formulaOptions)) .filter(d => d.parts.length) .concat(config.rolls ?? []); @@ -752,11 +754,12 @@ export default class BaseActivityData extends foundry.abstract.DataModel { * @param {Partial} rollConfig Roll configuration being built. * @param {ActivityRollData} rollData Roll data to populate with damage data. * @param {number} [index=0] Index of the damage part. + * @param {DamageFormulaOptions} [options={}] Options to configure the formula. * @returns {DamageRollConfiguration} * @protected */ - _processDamagePart(damage, rollConfig, rollData, index=0) { - const scaledFormula = damage.scaledFormula(rollConfig.scaling ?? rollData.scaling); + _processDamagePart(damage, rollConfig, rollData, index=0, options={}) { + const scaledFormula = damage.scaledFormula(rollConfig.scaling ?? rollData.scaling, options); const parts = scaledFormula ? [scaledFormula] : []; const data = { ...rollData }; diff --git a/module/data/activity/damage-data.mjs b/module/data/activity/damage-data.mjs index 490ba41b6c..60d6bf8655 100644 --- a/module/data/activity/damage-data.mjs +++ b/module/data/activity/damage-data.mjs @@ -63,8 +63,8 @@ export default class BaseDamageActivityData extends BaseActivityData { /* -------------------------------------------- */ /** @inheritDoc */ - getDamageConfig(config={}) { - const rollConfig = super.getDamageConfig(config); + getDamageConfig(config={}, options={}) { + const rollConfig = super.getDamageConfig(config, options); rollConfig.critical ??= {}; rollConfig.critical.allow ??= this.damage.critical.allow; diff --git a/module/data/activity/heal-data.mjs b/module/data/activity/heal-data.mjs index 3714215bd3..97414470ee 100644 --- a/module/data/activity/heal-data.mjs +++ b/module/data/activity/heal-data.mjs @@ -46,12 +46,14 @@ export default class BaseHealActivityData extends BaseActivityData { /* -------------------------------------------- */ /** @override */ - getDamageConfig(config={}) { + getDamageConfig(config={}, options={}) { if ( !this.healing.formula ) return foundry.utils.mergeObject({ rolls: [] }, config); const rollConfig = foundry.utils.mergeObject({ critical: { allow: false } }, config); - const rollData = this.getRollData(); - rollConfig.rolls = [this._processDamagePart(this.healing, rollConfig, rollData)].concat(config.rolls ?? []); + const rollData = options.rollData ?? this.getRollData(); + rollConfig.rolls = [ + this._processDamagePart(this.healing, rollConfig, rollData, options.formulaOptions) + ].concat(config.rolls ?? []); return rollConfig; } diff --git a/module/data/activity/save-data.mjs b/module/data/activity/save-data.mjs index 3cb18606d8..298e0057f0 100644 --- a/module/data/activity/save-data.mjs +++ b/module/data/activity/save-data.mjs @@ -138,8 +138,8 @@ export default class BaseSaveActivityData extends BaseActivityData { /* -------------------------------------------- */ /** @inheritDoc */ - getDamageConfig(config={}) { - const rollConfig = super.getDamageConfig(config); + getDamageConfig(config={}, options={}) { + const rollConfig = super.getDamageConfig(config, options); rollConfig.critical ??= {}; rollConfig.critical.allow ??= false; diff --git a/module/data/shared/_types.mjs b/module/data/shared/_types.mjs index b1f172cb6b..aed0d0fbd6 100644 --- a/module/data/shared/_types.mjs +++ b/module/data/shared/_types.mjs @@ -39,6 +39,12 @@ * @property {string} scaling.formula Arbitrary scaling formula which will be multiplied by scaling increase. */ +/** + * @typedef DamageFormulaOptions + * @property {Set|false} modifiers Additional modifiers to apply to the formula, if possible. + * A `false` value will remove modifiers provided by damage data. + */ + /** * @typedef DurationData * @property {string} value Scalar value for the activity's duration. diff --git a/module/data/shared/damage-field.mjs b/module/data/shared/damage-field.mjs index 33a429697c..8b4ff566c6 100644 --- a/module/data/shared/damage-field.mjs +++ b/module/data/shared/damage-field.mjs @@ -1,8 +1,13 @@ +import simplifyRollFormula from "../../dice/simplify-roll-formula.mjs"; import Scaling from "../../documents/scaling.mjs"; import FormulaField from "../fields/formula-field.mjs"; const { BooleanField, EmbeddedDataField, NumberField, SchemaField, SetField, StringField } = foundry.data.fields; +/** + * @import { DamageFormulaOptions } from "./_types.mjs"; + */ + /** * Field for storing damage data. */ @@ -62,9 +67,8 @@ export class DamageData extends foundry.abstract.DataModel { /** * Produce the auto-generated formula from the `number`, `denomination`, `modifiers`, and `bonus`. - * @param {number} [increase=0] Amount to increase the die count. - * @param {object} [options={}] - * @param {Set} [options.modifiers] Additional modifiers to apply to the formula, if possible. + * @param {number} [increase=0] Amount to increase the die count. + * @param {DamageFormulaOptions} [options={}] Options to configure the formula. * @returns {string} * @protected */ @@ -72,7 +76,8 @@ export class DamageData extends foundry.abstract.DataModel { let formula; const number = (this.number ?? 0) + increase; if ( number && this.denomination ) { - formula = `${number}d${this.denomination}${Array.from(this.modifiers).concat(modifiers ?? []).join("")}`; + formula = `${number}d${this.denomination}${modifiers !== false + ? Array.from(this.modifiers).concat(modifiers ?? []).join("") : ""}`; } if ( this.bonus ) formula = formula ? `${formula} + ${this.bonus}` : this.bonus; return formula ?? ""; @@ -82,27 +87,26 @@ export class DamageData extends foundry.abstract.DataModel { /** * Produce the manual formula from the `custom.formula` and `modifiers` (if possible). - * @param {object} [options={}] - * @param {Set} [options.modifiers] Additional modifiers to apply to the formula, if possible. + * @param {DamageFormulaOptions} [options={}] Options to configure the formula. * @returns {string} * @protected */ _manualFormula({ modifiers }={}) { if ( !this.custom.formula ) return ""; + if ( modifiers === false ) return this.custom.formula; modifiers = Array.from(this.modifiers).concat(modifiers ?? []).join(""); - return this.custom.formula.replace(/(?:\d|\))d(?:\d+\w*|\([^)]+\)\d*\w*)/, `$&${modifiers}`); + return this.custom.formula.replace(/(?:\d|\))?d(?:\d+\w*|\([^)]+\)\d*\w*)/, `$&${modifiers}`); } /* -------------------------------------------- */ /** * Scale the damage by a number of steps using its configured scaling configuration. - * @param {number|Scaling} increase Number of steps above base damage to scaling. - * @param {object} [options={}] - * @param {Set} [options.modifiers] Additional modifiers to apply to the formula, if possible. + * @param {number|Scaling} increase Number of steps above base damage to scaling. + * @param {DamageFormulaOptions} [options={}] Options to configure the formula. * @returns {string} */ - scaledFormula(increase, { modifiers }={}) { + scaledFormula(increase, options={}) { if ( increase instanceof Scaling ) increase = increase.increase; switch ( this.scaling.mode ) { @@ -111,16 +115,16 @@ export class DamageData extends foundry.abstract.DataModel { default: increase = 0; break; } if ( !increase ) return this.custom.enabled - ? this._manualFormula({ modifiers }) : this._automaticFormula(0, { modifiers }); + ? this._manualFormula(options) : this._automaticFormula(0, options); let formula; // If dice count scaling, increase the count on the first die rolled const dieIncrease = (this.scaling.number ?? 0) * increase; if ( this.custom.enabled ) { - formula = this._manualFormula({ modifiers }); + formula = this._manualFormula(options); formula = formula.replace(/^(\d)+d/, (match, number) => `${Number(number) + dieIncrease}d`); } else { - formula = this._automaticFormula(dieIncrease, { modifiers }); + formula = this._automaticFormula(dieIncrease, options); } // If custom scaling included, modify to match increase and append for formula From ee1d680cf87061440142ee5f19dbdb7f10b9d469 Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Tue, 16 Jun 2026 09:58:41 -0700 Subject: [PATCH 4/5] [#6261] Fix issue with joining modifier lists --- module/data/shared/damage-field.mjs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/module/data/shared/damage-field.mjs b/module/data/shared/damage-field.mjs index 8b4ff566c6..5482e18a7d 100644 --- a/module/data/shared/damage-field.mjs +++ b/module/data/shared/damage-field.mjs @@ -1,4 +1,3 @@ -import simplifyRollFormula from "../../dice/simplify-roll-formula.mjs"; import Scaling from "../../documents/scaling.mjs"; import FormulaField from "../fields/formula-field.mjs"; @@ -77,7 +76,7 @@ export class DamageData extends foundry.abstract.DataModel { const number = (this.number ?? 0) + increase; if ( number && this.denomination ) { formula = `${number}d${this.denomination}${modifiers !== false - ? Array.from(this.modifiers).concat(modifiers ?? []).join("") : ""}`; + ? Array.from(this.modifiers).concat(...(modifiers ?? [])).join("") : ""}`; } if ( this.bonus ) formula = formula ? `${formula} + ${this.bonus}` : this.bonus; return formula ?? ""; @@ -94,7 +93,7 @@ export class DamageData extends foundry.abstract.DataModel { _manualFormula({ modifiers }={}) { if ( !this.custom.formula ) return ""; if ( modifiers === false ) return this.custom.formula; - modifiers = Array.from(this.modifiers).concat(modifiers ?? []).join(""); + modifiers = Array.from(this.modifiers).concat(...(modifiers ?? [])).join(""); return this.custom.formula.replace(/(?:\d|\))?d(?:\d+\w*|\([^)]+\)\d*\w*)/, `$&${modifiers}`); } From 6709a94a90db2df948328dac8120afce5b5cae84 Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Tue, 16 Jun 2026 10:08:48 -0700 Subject: [PATCH 5/5] [#6261] Fix double background on `` --- less/v2/forms.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/less/v2/forms.less b/less/v2/forms.less index 08a5f7c6cc..749b655615 100644 --- a/less/v2/forms.less +++ b/less/v2/forms.less @@ -541,6 +541,8 @@ } :is(document-tags, multi-select, string-tags) { + input { --input-background-color: transparent; } + .tags.input-element-tags .tag { cursor: var(--cursor-pointer); font-size: var(--font-size-10);