[Feature] Show token distance on hover (#1607)

* Show token distance on hover

* Do not show distance hover when ranges variant rule is disabled

* Use range labels function for distance hover

* Fix very far and support feet

* .

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>
This commit is contained in:
Carlos Fernandez 2026-01-30 09:58:21 -05:00 committed by GitHub
parent b374070809
commit 1bc9e07098
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 92 additions and 26 deletions

View file

@ -2405,6 +2405,14 @@
"hideAttribution": { "hideAttribution": {
"label": "Hide Attribution" "label": "Hide Attribution"
}, },
"showTokenDistance": {
"label": "Show Token Distance on Hover",
"choices": {
"always": "Always",
"encounters": "Encounters",
"never": "Never"
}
},
"expandedTitle": "Auto-expand Descriptions", "expandedTitle": "Auto-expand Descriptions",
"extendCharacterDescriptions": { "extendCharacterDescriptions": {
"label": "Characters" "label": "Characters"

View file

@ -18,8 +18,9 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
static getRangeLabels(distanceValue, settings) { static getRangeLabels(distanceValue, settings) {
let result = { distance: distanceValue, units: '' }; let result = { distance: distanceValue, units: '' };
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement; if (!settings.enabled) return result;
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting; const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
if (sceneRangeMeasurement?.setting === disable.id) { if (sceneRangeMeasurement?.setting === disable.id) {
result.distance = distanceValue; result.distance = distanceValue;
@ -27,31 +28,9 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
return result; return result;
} }
const melee = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.melee : settings.melee; const ranges = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement : settings;
const veryClose = const distanceKey = ['melee', 'veryClose', 'close', 'far'].find(r => ranges[r] >= distanceValue);
sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.veryClose : settings.veryClose; result.distance = game.i18n.localize(`DAGGERHEART.CONFIG.Range.${distanceKey ?? 'veryFar'}.name`);
const close = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.close : settings.close;
const far = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.far : settings.far;
if (distanceValue <= melee) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.melee.name');
return result;
}
if (distanceValue <= veryClose) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryClose.name');
return result;
}
if (distanceValue <= close) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.close.name');
return result;
}
if (distanceValue <= far) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.far.name');
return result;
}
if (distanceValue > far) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryFar.name');
}
return result; return result;
} }
} }

View file

@ -1,3 +1,5 @@
import DhMeasuredTemplate from "./measuredTemplate.mjs";
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
/** @inheritdoc */ /** @inheritdoc */
async _draw(options) { async _draw(options) {
@ -78,6 +80,60 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance; return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
} }
_onHoverIn(event, options) {
super._onHoverIn(event, options);
// Check if the setting is enabled
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).showTokenDistance;
if (setting === "never" || (setting === "encounters" && !game.combat?.started)) return;
// Check if this token isn't invisible and is actually being hovered
const isTokenValid =
this.visible &&
this.hover &&
!this.isPreview &&
!this.document.isSecret &&
!this.controlled &&
!this.animation;
if (!isTokenValid) return;
// Ensure we have a single controlled token
const originToken = canvas.tokens.controlled[0];
if (!originToken || canvas.tokens.controlled.length > 1) return;
// 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 distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim();
// Create the element
const element = document.createElement('div');
element.id = 'token-hover-distance';
element.classList.add('waypoint-label', 'last');
const ruler = document.createElement('i');
ruler.classList.add('fa-solid', 'fa-ruler');
element.appendChild(ruler);
const labelEl = document.createElement('span');
labelEl.classList.add('total-measurement');
labelEl.textContent = distanceLabel;
element.appendChild(labelEl);
// Position the element and add to the DOM
const center = this.getCenterPoint();
element.style.setProperty('--transformY', 'calc(-100% - 10px)');
element.style.setProperty('--position-y', `${this.y}px`);
element.style.setProperty('--position-x', `${center.x}px`);
element.style.setProperty('--ui-scale', String(canvas.dimensions.uiScale));
document.querySelector('#token-hover-distance')?.remove();
document.querySelector('#measurement').appendChild(element);
}
_onHoverOut(...args) {
super._onHoverOut(...args);
document.querySelector('#token-hover-distance')?.remove();
}
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */ /** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
#getEdgeBoundary(bounds, originPoint, destinationPoint) { #getEdgeBoundary(bounds, originPoint, destinationPoint) {
const points = [ const points = [

View file

@ -42,6 +42,25 @@ export default class DhAppearance extends foundry.abstract.DataModel {
damage: new BooleanField(), damage: new BooleanField(),
target: new BooleanField() target: new BooleanField()
}), }),
showTokenDistance: new StringField({
required: true,
choices: {
always: {
value: 'always',
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.always'
},
encounters: {
value: 'encounters',
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.encounters'
},
never: {
value: 'never',
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.never'
}
},
nullable: false,
initial: 'always'
}),
hideAttribution: new BooleanField(), hideAttribution: new BooleanField(),
showGenericStatusEffects: new BooleanField({ initial: true }) showGenericStatusEffects: new BooleanField({ initial: true })
}; };

View file

@ -16,6 +16,10 @@
value=setting.showGenericStatusEffects value=setting.showGenericStatusEffects
localize=true}} localize=true}}
{{formGroup {{formGroup
fields.showTokenDistance
value=setting.showTokenDistance
localize=true}}
{{formGroup
fields.hideAttribution fields.hideAttribution
value=setting.hideAttribution value=setting.hideAttribution
localize=true}} localize=true}}