diff --git a/languages/en.json b/languages/en.json index eda25bc..27b2937 100644 --- a/languages/en.json +++ b/languages/en.json @@ -15,6 +15,9 @@ "elevationruler.keybindings.togglePathfinding.name": "Temporarily Toggle Pathfinding", "elevationruler.keybindings.togglePathfinding.hint": "If the pathfinding button is enabled, holding this key will temporarily disable pathfinding. If the pathfinding button is not enabled, holding this key will temporarily enable pathfinding.", + "elevationruler.keybindings.forceToGround.name": "Force Ruler Elevation to Ground", + "elevationruler.keybindings.forceToGround.hint": "When measuring, press this key to get the distance to the ground. Press again to revert.", + "elevationruler.settings.levels-use-floor-label.name": "Levels Floor Label", "elevationruler.settings.levels-use-floor-label.hint": "If Levels module is active, label the ruler with the current floor, if the Levels UI floors are named.", "elevationruler.settings.levels-labels-never": "Never", diff --git a/scripts/Ruler.js b/scripts/Ruler.js index dd3c8cd..5828aee 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -538,10 +538,13 @@ function segmentGridHalfIntersection(gridCoords, a, b) { /** * Wrap Ruler.prototype._onDragStart * Record whether shift is held. + * Reset FORCE_TO_GROUND * @param {PIXI.FederatedEvent} event The drag start event * @see {Canvas._onDragLeftStart} */ function _onDragStart(wrapped, event) { + Settings.FORCE_TO_GROUND = false; + this._userElevationIncrements = 0; this._unsnap = event.shiftKey || canvas.scene.grid.type === CONST.GRID_TYPES.GRIDLESS; return wrapped(event); } diff --git a/scripts/module.js b/scripts/module.js index cf9de81..68adb3c 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -54,8 +54,6 @@ import { benchPathfinding } from "./pathfinding/benchmark.js"; import { SCENE_GRAPH, WallTracer, WallTracerEdge, WallTracerVertex } from "./pathfinding/WallTracer.js"; Hooks.once("init", function() { - // Cannot access localization until init. - PREFER_TOKEN_CONTROL.title = game.i18n.localize(PREFER_TOKEN_CONTROL.title); registerGeometry(); // Configuration @@ -169,14 +167,6 @@ Hooks.once("devModeReady", ({ registerPackageDebugFlag }) => { }); -// Add Token lock button to token controls to use token elevation when using the ruler. -const PREFER_TOKEN_CONTROL = { - name: Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION, - title: `${MODULE_ID}.controls.${Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION}.name`, - icon: "fa-solid fa-user-lock", - toggle: true -}; - // Add pathfinding button to token controls. const PATHFINDING_CONTROL = { name: Settings.KEYS.CONTROLS.PATHFINDING, @@ -191,11 +181,9 @@ Hooks.on("getSceneControlButtons", controls => { if ( !canvas.scene ) return; const tokenTools = controls.find(c => c.name === "token"); tokenTools.tools.push(PATHFINDING_CONTROL); - if ( Settings.get(Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION) ) tokenTools.tools.push(PREFER_TOKEN_CONTROL); }); Hooks.on("canvasInit", function(_canvas) { - updatePreferTokenControl(); updatePathfindingControl(); ui.controls.render(true); }); @@ -204,26 +192,10 @@ Hooks.on("renderSceneControls", async function(controls, _html, _data) { // Monitor enabling/disabling of custom controls. if ( controls.activeControl !== "token" ) return; - if ( Settings.get(Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION) ) { - const toggle = controls.control.tools.find(t => t.name === Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION); - // Should always find a toggle, but... - if ( toggle ) await Settings.set(Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION_CURRENT_VALUE, toggle.active); - } - const toggle = controls.control.tools.find(t => t.name === Settings.KEYS.CONTROLS.PATHFINDING); if ( toggle ) await Settings.set(Settings.KEYS.CONTROLS.PATHFINDING, toggle.active); }); -function updatePreferTokenControl(enable) { - enable ??= Settings.get(Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION); - const tokenTools = ui.controls.controls.find(c => c.name === "token"); - const index = tokenTools.tools.findIndex(b => b.name === Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION); - if ( enable && !~index ) tokenTools.tools.push(PREFER_TOKEN_CONTROL); - else if ( ~index ) tokenTools.tools.splice(index, 1); - PREFER_TOKEN_CONTROL.active = Settings.get(Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION_CURRENT_VALUE); - // Do in the hook instead to avoid repetition: ui.controls.render(true); -} - function updatePathfindingControl(enable) { enable ??= Settings.get(Settings.KEYS.CONTROLS.PATHFINDING); const tokenTools = ui.controls.controls.find(c => c.name === "token"); diff --git a/scripts/settings.js b/scripts/settings.js index 9f545ff..e3ce0f1 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -16,9 +16,7 @@ import { BorderEdge } from "./pathfinding/BorderTriangle.js"; const SETTINGS = { CONTROLS: { - PATHFINDING: "pathfinding-control", - PREFER_TOKEN_ELEVATION: "prefer-token-elevation", - PREFER_TOKEN_ELEVATION_CURRENT_VALUE: "prefer-token-elevation-current-value" + PATHFINDING: "pathfinding-control" }, PATHFINDING: { @@ -65,7 +63,8 @@ const KEYBINDINGS = { ADD_WAYPOINT: "addWaypointTokenRuler", REMOVE_WAYPOINT: "removeWaypointTokenRuler" }, - TOGGLE_PATHFINDING: "togglePathfinding" + TOGGLE_PATHFINDING: "togglePathfinding", + FORCE_TO_GROUND: "forceToGround" }; @@ -76,8 +75,12 @@ export class Settings extends ModuleSettingsAbstract { /** @type {object} */ static KEYBINDINGS = KEYBINDINGS; + /** @type {boolean} */ static FORCE_TOGGLE_PATHFINDING = false; + /** @type {boolean} */ + static FORCE_TO_GROUND = false; + /** * Register all settings */ @@ -98,25 +101,6 @@ export class Settings extends ModuleSettingsAbstract { } }); - register(KEYS.CONTROLS.PREFER_TOKEN_ELEVATION, { - name: localize(`${KEYS.CONTROLS.PREFER_TOKEN_ELEVATION}.name`), - hint: localize(`${KEYS.CONTROLS.PREFER_TOKEN_ELEVATION}.hint`), - scope: "user", - config: true, - default: false, - type: Boolean, - requiresReload: false, - onChange: reloadTokenControls - }); - - register(KEYS.CONTROLS.PREFER_TOKEN_ELEVATION_CURRENT_VALUE, { - scope: "user", - config: false, - default: false, - type: Boolean, - requiresReload: false - }); - // ----- NOTE: Pathfinding ----- // register(KEYS.CONTROLS.PATHFINDING, { @@ -296,6 +280,22 @@ export class Settings extends ModuleSettingsAbstract { }, precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL }); + + game.keybindings.register(MODULE_ID, KEYBINDINGS.FORCE_TO_GROUND, { + name: game.i18n.localize(`${MODULE_ID}.keybindings.${KEYBINDINGS.FORCE_TO_GROUND}.name`), + hint: game.i18n.localize(`${MODULE_ID}.keybindings.${KEYBINDINGS.FORCE_TO_GROUND}.hint`), + editable: [ + { key: "KeyG" } + ], + onDown: _context => { + const ruler = canvas.controls.ruler; + if ( !ruler.active ) return; + this.FORCE_TO_GROUND = !this.FORCE_TO_GROUND; + ruler.measure(ruler.destination, { force: true }); + ui.notifications.info(`Ruler measure to ground ${this.FORCE_TO_GROUND ? "enabled" : "disabled"}.`); + }, + precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL + }); } static toggleTokenBlocksPathfinding(blockSetting) { diff --git a/scripts/terrain_elevation.js b/scripts/terrain_elevation.js index 6df1e21..23cf674 100644 --- a/scripts/terrain_elevation.js +++ b/scripts/terrain_elevation.js @@ -42,7 +42,7 @@ elevationAtLocation -- all ruler types Used by ruler to get elevation at waypoints and at the end of the ruler. */ -import { MODULES_ACTIVE } from "./const.js"; +import { MODULES_ACTIVE, MOVEMENT_TYPES } from "./const.js"; import { Settings } from "./settings.js"; import { Point3d } from "./geometry/3d/Point3d.js"; @@ -114,23 +114,50 @@ export function elevationAtLocation(location, token) { token ??= this._getMovementToken(); const isTokenRuler = Settings.get(Settings.KEYS.TOKEN_RULER.ENABLED) && ui.controls.activeControl === "token" - && ui.controls.activeTool === "select"; + && ui.controls.activeTool === "select" + && token; + + // 1. If forcing to ground, always use the terrain elevation. + + /* If not forcing to ground: + 2. No move token + --> Default: Use origin elevation + --> If hovering over a token, use that token's elevation + 3. Move token, normal ruler + --> Default: Use move token elevation + --> If hovering over a token, use that token's elevation + 4. Token ruler + --> Default: Use move token elevation + --> If token is walking, use terrain elevation + */ + const terrainElevationFn = () => this.constructor.terrainElevationAtLocation(location, { + movementToken: token, + startingElevation: this.originElevation + }); - // If at the token, use the token's elevation. - if ( token && location.almostEqual(token.center) ) return token.elevationE; - - // If normal ruler and not prioritizing the token elevation, use elevation of other tokens at this point. - if ( !isTokenRuler && !preferTokenElevation() ) { - const maxTokenE = retrieveVisibleTokens() - .filter(t => t.constrainedTokenBorder.contains(location.x, location.y)) - .reduce((e, t) => Math.max(t.elevationE, e), Number.NEGATIVE_INFINITY); - if ( isFinite(maxTokenE) ) return maxTokenE; + // #1 Forcing to ground + if ( Settings.FORCE_TO_GROUND ) return terrainElevationFn(); + + // #4 token ruler + if ( isTokenRuler ) { + if ( token.movementType === MOVEMENT_TYPES.WALK ) return terrainElevationFn(); + return token.elevationE; } - // Use the terrain at this point. - return this.constructor.terrainElevationAtLocation(location, { - movementToken: token, - startingElevation: this.originElevation }); + // If at the token, use the token's elevation. + // if ( token && location.almostEqual(token.center) ) return token.elevationE; + + // Check for other tokens at destination and use that elevation. + const maxTokenE = retrieveVisibleTokens() + .filter(t => t.constrainedTokenBorder.contains(location.x, location.y)) + .reduce((e, t) => Math.max(t.elevationE, e), Number.NEGATIVE_INFINITY); + if ( isFinite(maxTokenE) ) return maxTokenE; // #2 or #3 + + // #3 move token + if ( token ) return token.elevationE; + + // #2 no move token + return this.originElevation; } // ----- NOTE: HELPER FUNCTIONS ----- // @@ -139,18 +166,6 @@ function retrieveVisibleTokens() { return canvas.tokens.children[0].children.filter(c => c.visible); } -/** - * Determine if token elevation should be preferred - * @returns {boolean} - */ -function preferTokenElevation() { - const PREFER_TOKEN_ELEVATION = Settings.KEYS.CONTROLS.PREFER_TOKEN_ELEVATION; - if ( !Settings.get(PREFER_TOKEN_ELEVATION) ) return false; - const token_controls = ui.controls.controls.find(elem => elem.name === "token"); - const prefer_token_control = token_controls.tools.find(elem => elem.name === PREFER_TOKEN_ELEVATION); - return prefer_token_control.active; -} - // ----- NOTE: ELEVATED VISION ELEVATION ----- // /** * Measure the terrain elevation at a given point using Elevated Vision.