From 1b09b44d6c46568ed625454e202327dc68b1a018 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:32:05 +0100 Subject: [PATCH 01/16] [Fix] 1676 - Horde Damage Fix (#1678) * Fixed so that horde damage reduction is only applied to the standard attack * Changed to just adding 'isStandardAttack' in adversary data prep * . --- module/data/actor/adversary.mjs | 4 ++++ module/data/fields/action/damageField.mjs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 78964720..0a446c15 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -190,6 +190,10 @@ export default class DhpAdversary extends DhCreature { } } + prepareDerivedData() { + this.attack.roll.isStandardAttack = true; + } + _getTags() { const tags = [ game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`), diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index efad726c..6439344b 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -165,7 +165,8 @@ export default class DamageField extends fields.SchemaField { if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt; const isAdversary = this.actor.type === 'adversary'; - if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) { + const isHorde = this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id; + if (isAdversary && isHorde && this.roll?.isStandardAttack) { const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde'); if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt; } From 4324c3abf23f909e7cb9d545868ebbcdd39b41c7 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 26 Feb 2026 05:37:40 -0500 Subject: [PATCH 02/16] [Fix] Support elevation in token distance hovering and fix error when overlapping (#1675) * Support elevation in token distance hovering * Reduce diffs * Refine elevation check to handle stacked tokens * Fix issue with overlapping tokens * Fix tooltip reporting very close for adjacent diagonal tokens --- daggerheart.mjs | 5 +-- module/canvas/placeables/token.mjs | 68 ++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/daggerheart.mjs b/daggerheart.mjs index 1987ec12..05b57ac9 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -420,10 +420,7 @@ const updateActorsRangeDependentEffects = async token => { // Get required distance and special case 5 feet to test adjacency const required = rangeMeasurement[range]; const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id; - const inRange = - required === 5 - ? userTarget.isAdjacentWith(token.object) - : userTarget.distanceTo(token.object) <= required; + const inRange = userTarget.distanceTo(token.object) <= required; if (reverse ? inRange : !inRange) { enabledEffect = false; break; diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 068f21e1..148466c1 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -54,30 +54,58 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { if (this === target) return 0; const originPoint = this.center; - const destinationPoint = target.center; + const targetPoint = target.center; + const thisBounds = this.bounds; + const targetBounds = target.bounds; + const adjacencyBuffer = canvas.grid.distance * 1.75; // handles diagonals with one square elevation difference + + // Figure out the elevation difference. + // This intends to return "grid distance" for adjacent ones, so we add that number if not overlapping. + const sizePerUnit = canvas.grid.size / canvas.grid.distance; + const thisHeight = Math.max(thisBounds.width, thisBounds.height) / sizePerUnit; + const targetHeight = Math.max(targetBounds.width, targetBounds.height) / sizePerUnit; + const thisElevation = [this.document.elevation, this.document.elevation + thisHeight]; + const targetElevation = [target.document.elevation, target.document.elevation + targetHeight]; + const isSameAltitude = + thisElevation[0] < targetElevation[1] && // bottom of this must be at or below the top of target + thisElevation[1] > targetElevation[0]; // top of this must be at or above the bottom of target + const [lower, higher] = [targetElevation, thisElevation].sort((a, b) => a[1] - b[1]); + const elevation = isSameAltitude ? 0 : higher[0] - lower[1] + canvas.grid.distance; // Compute for gridless. This version returns circular edge to edge + grid distance, // so that tokens that are touching return 5. if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { const boundsCorrection = canvas.grid.distance / canvas.grid.size; - const originRadius = (this.bounds.width * boundsCorrection) / 2; - const targetRadius = (target.bounds.width * boundsCorrection) / 2; - const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance; - return Math.floor(distance - originRadius - targetRadius + canvas.grid.distance); + const originRadius = (thisBounds.width * boundsCorrection) / 2; + const targetRadius = (targetBounds.width * boundsCorrection) / 2; + const measuredDistance = canvas.grid.measurePath([ + { ...originPoint, elevation: 0 }, + { ...targetPoint, elevation } + ]).distance; + const distance = Math.floor(measuredDistance - originRadius - targetRadius + canvas.grid.distance); + return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance); } // Compute what the closest grid space of each token is, then compute that distance - const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint); - const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint); - const adjustedOriginPoint = canvas.grid.getTopLeftPoint({ - x: originEdge.x + Math.sign(originPoint.x - originEdge.x), - y: originEdge.y + Math.sign(originPoint.y - originEdge.y) - }); - const adjustDestinationPoint = canvas.grid.getTopLeftPoint({ - x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x), - y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y) - }); - return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance; + const originEdge = this.#getEdgeBoundary(thisBounds, originPoint, targetPoint); + const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint); + const adjustedOriginPoint = originEdge + ? canvas.grid.getTopLeftPoint({ + x: originEdge.x + Math.sign(originPoint.x - originEdge.x), + y: originEdge.y + Math.sign(originPoint.y - originEdge.y) + }) + : originPoint; + const adjustDestinationPoint = targetEdge + ? canvas.grid.getTopLeftPoint({ + x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x), + y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y) + }) + : targetPoint; + const distance = canvas.grid.measurePath([ + { ...adjustedOriginPoint, elevation: 0 }, + { ...adjustDestinationPoint, elevation } + ]).distance; + return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance); } _onHoverIn(event, options) { @@ -103,8 +131,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { // Determine the actual range const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement; - const distanceNum = originToken.distanceTo(this); - const distanceResult = DhMeasuredTemplate.getRangeLabels(distanceNum, ranges); + const distanceResult = DhMeasuredTemplate.getRangeLabels(originToken.distanceTo(this), ranges); const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim(); // Create the element @@ -156,11 +183,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { return null; } - /** Tests if the token is at least adjacent with another, with some leeway for diagonals */ - isAdjacentWith(token) { - return this.distanceTo(token) <= canvas.grid.distance * 1.5; - } - /** @inheritDoc */ _drawBar(number, bar, data) { const val = Number(data.value); From e79ccd34e98bda971f81e88252ad16434a26a8ea Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:42:42 +0100 Subject: [PATCH 03/16] [Fix] 1671 - Compendium Context Menues (#1677) * Fixed * . --- .../sheets/api/application-mixin.mjs | 18 ++++++------- .../partials/inventory-fieldset-items-V2.hbs | 1 + .../global/partials/inventory-item-V2.hbs | 8 ++++-- templates/sheets/global/tabs/tab-effects.hbs | 25 ++++++++++--------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 49f7dcf0..449880fb 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -431,18 +431,18 @@ export default function DHApplicationMixin(Base) { { name: 'disableEffect', icon: 'fa-solid fa-lightbulb', - condition: target => { - const doc = getDocFromElementSync(target); - return doc && !doc.disabled && doc.type !== 'beastform'; + condition: element => { + const target = element.closest('[data-item-uuid]'); + return !target.dataset.disabled && target.dataset.itemType !== 'beastform'; }, callback: async target => (await getDocFromElement(target)).update({ disabled: true }) }, { name: 'enableEffect', icon: 'fa-regular fa-lightbulb', - condition: target => { - const doc = getDocFromElementSync(target); - return doc && doc.disabled && doc.type !== 'beastform'; + condition: element => { + const target = element.closest('[data-item-uuid]'); + return target.dataset.disabled && target.dataset.itemType !== 'beastform'; }, callback: async target => (await getDocFromElement(target)).update({ disabled: false }) } @@ -536,9 +536,9 @@ export default function DHApplicationMixin(Base) { options.push({ name: 'CONTROLS.CommonDelete', icon: 'fa-solid fa-trash', - condition: target => { - const doc = getDocFromElementSync(target); - return doc && doc.type !== 'beastform'; + condition: element => { + const target = element.closest('[data-item-uuid]'); + return target.dataset.itemType !== 'beastform'; }, callback: async (target, event) => { const doc = await getDocFromElement(target); diff --git a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs index d2534a5a..0a3275d0 100644 --- a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs +++ b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs @@ -56,6 +56,7 @@ Parameters: {{> 'daggerheart.inventory-item' item=item type=../type + disabledEffect=../disabledEffect actorType=../actorType hideControls=../hideControls hideContextMenu=../hideContextMenu diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index 76e13a5c..fec215a0 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -17,8 +17,12 @@ Parameters: - showActions {boolean} : If true show feature's actions. --}} -
  • +
  • {{!-- Image --}}
    {{> 'daggerheart.inventory-items' - title='DAGGERHEART.GENERAL.activeEffects' - type='effect' - isGlassy=true - collection=effects.actives - canCreate=true - hideResources=true + title='DAGGERHEART.GENERAL.activeEffects' + type='effect' + isGlassy=true + collection=effects.actives + canCreate=true + hideResources=true }} {{> 'daggerheart.inventory-items' - title='DAGGERHEART.GENERAL.inactiveEffects' - type='effect' - isGlassy=true - collection=effects.inactives - canCreate=true - hideResources=true + title='DAGGERHEART.GENERAL.inactiveEffects' + type='effect' + disabledEffect=true + isGlassy=true + collection=effects.inactives + canCreate=true + hideResources=true }} \ No newline at end of file From c48842dd2d25c0f1487c9bb123d15cb4dad95964 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 26 Feb 2026 20:04:59 +0100 Subject: [PATCH 04/16] Fixed error on deleting a sceneEnvironment item --- module/data/registeredTriggers.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/data/registeredTriggers.mjs b/module/data/registeredTriggers.mjs index ee4f3b49..ab86351c 100644 --- a/module/data/registeredTriggers.mjs +++ b/module/data/registeredTriggers.mjs @@ -75,7 +75,7 @@ export default class RegisteredTriggers extends Map { unregisterSceneEnvironmentTriggers(flagSystemData) { const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData); for (const environment of sceneData.sceneEnvironments) { - if (environment.pack) continue; + if (!environment || environment.pack) continue; this.unregisterItemTriggers(environment.system.features); } } From 0d0b5125bacc5d61b4f0596490842e79e8eec3de Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:37:33 +0100 Subject: [PATCH 05/16] [Fix] 1683 - Strange Patterns Explanation (#1693) * Added an explanation text to Strange Patterns trigger dialog * Update lang/en.json Co-authored-by: Chris Ryan <73275196+chrisryan10@users.noreply.github.com> --------- Co-authored-by: Chris Ryan <73275196+chrisryan10@users.noreply.github.com> --- lang/en.json | 1 + .../classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lang/en.json b/lang/en.json index 937de844..7b0840e4 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1294,6 +1294,7 @@ "triggerTexts": { "strangePatternsContentTitle": "Matched {nr} times.", "strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.", + "strangePatternsActionExplanation": "Left click to increase, right click to decrease", "ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?", "ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you." }, diff --git a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json index 95f42c06..953b3a2c 100644 --- a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json +++ b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json @@ -85,7 +85,7 @@ { "trigger": "dualityRoll", "triggeringActorType": "self", - "command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n
    ${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}
    \n
    ${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}
    \n
    \n \n \n
    \n
    `;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;" + "command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n
    ${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}
    \n
    ${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}
    \n
    ${game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsActionExplanation')}
    \n
    \n \n \n
    \n
    `;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;" } ] } From 5459581f7fd0f33fbfe5f4ac3d84891ee086a332 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 4 Mar 2026 01:10:40 +0100 Subject: [PATCH 06/16] Fixed styling in firefox (#1692) --- styles/less/sheets/actors/actor-sheet-shared.less | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index bf6393f4..23db088a 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -183,6 +183,11 @@ } } + .domain-details { + display: flex; + flex-direction: column; + } + .level-details { align-self: center; } From 986544a653c35b5a424990b69b86514ab90e123f Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:16:11 +0100 Subject: [PATCH 07/16] [Fix] 1689 - Missing Feature Errors (#1690) * Fixed so that weaponfeatures and armorFeatures are tolerant of features having been removed * . --- module/config/itemConfig.mjs | 8 ++------ module/data/item/armor.mjs | 6 ++---- module/data/item/weapon.mjs | 6 ++---- module/helpers/utils.mjs | 2 +- system.json | 2 +- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index 7d80e597..77328987 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -467,9 +467,7 @@ export const allArmorFeatures = () => { }; export const orderedArmorFeatures = () => { - const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures - .armorFeatures; - const allFeatures = { ...armorFeatures, ...homebrewFeatures }; + const allFeatures = allArmorFeatures(); const all = Object.keys(allFeatures).map(key => { const feature = allFeatures[key]; return { @@ -1404,9 +1402,7 @@ export const allWeaponFeatures = () => { }; export const orderedWeaponFeatures = () => { - const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures - .weaponFeatures; - const allFeatures = { ...weaponFeatures, ...homebrewFeatures }; + const allFeatures = allWeaponFeatures(); const all = Object.keys(allFeatures).map(key => { const feature = allFeatures[key]; return { diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 2d31c290..0958a9f3 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -23,9 +23,7 @@ export default class DHArmor extends AttachableItem { armorFeatures: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ - required: true, - choices: CONFIG.DH.ITEM.allArmorFeatures, - blank: true + required: true }), effectIds: new fields.ArrayField(new fields.StringField({ required: true })), actionIds: new fields.ArrayField(new fields.StringField({ required: true })) @@ -58,7 +56,7 @@ export default class DHArmor extends AttachableItem { async getDescriptionData() { const baseDescription = this.description; const allFeatures = CONFIG.DH.ITEM.allArmorFeatures(); - const features = this.armorFeatures.map(x => allFeatures[x.value]); + const features = this.armorFeatures.map(x => allFeatures[x.value]).filter(x => x); const prefix = await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/sheets/items/armor/description.hbs', diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 5c6f8514..051fd42d 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -38,9 +38,7 @@ export default class DHWeapon extends AttachableItem { weaponFeatures: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ - required: true, - choices: CONFIG.DH.ITEM.allWeaponFeatures, - blank: true + required: true }), effectIds: new fields.ArrayField(new fields.StringField({ required: true })), actionIds: new fields.ArrayField(new fields.StringField({ required: true })) @@ -121,7 +119,7 @@ export default class DHWeapon extends AttachableItem { const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label); const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures(); - const features = this.weaponFeatures.map(x => allFeatures[x.value]); + const features = this.weaponFeatures.map(x => allFeatures[x.value]).filter(x => x); const prefix = await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/sheets/items/weapon/description.hbs', diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 4ecc7809..57badd89 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -119,8 +119,8 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {} }), maxTags: typeof maxTags === 'function' ? maxTags() : maxTags, dropdown: { + searchKeys: ['value', 'name'], mapValueTo: 'name', - searchKeys: ['value'], enabled: 0, maxItems: 100, closeOnSelect: true, diff --git a/system.json b/system.json index fb23ad7b..40de5fa1 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.7.2", + "version": "1.7.3", "compatibility": { "minimum": "13.346", "verified": "13.351", From 3267f3f53143ef4a0e7454fdae610d21319db774 Mon Sep 17 00:00:00 2001 From: Psitacus <59754077+Psitacus@users.noreply.github.com> Date: Thu, 5 Mar 2026 01:56:35 -0700 Subject: [PATCH 08/16] fix instances of rolls being called checks (#1702) Co-authored-by: Psitacus --- lang/en.json | 8 ++++---- .../domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lang/en.json b/lang/en.json index 7b0840e4..6c52beeb 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1166,12 +1166,12 @@ }, "far": { "name": "Far", - "description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility check to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.", + "description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility roll to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.", "short": "Far" }, "veryFar": { "name": "Very Far", - "description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility check to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.", + "description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility roll to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.", "short": "V. Far" } }, @@ -2806,7 +2806,7 @@ "title": "Domain Card" }, "dualityRoll": { - "abilityCheckTitle": "{ability} Check" + "abilityCheckTitle": "{ability} Roll" }, "effectSummary": { "title": "Effects Applied", @@ -2821,7 +2821,7 @@ "selectLeader": "Select a Leader", "selectMember": "Select a Member", "rerollTitle": "Reroll Group Roll", - "rerollContent": "Are you sure you want to reroll your {trait} check?", + "rerollContent": "Are you sure you want to reroll your {trait} roll?", "rerollTooltip": "Reroll", "wholePartySelected": "The whole party is selected" }, diff --git a/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json b/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json index 8cdb62b0..16753e1e 100644 --- a/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json +++ b/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json @@ -53,7 +53,7 @@ "difficulty": null, "damageMod": "none" }, - "name": "Agility Check", + "name": "Agility Roll", "img": "icons/skills/melee/sword-engraved-glow-purple.webp", "range": "close" } From 1212bd01f8eb45b3f2911b3536c72e438c6900a8 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 5 Mar 2026 15:30:31 -0500 Subject: [PATCH 09/16] Increase the click area of sidebar and inventory control buttons (#1703) --- styles/less/global/inventory-item.less | 7 ++++--- templates/sheets/global/partials/inventory-item-V2.hbs | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index 9045baf5..b6c09dbf 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -103,10 +103,9 @@ display: flex; align-items: center; justify-content: end; - gap: 8px; a { - width: 15px; + width: 20px; text-align: center; } @@ -275,8 +274,10 @@ grid-area: controls; align-self: start; padding-top: 0.3125rem; - gap: 4px; margin-bottom: -1px; + a { + width: 18px; + } } > .item-labels { align-self: start; diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index fec215a0..e496ce4b 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -109,7 +109,7 @@ Parameters: {{else if (eq type 'armor')}} - + {{/if}} {{#if (eq type 'domainCard')}} @@ -125,7 +125,7 @@ Parameters: {{/if}} {{#if (hasProperty item "toChat")}} - + {{/if}} {{else}} @@ -138,7 +138,7 @@ Parameters: {{/unless}} {{#unless hideContextMenu}} - + {{/unless}} {{/if}} From 0675e1f0199b2a195d77dcf9d41ff45f98b38ab7 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 5 Mar 2026 15:31:49 -0500 Subject: [PATCH 10/16] Add recall cost to domain cards in grid view (#1700) --- styles/less/global/inventory-item.less | 21 +++++++++++++++++++ .../global/partials/domain-card-item.hbs | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index b6c09dbf..d703d189 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -335,6 +335,27 @@ border-radius: 6px; } + .recall-cost { + position: absolute; + right: 4px; + top: 4px; + width: 1.75em; + height: 1.75em; + + align-items: center; + background: @dark-blue; + border-radius: 50%; + border: 1px solid @golden; + color: @golden; + display: flex; + justify-content: center; + padding-top: 0.1em; // compensate for font + + i { + font-size: 0.68em; + } + } + .card-label { display: flex; flex-direction: column; diff --git a/templates/sheets/global/partials/domain-card-item.hbs b/templates/sheets/global/partials/domain-card-item.hbs index ae95b7af..54e44e64 100644 --- a/templates/sheets/global/partials/domain-card-item.hbs +++ b/templates/sheets/global/partials/domain-card-item.hbs @@ -1,5 +1,9 @@
  • + + {{item.system.recallCost}} + +