From 1bccd0422d32b099f86c1bc1a727e01333ce5a1b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 31 Dec 2025 20:45:32 -0500 Subject: [PATCH] Fix detection of range dependencies --- daggerheart.mjs | 29 +++++++------- module/canvas/placeables/token.mjs | 63 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 14 deletions(-) diff --git a/daggerheart.mjs b/daggerheart.mjs index f1d8c67a..d8ebb713 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -330,14 +330,14 @@ const updateActorsRangeDependentEffects = async token => { break; } - const distanceBetween = canvas.grid.measurePath([ - userTarget.document.movement.destination, - token.movement.destination - ]).distance; - const distance = rangeMeasurement[range]; - + // Get required distance and special case 5 feet to test adjacency + const required = rangeMeasurement[range]; const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id; - if (reverse ? distanceBetween <= distance : distanceBetween > distance) { + const inRange = + required === 5 + ? userTarget.isAdjacentWith(token.object) + : userTarget.distanceTo(token.object) <= required; + if (reverse ? inRange : !inRange) { enabledEffect = false; break; } @@ -351,16 +351,15 @@ const updateAllRangeDependentEffects = async () => { const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects; if (!effectsAutomation.rangeDependent) return; - // Only consider tokens on the active scene - const tokens = game.scenes.find(x => x.active).tokens; + const tokens = canvas.scene.tokens; if (game.user.character) { // The character updates their character's token. There can be only one token. const characterToken = tokens.find(x => x.actor === game.user.character); updateActorsRangeDependentEffects(characterToken); - } else if (game.user.isGM) { + } else if (game.user.isActiveGM) { // The GM is responsible for all other tokens. const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character); - for (let token of tokens.filter(x => !playerCharacters.includes(x.actor))) { + for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) { updateActorsRangeDependentEffects(token); } } @@ -368,12 +367,14 @@ const updateAllRangeDependentEffects = async () => { const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50); -Hooks.on('targetToken', async (user, token, targeted) => { +Hooks.on('targetToken', () => { debouncedRangeEffectCall(); }); -Hooks.on('moveToken', async (movedToken, data) => { - debouncedRangeEffectCall(); +Hooks.on('refreshToken', (_, options) => { + if (options.refreshPosition) { + debouncedRangeEffectCall(); + } }); Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 64ec3fa9..e8b85938 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -34,6 +34,69 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { this.renderFlags.set({ refreshEffects: true }); } + /** + * Returns the distance from this token to another token object. + * This value is corrected to handle alternate token sizes and other grid types + * according to the diagonal rules. + */ + distanceTo(target) { + if (!canvas.ready) return NaN; + if (this === target) return 0; + + const originPoint = this.center; + const destinationPoint = target.center; + + // 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 distance - originRadius - targetRadius + 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; + } + + /** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */ + #getEdgeBoundary(bounds, originPoint, destinationPoint) { + const points = [ + { x: bounds.x, y: bounds.y }, + { x: bounds.x + bounds.width, y: bounds.y }, + { x: bounds.x + bounds.width, y: bounds.y + bounds.height }, + { x: bounds.x, y: bounds.y + bounds.height } + ]; + const pairsToTest = [ + [points[0], points[1]], + [points[1], points[2]], + [points[2], points[3]], + [points[3], points[0]] + ]; + for (const pair of pairsToTest) { + const result = foundry.utils.lineSegmentIntersection(originPoint, destinationPoint, pair[0], pair[1]); + if (result) return result; + } + + 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);