Skip to content

Commit

Permalink
Merge branch 'release/0.10.12' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
caewok committed Oct 2, 2024
2 parents cdb5737 + e00f49a commit d0591b1
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 33 deletions.
6 changes: 6 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.10.12
Fixes for calculating movement penalty in drawings.
Avoid displaying the default ruler label in some situations when using the custom label. Closes issue #212.
Fix for PF2e speed not displaying correctly. Closes issue #211.
Fix for user seeing the GM ruler when moving hidden tokens. Closes issue #207.

# 0.10.11
When snapping pathfinding to grid, avoid two waypoints within the same space when snapping is possible.
Fix for ray undefined in cost measurement.
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ Elevation can be changed while using the ruler (default: '[' to increment and ']
When dragging a token, the ruler will automatically appear if you have enabled "Token Ruler" in the module settings. To set waypoints when dragging a token, hit the specified hotkey (default '=' to add and '-' to remove). To adjust the colors that appear for designated speeds see [Setting speed colors](#setting-speed-colors). If you would like specific system support, please feel free to submit a git issue.

## Movement speed / history
Elevation Ruler supports coloring the ruler highlighting to represent token speeds based on various systems. The user can also control whether the speed for walking, burrowing, or flying should be used by changing the setting on the token hud. Settings determine, on a per-user basis, whether the speed highlighting should always be used, only during combat, or never. The GM can also from users the speeds of hostile tokens. Movement history during combat can also be tracked.
Elevation Ruler supports coloring the ruler highlighting to represent token speeds based on various systems. The user can also control whether the speed for walking, burrowing, or flying should be used by changing the setting on the token hud. Settings determine, on a per-user basis, whether the speed highlighting should always be used, only during combat, or never. The GM can also from users the speeds of hostile tokens. Movement history during combat can also be tracked.

## Pathfinding
[Screen Recording 2024-06-04 at 4.26.31 PM.webm](https://github.com/caewok/fvtt-elevation-ruler/assets/1267134/1efca6e2-41b1-4cdb-87a3-2fb5e48be5cd)

As of version 0.8.0, a button in the Token controls enables pathfinding for the ruler (or Token Ruler, when enabled). Pathfinding works on gridded (both hex and square) and gridless maps. If using the ruler, start a measurement at a token in order to start pathfinding.
As of version 0.8.0, a button in the Token controls enables pathfinding for the ruler (or Token Ruler, when enabled). Pathfinding works on gridded (both hex and square) and gridless maps. If using the ruler, start a measurement at a token in order to start pathfinding.

Settings allow you to designate all tokens or hostile tokens as spaces to be avoided.
Settings allow you to designate all tokens or hostile tokens as spaces to be avoided.

To enable/disable pathfinding, toggle the pathfinding icon in the token controls (upper left controls in Foundry). You can also hold the specified hotkey (default 'P').
To enable/disable pathfinding, toggle the pathfinding icon in the token controls (upper left controls in Foundry). You can also hold the specified hotkey (default 'P').

## Module history
As of v0.7, Elevation Ruler adds a setting to display the Foundry ruler when dragging tokens.
Expand All @@ -53,14 +53,15 @@ Add this [Manifest URL](https://github.com/caewok/fvtt-elevation-ruler/releases/
## Modules that add functionality
- [Wall Height](https://github.com/erithtotl/FVTT-Wall-Height). For defining limited-height walls.
- [Terrain Mapper](https://github.com/caewok/fvtt-terrain-mapper). In v12, use Terrain Mapper to define elevation for regions.
- [PF2E Elevation Ruler](https://github.com/7H3LaughingMan/pf2e-elevation-ruler). Defines token speed colors for the PF2e system.
- [TDE5/DSA5 Elevation Ruler](https://github.com/Rapunzel77/dsa5-elevation-ruler-integration). Defines token speeds and colors for Das Schwarze Auge (TDE/DSA)
- [Lancer Ruler Integration](https://foundryvtt.com/packages/lancer-speed-provider). Defines token speeds and colors and handles speed changes based on conditions for LANCER

## Known conflicts
- [Terrain Ruler](https://github.com/manuelVo/foundryvtt-terrain-ruler)
- [Enhanced Terrain Layer](https://github.com/ironmonk88/enhanced-terrain-layer)
- [Drag Ruler](https://github.com/manuelVo/foundryvtt-drag-ruler). Elevation ruler v0.6 series worked with Drag Ruler, but v0.7+ no longer supports Drag Ruler.
- [Dynamic Active Effects](https://gitlab.com/tposney/dae). DAE interferes with the active effects used by Terrain Mapper from being calculated for movement penalties in Elevation Ruler. The effect appears to be applied but the movement penalty when moving over the region will not be calculated correctly. Elevation Ruler should otherwise work with DAE.
- Pathfinder 2e system. The movement penalty calculation for Terrain Mapper regions may not be correctly calculated because PF2e does not use the Foundry active effects system (and current workarounds are limited). Other speed calculations for tokens should work, and Elevation Ruler should otherwise work with PF2e.

In general, modules that overwrite or extend the Ruler Class may cause the elevation ruler module to fail to display or calculate correctly.

Expand Down Expand Up @@ -195,7 +196,7 @@ const MaximumSpeedCategory = {
multiplier: Number.POSITIVE_INFINITY
};
```
Categories are processed in order in the `SPEED.CATEGORIES` array. Usually (unless you modify the `SPEED.maximumCategoryDistance` function per below) you would want the categories sorted from smallest to largest multiplier. For example, a token with speed 30 could walk for 30 * 1 grid units, and dash for 30 * 2 = 60 grid units. So the first 30 grid units would be highlighted for walk, the next 30 highlighted for dash, and everything beyond that highlighted with the maximum color.
Categories are processed in order in the `SPEED.CATEGORIES` array. Usually (unless you modify the `SPEED.maximumCategoryDistance` function per below) you would want the categories sorted from smallest to largest multiplier. For example, a token with speed 30 could walk for 30 * 1 grid units, and dash for 30 * 2 = 60 grid units. So the first 30 grid units would be highlighted for walk, the next 30 highlighted for dash, and everything beyond that highlighted with the maximum color.

If you have a specific system that you would like supported by default, please open a Git issue and explain how the system measures speed and, preferably, what properties need to be changed.

Expand Down
2 changes: 1 addition & 1 deletion scripts/Ruler.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ function _broadcastMeasurement(wrapped) {
&& this.token
&& (this.token.document.disposition === CONST.TOKEN_DISPOSITIONS.SECRET
|| this.token.document.hasStatusEffect(CONFIG.specialStatusEffects.INVISIBLE)
|| this.token.document.isHidden) ) return;
|| this.token.document.hidden) ) return;

wrapped();
}
Expand Down
22 changes: 18 additions & 4 deletions scripts/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,27 @@ function keyForValue(object, value) {

/**
* Given a token, retrieve its base speed.
* @param {Token} token Token whose speed is required
* @param {Token} token Token whose speed is required
* @param {MOVEMENT_TYPES} [movementType] Type of movement; if omitted automatically determined
* @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 moveType = token.movementType;
const speed = foundry.utils.getProperty(token, SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, moveType)]);
SPEED.tokenSpeed = function(token, movementType) {
movementType ??= token.movementType;
const speed = foundry.utils.getProperty(token, SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, movementType)]);
if ( speed === null ) return null;
return Number(speed);
};

/**
* Temporarily set the speed attribute for a token. Only sets locally; does not hit the database.
* @param {number} value Speed to set
* @param {Token} token Token on which to set the speed property
* @param {MOVEMENT_TYPES} [movementType] Type of movement; if omitted automatically determined
*/
SPEED.setTokenSpeed = function(value, token, movementType) {
movementType ??= token.movementType;
const key = SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, movementType)];
if ( !key ) return
foundry.utils.setProperty(token, key, value);
}
2 changes: 1 addition & 1 deletion scripts/geometry
52 changes: 40 additions & 12 deletions scripts/measurement/MovePenalty.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* globals
canvas,
CONFIG,
foundry,
game,
PIXI
*/
Expand All @@ -11,7 +10,7 @@ PIXI
import { MODULE_ID, FLAGS, OTHER_MODULES, SPEED, MOVEMENT_TYPES } from "../const.js";
import { Settings } from "../settings.js";
import { movementType } from "../token_hud.js";
import { log, keyForValue } from "../util.js";
import { log } from "../util.js";

/*
Class to measure penalty, as percentage of distance, between two points.
Expand Down Expand Up @@ -57,15 +56,15 @@ export class MovePenalty {
*/
#localTokenClone;

/** @type {string} */
speedAttribute = "";
/** @type {MOVEMENT_TYPES} */
movementType = MOVEMENT_TYPES.WALK;

/**
* @param {Token} moveToken The token doing the movement
*/
constructor(moveToken) {
this.moveToken = moveToken;
this.speedAttribute = SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, moveToken.movementType)];
this.movementType = moveToken.movementType;
const tokenMultiplier = this.constructor.tokenMultiplier;
const terrainAPI = this.constructor.terrainAPI;

Expand Down Expand Up @@ -132,7 +131,8 @@ export class MovePenalty {
* @returns {object}
*/
static _constructTokenClone(token) {
const actor = new CONFIG.Actor.documentClass(token.actor.toObject(), {});
// Alternative to clone(): const actor = new CONFIG.Actor.documentClass(token.actor.toObject(), {});
const actor = token.actor.clone();
const document = new CONFIG.Token.documentClass(token.document.toObject());
const tClone = { document, actor, _original: token };

Expand Down Expand Up @@ -459,6 +459,8 @@ export class MovePenalty {
ix = nextIx;
}

// console.debug(`_penaltiesForIntersections|${start.x},${start.y},${start.z} -> ${end.x},${end.y},${end.z}`, calcSteps, cutawayIxs);

// Make sure the token clone speed is reset.
if ( testRegions ) speedFn();

Expand Down Expand Up @@ -507,9 +509,9 @@ export class MovePenalty {
}

/** @type {number} */
get _tokenCloneSpeed() { return foundry.utils.getProperty(this.#localTokenClone, this.speedAttribute) || 1; }
get _tokenCloneSpeed() { return SPEED.tokenSpeed(this.#localTokenClone, this.movementType) || 1; }

set _tokenCloneSpeed(value) { foundry.utils.setProperty(this.#localTokenClone, this.speedAttribute, value); }
set _tokenCloneSpeed(value) { SPEED.setTokenSpeed(value, this.#localTokenClone, this.movementType); }

/**
* Set up the token clone for measurement and return a function that can get the token speed.
Expand Down Expand Up @@ -556,8 +558,21 @@ export class MovePenalty {
const centeredShape = CONFIG.GeometryLib.utils.centeredPolygonFromDrawing(drawing);

// Multiple cutaways are possible for polygons.
const cutaways = centeredShape.cutaway(start, end, { bottomElevationFn, topElevationFn });
return cutaways.flatMap(cutaway => cutaway.intersectSegment3d(start, end));
// Use the full drawing shape b/c we need to test for actual intersections with the shape.
// Can get by extending the start and end points to the canvas edge.
const dist = -canvas.dimensions.maxR;
const a = start.towardsPoint(end, dist);
const b = end.towardsPoint(start, dist);
const cutaways = centeredShape.cutaway(a, b, { start, end, bottomElevationFn, topElevationFn });
return cutaways.flatMap(cutaway => {
const ixs = cutaway.intersectSegment3d(start, end);
if ( cutaway.contains3d(start) ) {
const pt = cutaway._to2d(start);
pt.movingInto = true;
ixs.push(pt);
}
return ixs;
});
}

/**
Expand All @@ -573,8 +588,21 @@ export class MovePenalty {
const topElevationFn = () => token.topZ;

// Multiple cutaways are possible if the token is constrained (e.g., inset edge).
const cutaways = token.constrainedTokenBorder.cutaway(start, end, { bottomElevationFn, topElevationFn });
return cutaways.flatMap(cutaway => cutaway.intersectSegment3d(start, end));
// Use the full token shape b/c we need to test for actual intersections with the shape.
// Can get by extending the start and end points to the canvas edge.
const dist = -canvas.dimensions.maxR;
const a = start.towardsPoint(end, dist);
const b = end.towardsPoint(start, dist);
const cutaways = token.constrainedTokenBorder.cutaway(a, b, { start, end, bottomElevationFn, topElevationFn });
return cutaways.flatMap(cutaway => {
const ixs = cutaway.intersectSegment3d(start, end);
if ( cutaway.contains3d(start) ) {
const pt = cutaway._to2d(start);
pt.movingInto = true;
ixs.push(pt);
}
return ixs;
});
}
}

Expand Down
8 changes: 6 additions & 2 deletions scripts/segment_labels_highlighting.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ export function customizedTextLabel(ruler, segment, origLabel = "") {
// (2) Extra text
// Strip out any custom text from the original label.
// Format for Foundry Default: '0 ft [0 ft]'
const tmpLabel = origLabel;
origLabel = origLabel.replace(getDefaultLabel(segment), "");
if ( origLabel !== "" ) console.debug(`Ruler original label: ${origLabel}`);

// (3) Waypoint
if ( segment.last && segment.waypoint.idx > 0 ) childLabels.waypoint = {
Expand Down Expand Up @@ -305,10 +307,12 @@ function getDefaultLabel(segment) {
// Label based on Foundry default _getSegmentLabel.
if ( segment.teleport ) return "";
const units = canvas.grid.units;
let label = `${Math.round(roundMultiple(segment.waypoint.distance) * 100) / 100}`;

// As modified in _getSegmentLabel wrap.
let label = `${Math.round(roundMultiple(segment.waypoint.cost) * 100) / 100}`;
if ( units ) label += ` ${units}`;
if ( segment.last ) {
label += ` [${Math.round(canvas.controls.ruler.totalDistance * 100) / 100}`;
label += ` [${Math.round(canvas.controls.ruler.totalCost * 100) / 100}`;
if ( units ) label += ` ${units}`;
label += "]";
}
Expand Down
76 changes: 69 additions & 7 deletions scripts/system_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ Hooks.once("init", function() {
// Add specialized token speed function
const tokenSpeedFn = SPECIALIZED_TOKEN_SPEED[game.system.id];
if ( tokenSpeedFn ) SPEED.tokenSpeed = tokenSpeedFn;

// Add specialized token speed setting function
const setTokenSpeedFn = SPECIALIZED_SET_TOKEN_SPEED[game.system.id];
if ( setTokenSpeedFn ) SPEED.setTokenSpeed = setTokenSpeedFn;
});

// ----- NOTE: Attributes ----- //
Expand Down Expand Up @@ -257,13 +261,14 @@ const SPECIALIZED_SPEED_CATEGORIES = {
/**
* sfrpg
* Given a token, retrieve its base speed.
* @param {Token} token Token whose speed is required
* @param {Token} token Token whose speed is required
* @param {MOVEMENT_TYPES} [movementType] Type of movement; if omitted automatically determined
* @returns {number|null} Distance, in grid units. Null if no speed provided for that category.
* (Null will disable speed highlighting.)
*/
function sfrpgTokenSpeed(token) {
const moveType = token.movementType;
let speed = foundry.utils.getProperty(token, SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, moveType)]);
function sfrpgTokenSpeed(token, movementType) {
movementType ??= token.movementType;
let speed = foundry.utils.getProperty(token, SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, movementType)]);
switch ( token.actor?.type ) {
case "starship": speed = foundry.utils.getProperty(token, "actor.system.attributes.speed.value"); break;
case "vehicle": speed = foundry.utils.getProperty(token, "actor.system.attributes.speed.drive"); break;
Expand All @@ -276,13 +281,16 @@ function sfrpgTokenSpeed(token) {
* pf2e
* See https://github.com/7H3LaughingMan/pf2e-elevation-ruler/blob/main/scripts/module.js
* Finds walk, fly, burrow values.
* @param {Token} token Token whose speed is required
* @param {Token} token Token whose speed is required
* @param {MOVEMENT_TYPES} [movementType] Type of movement; if omitted automatically determined
* @returns {number|null} Distance, in grid units. Null if no speed provided for that category.
* (Null will disable speed highlighting.)
*/
function pf2eTokenSpeed(token) {
function pf2eTokenSpeed(token, movementType) {
movementType ??= token.movementType;
const tokenSpeed = token.actor.system.attributes.speed;
let speed = null;
switch (token.movementType) {
switch (movementType) {
case MOVEMENT_TYPES.WALK: speed = tokenSpeed.total; break;
case MOVEMENT_TYPES.FLY: {
const flySpeed = tokenSpeed.otherSpeeds.find(x => x.type == "fly");
Expand All @@ -304,6 +312,60 @@ const SPECIALIZED_TOKEN_SPEED = {
pf2e: pf2eTokenSpeed
};

// ----- Specialized set token speed by system ----- //

/**
* sfrpg
* Temporarily set the speed attribute for a token. Only sets locally; does not hit the database.
* @param {number} value Speed to set
* @param {Token} token Token on which to set the speed property
* @param {MOVEMENT_TYPES} [movementType] Type of movement; if omitted automatically determined
*/
function sfrpgSetTokenSpeed(value, token, movementType) {
movementType ??= token.movementType;
switch ( token.actor?.type ) {
case "starship": speed = foundry.utils.setProperty(token, "actor.system.attributes.speed.value", value); break;
case "vehicle": speed = foundry.utils.setProperty(token, "actor.system.attributes.speed.drive", value); break;
default: foundry.utils.setProperty(token, SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, movementType)], value);
}
}

/**
* pf2e
* See https://github.com/7H3LaughingMan/pf2e-elevation-ruler/blob/main/scripts/module.js
* Finds walk, fly, burrow values.
* Temporarily set the speed attribute for a token. Only sets locally; does not hit the database.
* @param {number} value Speed to set
* @param {Token} token Token on which to set the speed property
* @param {MOVEMENT_TYPES} [movementType] Type of movement; if omitted automatically determined
*/
function pf2eSetTokenSpeed(value, token, movementType) {

/* Cannot set token speed in pf2e this way.
movementType ??= token.movementType;
const tokenSpeed = token.actor.system.attributes.speed;
switch (movementType) {
case MOVEMENT_TYPES.WALK: tokenSpeed.total = value; break;
case MOVEMENT_TYPES.FLY: {
const flySpeed = tokenSpeed.otherSpeeds.find(x => x.type == "fly");
if ( typeof flySpeed !== "undefined" ) flySpeed.total = value;
break;
}
case MOVEMENT_TYPES.BURROW: {
const burrowSpeed = tokenSpeed.otherSpeeds.find(x => x.type == "burrow");
if ( typeof burrowSpeed !== "undefined" ) burrowSpeed.total = value;
break;
}
};
*/
}

const SPECIALIZED_SET_TOKEN_SPEED = {
sfrpg: sfrpgSetTokenSpeed,
pf2e: pf2eSetTokenSpeed
};


// ----- Specialized category distances by system ----- //

/**
Expand Down

0 comments on commit d0591b1

Please sign in to comment.