Skip to content

Commit

Permalink
Merge branch 'release/0.6.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
caewok committed Apr 10, 2023
2 parents fde38fc + 698632c commit 0ee5ce8
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 58 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 0.6.7
- Fix measuring elevation with Elevated Vision enabled (issue #18).
- No longer require reload of the canvas when enabling/disabling prefer token control.
- Prefer token control now remembers its current setting (enabled/disabled) when switching back-and-forth between layers

# 0.6.6
Update geometry lib to v0.1.5. Fix for incorrect diagonal measurement in grids (issue #3). Issue with dnd5e may still result in questionable rounded values when measuring Euclidean distances and 5/10/5 measurements on hex maps. See https://github.com/foundryvtt/dnd5e/issues/2257 and https://github.com/foundryvtt/dnd5e/issues/2256.
Fix for updating ruler elevation label on gridless maps when increasing or decreasing elevation.
Expand Down
35 changes: 27 additions & 8 deletions scripts/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,39 @@ Hooks.once("libWrapper.Ready", async function() {
registerRuler();
});

Hooks.on("getSceneControlButtons", controls => {
if ( !getSetting(SETTINGS.PREFER_TOKEN_ELEVATION) ) return;
const PREFER_TOKEN_CONTROL = {
name: SETTINGS.PREFER_TOKEN_ELEVATION,
title: `${MODULE_ID}.controls.${SETTINGS.PREFER_TOKEN_ELEVATION}.name`,
icon: "fa-solid fa-user-lock",
toggle: true
};

Hooks.once("init", function() {
// Cannot access localization until init.
PREFER_TOKEN_CONTROL.title = game.i18n.localize(PREFER_TOKEN_CONTROL.title);
});

// Render the prefer token control if that setting is enabled
Hooks.on("getSceneControlButtons", controls => {
if ( !canvas.scene || !getSetting(SETTINGS.PREFER_TOKEN_ELEVATION) ) return;
const tokenTools = controls.find(c => c.name === "token");
tokenTools.tools.push({
name: SETTINGS.PREFER_TOKEN_ELEVATION,
title: game.i18n.localize(`${MODULE_ID}.controls.${SETTINGS.PREFER_TOKEN_ELEVATION}.name`),
icon: "fa-solid fa-user-lock",
toggle: true
});
tokenTools.tools.push(PREFER_TOKEN_CONTROL);
});

Hooks.on("dragRuler.ready", function() {
registerDragRuler();
});

Hooks.on("canvasInit", function(_canvas) {
updatePreferTokenControl();
})

function updatePreferTokenControl(enable) {
enable ??= getSetting(SETTINGS.PREFER_TOKEN_ELEVATION);
const tokenTools = ui.controls.controls.find(c => c.name === "token");
const index = tokenTools.tools.findIndex(b => b.name === SETTINGS.PREFER_TOKEN_CONTROL);
if ( enable && !~index ) tokenTools.tools.push(PREFER_TOKEN_CONTROL);
else if ( ~index ) tokenTools.tools.splice(index, 1);
ui.controls.render(true);
}

2 changes: 1 addition & 1 deletion scripts/segments.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function elevateSegments(ruler, segments) { // Add destination as the final way
* @param {object} waypoint
* @returns {number}
*/
function elevationAtWaypoint(waypoint) {
export function elevationAtWaypoint(waypoint) {
return waypoint._terrainElevation + (waypoint._userElevationIncrements * canvas.dimensions.distance);
}

Expand Down
13 changes: 12 additions & 1 deletion scripts/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export function registerSettings() {
config: true,
default: false,
type: Boolean,
requiresReload: true
requiresReload: false,
onChange: reloadTokenControls
});

log("Done registering settings.");
Expand All @@ -125,3 +126,13 @@ export function registerKeybindings() {
precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL
});
}

/**
* Force a reload of token controls layer.
* Used to force the added control to appear/disappear.
*/
function reloadTokenControls() {
if ( !canvas.tokens.active ) return;
canvas.tokens.deactivate();
canvas.tokens.activate();
}
119 changes: 71 additions & 48 deletions scripts/terrain_elevation.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* globals
canvas,
game,
CONFIG,
ui,
PIXI
*/
Expand All @@ -14,29 +13,22 @@ Used by ruler to get elevation at waypoints and at the end of the ruler.
import { MODULE_ID } from "./const.js";
import { log } from "./util.js";
import { SETTINGS, getSetting } from "./settings.js";
import { elevationAtWaypoint } from "./segments.js";

/**
* Retrieve the elevation at the current ruler origin.
* This is either the measuring token elevation or terrain elevation or 0.
* Cached during a ruler movement
*/
export function elevationAtOrigin() {
const measuringToken = this._getMovementToken();
const origin = this.waypoints[0];
if ( !origin ) return undefined;
if ( typeof origin._terrainElevation !== "undefined" ) return origin._terrainElevation;

let value = 0;

// Use the measuring token's elevation, if any, as the starting point.
let value = Number.NEGATIVE_INFINITY;
const measuringToken = this._getMovementToken();
if ( measuringToken ) value = tokenElevation(measuringToken);

// If the Levels UI is enabled, start at the bottom of the current layer.
else if ( useLevels() && CONFIG.Levels.UI.rangeEnabled ) value = parseFloat(CONFIG.Levels.UI.range[0]);

// Otherwise, get the elevation for this origin point.
// Pass startingElevation to avoid a loop.
else value = this.terrainElevationAtPoint(origin, { considerTokens: false, startingElevation: 0 });
else value = elevationAtLocation(origin, measuringToken);

origin._terrainElevation = value;
origin._userElevationIncrements = 0;
Expand Down Expand Up @@ -74,6 +66,56 @@ export function terrainElevationAtDestination({ considerTokens = true } = {}) {
return this.terrainElevationAtPoint(this.destination, { considerTokens });
}

/**
* Measure elevation for a given rule position
* Try the following, in order:
* 1. If measuring token, the measuring token elevation.
* 2. If currently selected token && EV, consider tiles
* 3. If currently selected token && Levels, current layer
* 4. If Levels UI, current layer
* 5. If enhanced terrain layer, terrain layer
* 5. If EV, point measure
* 6. 0
* @param {Point} location
* @param {Token} [measuringToken]
* @param {number} [startingElevation=Number.NEGATIVE_INFINITY]
* @returns {number}
*/
function elevationAtLocation(location, measuringToken, startingElevation = Number.NEGATIVE_INFINITY) {
const ignoreBelow = (measuringToken && preferTokenElevation()) ? startingElevation : Number.NEGATIVE_INFINITY;
log(`Checking Elevation at (${location.x}, ${location.y})\n\tstarting elevation ${startingElevation}\n\tignoring below ${ignoreBelow}`);

// If at the measuring token, use that
location = new PIXI.Point(location.x, location.y);
if ( measuringToken && location.almostEqual(measuringToken.center) ) return measuringToken.document?.elevation ?? 0;

// Prioritize the highest token at the location
const max_token_elevation = retrieveVisibleTokens().reduce((e, t) => {
// Is the point within the token control area?
if ( !t.bounds.contains(location.x, location.y) ) return e;
return Math.max(tokenElevation(t), e);
}, Number.NEGATIVE_INFINITY);
if ( isFinite(max_token_elevation) && max_token_elevation >= ignoreBelow ) return max_token_elevation;

// Try Enhanced Terrain Layer
// Terrain layers trumps all others
const terrain_elevation = TerrainLayerElevationAtPoint(location);
if ( terrain_elevation !== undefined && terrain_elevation > ignoreBelow ) return terrain_elevation;

// Try Elevated Vision
// If EV is present, it should handle Levels elevation as well
const ev_elevation = EVElevationAtPoint(location, measuringToken, startingElevation);
if ( ev_elevation !== undefined && ev_elevation > ignoreBelow ) return ev_elevation;

// Try Levels
const levels_elevation = LevelsElevationAtPoint(location, { startingElevation });
if ( levels_elevation !== undefined && levels_elevation > ignoreBelow ) return levels_elevation;

// Default to 0 elevation for the point
return Math.max(ignoreBelow, 0);
}


/**
* Measure elevation at a given point.
* Prioritize:
Expand All @@ -87,42 +129,13 @@ export function terrainElevationAtDestination({ considerTokens = true } = {}) {
* @param {boolean} [options.considerTokens] Consider token elevations at that point.
* @returns {number} Elevation for the given point.
*/
export function terrainElevationAtPoint(p, {
considerTokens = true,
startingElevation = this.elevationAtOrigin() } = {}) {

export function terrainElevationAtPoint(p, { startingElevation } = {}) {
const measuringToken = this._getMovementToken();
const ignoreBelow = ( measuringToken && preferTokenElevation() ) ? startingElevation : Number.NEGATIVE_INFINITY;

log(`Checking Elevation at (${p.x}, ${p.y}) ${considerTokens ? "" : "not "} considering tokens\n\tstarting elevation ${startingElevation}\n\tignoring below ${ignoreBelow}`);

if ( considerTokens ) { // Check for tokens; take the highest one at a given position
const tokens = retrieveVisibleTokens();
const max_token_elevation = tokens.reduce((e, t) => {
// Is the point within the token control area?
if ( !t.bounds.contains(p.x, p.y) ) return e;
return Math.max(tokenElevation(t), e);
}, Number.NEGATIVE_INFINITY);
log(`calculateEndElevation: ${tokens.length} tokens at (${p.x}, ${p.y}) with maximum elevation ${max_token_elevation}`);
startingElevation ??= this.waypoints.length
? elevationAtWaypoint(this.waypoints[this.waypoints.length - 1]) : measuringToken
? tokenElevation(measuringToken) : Number.NEGATIVE_INFINITY;

// Use tokens rather than elevation if available
if ( isFinite(max_token_elevation) && max_token_elevation >= ignoreBelow ) return max_token_elevation;
}

// Try Levels
const levels_elevation = LevelsElevationAtPoint(p, { startingElevation });
if ( levels_elevation !== undefined && levels_elevation > ignoreBelow ) return levels_elevation;

// Try Elevated Vision
const ev_elevation = EVElevationAtPoint(p);
if ( ev_elevation !== undefined && ev_elevation > ignoreBelow ) return ev_elevation;

// Try Enhanced Terrain Layer
const terrain_elevation = TerrainLayerElevationAtPoint(p);
if ( terrain_elevation !== undefined && terrain_elevation > ignoreBelow ) return terrain_elevation;

// Default to 0 elevation for the point
return Math.max(ignoreBelow, 0);
return elevationAtLocation(p, measuringToken, startingElevation);
}

function retrieveVisibleTokens() {
Expand Down Expand Up @@ -163,9 +176,19 @@ function useLevels() {
* @param {Point} {x,y} Point to measure, in {x, y} format
* @returns {Number|undefined} Point elevation or undefined if elevated vision layer is inactive
*/
function EVElevationAtPoint({x, y}) {
function EVElevationAtPoint(location, measuringToken, startingElevation = 0) {
if ( !useElevatedVision() ) return undefined;
return canvas.elevation.elevationAt(x, y);

const EVCalc = measuringToken
? new canvas.elevation.TokenElevationCalculator(measuringToken)
: new canvas.elevation.CoordinateElevationCalculator(location);

// Location may or may not be correct, depending on above.
// Use positive infinity for elevation so that all tiles can be found
EVCalc.location = location;
EVCalc.elevation = isFinite(startingElevation) ? startingElevation : Number.POSITIVE_INFINITY;

return EVCalc.groundElevation();
}

// ----- TERRAIN LAYER ELEVATION ----- //
Expand Down

0 comments on commit 0ee5ce8

Please sign in to comment.