Skip to content

Commit

Permalink
Merge branch 'release/0.8.11' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
caewok committed Apr 5, 2024
2 parents f353a47 + dad2c91 commit 9557d1c
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 89 deletions.
9 changes: 9 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# 0.8.11

## New features
Add a keybinding ("g") to force the ruler to measure from ground terrain. Replaces "Prefer Token Elevation," which was removed. Closes #63, #64.

## Bug fixes
Catch when a segment color is not defined, to avoid throwing an error.
Fix for incorrect combat speed movement highlighting after the first move. Closes #62.

# 0.8.10
## New features
Add indicator of past combat movement in the ruler.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Finally, the DnD Euclidean rule relies on Pythagorean's Theorem, rounded to the

## Token measurement

If you drag the ruler over a token that has been elevated or lowered, the ruler will reflect the elevation of that token (plus or minus manually incremented values). (This does not happen if you are dragging tokens; you must use the ruler tool.)
When measuring, the ruler will stay at the origin elevation (or originating token elevation) unless manually changed. But if you drag the ruler over a token that has been elevated or lowered, the ruler will reflect the elevation of that token (plus or minus manually incremented values). (This does not happen if you are dragging tokens; you must use the ruler tool.)

This is particularly useful where you have an elevated character at the origin, and want to fire or move downwards. Or vice-versa where you are aiming at an elevated token and need total distance to the elevated target.

Expand All @@ -102,6 +102,7 @@ Elevation Ruler defines certain keybindings:
- Add Token Ruler Waypoint (`=`): When dragging tokens, add a waypoint.
- Remove Token Ruler Waypoint (`-`): When dragging tokens, remove a waypoint.
- Temporarily Toggle Pathfinding (`p`): If pathfinding is enabled, temporarily disable while holding this key. If disabled, then temporarily enable it.
- Force to Ground (`g`): If you hit `g` while using the ruler, it will move the destination to use the ground elevation. You can hit `g` again to revert back. Note that the decrement and increment elevation keybindings will still change elevation accordingly. You can use this keybinding when dragging a flying token that you want to "land." Or if you are measuring with the ruler and want the measurement to not accoutn for another token's elevation at a destination point.

# Settings

Expand Down
3 changes: 3 additions & 0 deletions languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
28 changes: 22 additions & 6 deletions scripts/Ruler.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,14 +396,11 @@ function _computeTokenSpeed() {
let totalDistance = 0;
let totalMoveDistance = 0;
let totalCombatMoveDistance = 0;
let minDistance = 0;
let numPrevDiagonal = 0;
let s = 0;
let segment;

// Add in already moved combat distance.
if ( game.combat?.started
&& Settings.get(Settings.KEYS.TOKEN_RULER.COMBAT_HISTORY) ) totalCombatMoveDistance = token.lastMoveDistance;

// Progress through each speed attribute in turn.
const categoryIter = [...SPEED.CATEGORIES, MaximumSpeedCategory].values();
const maxDistFn = (token, speedCategory, tokenSpeed) => {
Expand All @@ -414,7 +411,23 @@ function _computeTokenSpeed() {
let speedCategory = categoryIter.next().value;
let maxDistance = maxDistFn(token, speedCategory, tokenSpeed);

// Determine which speed category we are starting with
// Add in already moved combat distance and determine the starting category
if ( game.combat?.started
&& Settings.get(Settings.KEYS.TOKEN_RULER.COMBAT_HISTORY) ) {

totalCombatMoveDistance = token.lastMoveDistance;
minDistance = totalCombatMoveDistance;
}


while ( (segment = this.segments[s]) ) {
// Skip speed categories that do not provide a distance larger than the last.
while ( speedCategory.name !== "Maximum" && maxDistance <= minDistance ) {
speedCategory = categoryIter.next().value;
maxDistance = maxDistFn(token, speedCategory, tokenSpeed);
}

segment.speed = speedCategory;
let newPrevDiagonal = _measureSegment(segment, token, numPrevDiagonal);

Expand All @@ -436,8 +449,8 @@ function _computeTokenSpeed() {
}

// Increment to the next speed category.
speedCategory = categoryIter.next().value;
maxDistance = maxDistFn(token, speedCategory, tokenSpeed);
// Next category will be selected in the while loop above: first category to exceed minDistance.
minDistance = maxDistance;
}

// Increment totals.
Expand Down Expand Up @@ -538,10 +551,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);
}
Expand Down
8 changes: 5 additions & 3 deletions scripts/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,13 @@ SPEED.maximumCategoryDistance = function(token, speedCategory, tokenSpeed) {
/**
* Given a token, retrieve its base speed.
* @param {Token} token Token whose speed is required
* @returns {number} Distance, in grid units
* @returns {number|null} Distance, in grid units. Null if no speed provided for that category.
* (Null will disable speed highlighting.)
*/
SPEED.tokenSpeed = function(token) {
const speedAttribute = SPEED.ATTRIBUTES[token.movementType] ?? SPEED.ATTRIBUTES.WALK;
return Number(foundry.utils.getProperty(token, speedAttribute));
const speed = foundry.utils.getProperty(token, SPEED.ATTRIBUTES[token.movementType]);
if ( speed === null ) return null;
return Number(speed);
};

// Avoid testing for the system id each time.
Expand Down
28 changes: 0 additions & 28 deletions scripts/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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);
});
Expand All @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion scripts/segments.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ export function _highlightMeasurementSegment(wrapped, segment) {
const token = this._getMovementToken();
const doSpeedHighlighting = token
// && this.user === game.user
&& Settings.get(Settings.KEYS.TOKEN_RULER.SPEED_HIGHLIGHTING);
&& Settings.get(Settings.KEYS.TOKEN_RULER.SPEED_HIGHLIGHTING)
&& segment.speed?.color;

// Highlight each split in turn, changing highlight color each time.
if ( doSpeedHighlighting ) this.color = segment.speed.color;
Expand Down
46 changes: 23 additions & 23 deletions scripts/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -65,7 +63,8 @@ const KEYBINDINGS = {
ADD_WAYPOINT: "addWaypointTokenRuler",
REMOVE_WAYPOINT: "removeWaypointTokenRuler"
},
TOGGLE_PATHFINDING: "togglePathfinding"
TOGGLE_PATHFINDING: "togglePathfinding",
FORCE_TO_GROUND: "forceToGround"
};


Expand All @@ -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
*/
Expand All @@ -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, {
Expand Down Expand Up @@ -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) {
Expand Down
69 changes: 42 additions & 27 deletions scripts/terrain_elevation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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 ----- //
Expand All @@ -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.
Expand Down

0 comments on commit 9557d1c

Please sign in to comment.