Compare commits

...

3 commits

Author SHA1 Message Date
WBHarry
e79ccd34e9
[Fix] 1671 - Compendium Context Menues (#1677)
* Fixed

* .
2026-02-26 11:42:42 +01:00
Carlos Fernandez
4324c3abf2
[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
2026-02-26 11:37:40 +01:00
WBHarry
1b09b44d6c
[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

* .
2026-02-26 11:32:05 +01:00
8 changed files with 81 additions and 51 deletions

View file

@ -420,10 +420,7 @@ const updateActorsRangeDependentEffects = async token => {
// Get required distance and special case 5 feet to test adjacency // Get required distance and special case 5 feet to test adjacency
const required = rangeMeasurement[range]; const required = rangeMeasurement[range];
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id; const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
const inRange = const inRange = userTarget.distanceTo(token.object) <= required;
required === 5
? userTarget.isAdjacentWith(token.object)
: userTarget.distanceTo(token.object) <= required;
if (reverse ? inRange : !inRange) { if (reverse ? inRange : !inRange) {
enabledEffect = false; enabledEffect = false;
break; break;

View file

@ -431,18 +431,18 @@ export default function DHApplicationMixin(Base) {
{ {
name: 'disableEffect', name: 'disableEffect',
icon: 'fa-solid fa-lightbulb', icon: 'fa-solid fa-lightbulb',
condition: target => { condition: element => {
const doc = getDocFromElementSync(target); const target = element.closest('[data-item-uuid]');
return doc && !doc.disabled && doc.type !== 'beastform'; return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
}, },
callback: async target => (await getDocFromElement(target)).update({ disabled: true }) callback: async target => (await getDocFromElement(target)).update({ disabled: true })
}, },
{ {
name: 'enableEffect', name: 'enableEffect',
icon: 'fa-regular fa-lightbulb', icon: 'fa-regular fa-lightbulb',
condition: target => { condition: element => {
const doc = getDocFromElementSync(target); const target = element.closest('[data-item-uuid]');
return doc && doc.disabled && doc.type !== 'beastform'; return target.dataset.disabled && target.dataset.itemType !== 'beastform';
}, },
callback: async target => (await getDocFromElement(target)).update({ disabled: false }) callback: async target => (await getDocFromElement(target)).update({ disabled: false })
} }
@ -536,9 +536,9 @@ export default function DHApplicationMixin(Base) {
options.push({ options.push({
name: 'CONTROLS.CommonDelete', name: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash', icon: 'fa-solid fa-trash',
condition: target => { condition: element => {
const doc = getDocFromElementSync(target); const target = element.closest('[data-item-uuid]');
return doc && doc.type !== 'beastform'; return target.dataset.itemType !== 'beastform';
}, },
callback: async (target, event) => { callback: async (target, event) => {
const doc = await getDocFromElement(target); const doc = await getDocFromElement(target);

View file

@ -54,30 +54,58 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
if (this === target) return 0; if (this === target) return 0;
const originPoint = this.center; 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, // Compute for gridless. This version returns circular edge to edge + grid distance,
// so that tokens that are touching return 5. // so that tokens that are touching return 5.
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
const boundsCorrection = canvas.grid.distance / canvas.grid.size; const boundsCorrection = canvas.grid.distance / canvas.grid.size;
const originRadius = (this.bounds.width * boundsCorrection) / 2; const originRadius = (thisBounds.width * boundsCorrection) / 2;
const targetRadius = (target.bounds.width * boundsCorrection) / 2; const targetRadius = (targetBounds.width * boundsCorrection) / 2;
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance; const measuredDistance = canvas.grid.measurePath([
return Math.floor(distance - originRadius - targetRadius + canvas.grid.distance); { ...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 // Compute what the closest grid space of each token is, then compute that distance
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint); const originEdge = this.#getEdgeBoundary(thisBounds, originPoint, targetPoint);
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint); const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint);
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({ const adjustedOriginPoint = originEdge
? canvas.grid.getTopLeftPoint({
x: originEdge.x + Math.sign(originPoint.x - originEdge.x), x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
y: originEdge.y + Math.sign(originPoint.y - originEdge.y) y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
}); })
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({ : originPoint;
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x), const adjustDestinationPoint = targetEdge
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y) ? canvas.grid.getTopLeftPoint({
}); x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance; 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) { _onHoverIn(event, options) {
@ -103,8 +131,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
// Determine the actual range // Determine the actual range
const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement; const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
const distanceNum = originToken.distanceTo(this); const distanceResult = DhMeasuredTemplate.getRangeLabels(originToken.distanceTo(this), ranges);
const distanceResult = DhMeasuredTemplate.getRangeLabels(distanceNum, ranges);
const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim(); const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim();
// Create the element // Create the element
@ -156,11 +183,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
return null; 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 */ /** @inheritDoc */
_drawBar(number, bar, data) { _drawBar(number, bar, data) {
const val = Number(data.value); const val = Number(data.value);

View file

@ -190,6 +190,10 @@ export default class DhpAdversary extends DhCreature {
} }
} }
prepareDerivedData() {
this.attack.roll.isStandardAttack = true;
}
_getTags() { _getTags() {
const tags = [ const tags = [
game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`), game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`),

View file

@ -165,7 +165,8 @@ export default class DamageField extends fields.SchemaField {
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt; if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
const isAdversary = this.actor.type === 'adversary'; 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'); const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt; if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
} }

View file

@ -56,6 +56,7 @@ Parameters:
{{> 'daggerheart.inventory-item' {{> 'daggerheart.inventory-item'
item=item item=item
type=../type type=../type
disabledEffect=../disabledEffect
actorType=../actorType actorType=../actorType
hideControls=../hideControls hideControls=../hideControls
hideContextMenu=../hideContextMenu hideContextMenu=../hideContextMenu

View file

@ -17,8 +17,12 @@ Parameters:
- showActions {boolean} : If true show feature's actions. - showActions {boolean} : If true show feature's actions.
--}} --}}
<li class="inventory-item" data-item-id="{{item.id}}" {{#if (or (eq type 'action' ) (eq type 'attack' ))}} <li class="inventory-item" data-item-id="{{item.id}}"
data-action-id="{{item.id}}" {{/if}} data-item-uuid="{{item.uuid}}" data-type="{{type}}" data-no-compendium-edit="{{noCompendiumEdit}}"> {{#if (or (eq type 'action' ) (eq type 'attack' ))}}data-action-id="{{item.id}}" {{/if}}
{{#if disabledEffect}}data-disabled="true"{{/if}}
data-type="{{type}}" data-item-type="{{item.type}}"
data-item-uuid="{{item.uuid}}" data-no-compendium-edit="{{noCompendiumEdit}}"
>
<div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}> <div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}>
{{!-- Image --}} {{!-- Image --}}
<div class="img-portait" data-action='{{ifThen (or (hasProperty item "use") (eq type "attack")) "useItem" (ifThen <div class="img-portait" data-action='{{ifThen (or (hasProperty item "use") (eq type "attack")) "useItem" (ifThen

View file

@ -13,6 +13,7 @@
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='DAGGERHEART.GENERAL.inactiveEffects' title='DAGGERHEART.GENERAL.inactiveEffects'
type='effect' type='effect'
disabledEffect=true
isGlassy=true isGlassy=true
collection=effects.inactives collection=effects.inactives
canCreate=true canCreate=true