Skip to content

Commit

Permalink
Merge 0.8.10 to main
Browse files Browse the repository at this point in the history
  • Loading branch information
caewok committed Apr 4, 2024
2 parents 3e5175a + 73eca15 commit f353a47
Show file tree
Hide file tree
Showing 27 changed files with 3,254 additions and 1,830 deletions.
63 changes: 63 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,66 @@
# 0.8.10
## New features
Add indicator of past combat movement in the ruler.
Allow for unlimited number of speed categories, colors, along with custom speed functions in the CONFIG. Closes issue #58.
More aggressive path straightening using a reverse Ramer-Douglas-Peucker algorithm based on collision checking. Closes issue #48.
Add CONFIG settings to ignore tokens for pathfinding based on HP attribute or set of token statuses. Closes issue #55.
Add a keybind ("p") to temporarily toggle pathfinding.
Add `rulerSegmentOrigin` and `rulerSegmentDestination` to options passed to token update.

## Bug fixes
Refactor physical and move distance measurement in anticipation of Foundry v12. Closes issue #59 (measurement near waypoints).
Refactor how segments are split for purposes of speed highlighting.
Fix for counting alternating diagonals across ruler segments/waypoints.
Fix for token stopping prematurely if the mouse is released after space bar pressed when using the Ruler.
Fix for "drunken token movement" in which a token would wander off the path when moving through several fake waypoints that were not centered on the grid.
Allow large tokens to move through triangle edge unless both vertices of the edge shares a blocking edge. This allows pathfinding to work for large tokens in more situations, while still not taking paths through narrow spaces constrained on both sides by walls.

## BREAKING
Options for `CONFIG.elevationruler.SPEED` have changed. To change the speed highlighting, you will need to change the array of speed categories in `CONFIG.elevationruler.SPEED.CATEGORIES`. A speed category is defined as:
```js
/**
* @typedef {object} SpeedCategory
*
* Object that stores the name, multiplier, and color of a given speed category.
* Custom properties are permitted. The SpeedCategory is passed to SPEED.maximumCategoryDistance,
* which in turn can be defined to use custom properties to calculate the maximum distance for the category.
*
* @prop {Color} color Color used with ruler highlighting
* @prop {string} name Unique name of the category (relative to other SpeedCategories)
* @prop {number} [multiplier] This times the token movement equals the distance for this category
*/
```

For more complex options, you can now replace two functions that control token speed measurements. You may also want to add additional properties to the `SpeedCategory` for your use case.
```js
/**
* Given a token, get the maximum distance the token can travel for a given type.
* Distance measured from 0, so types overlap. E.g.
* WALK (x1): Token speed 25, distance = 25.
* DASH (x2): Token speed 25, distance = 50.
*
* @param {Token} token Token whose speed should be used
* @param {SpeedCategory} speedCategory Category for which the maximum distance is desired
* @param {number} [tokenSpeed] Optional token speed to avoid repeated lookups
* @returns {number}
*/
SPEED.maximumCategoryDistance = function(token, speedCategory, tokenSpeed) {
tokenSpeed ??= SPEED.tokenSpeed(token);
return speedCategory.multiplier * tokenSpeed;
};

/**
* Given a token, retrieve its base speed.
* @param {Token} token Token whose speed is required
* @returns {number} Distance, in grid units
*/
SPEED.tokenSpeed = function(token) {
const speedAttribute = SPEED.ATTRIBUTES[token.movementType] ?? SPEED.ATTRIBUTES.WALK;
return Number(foundry.utils.getProperty(token, speedAttribute));
};

```
# 0.8.9
### New features
Track combat moves on a per-combat basis. Add settings toggle to have movement speed highlighting reflect sum of moves for that combat round.
Expand Down
124 changes: 119 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,130 @@ This is particularly useful where you have an elevated character at the origin,

As with the normal Foundry ruler, if you begin a measurement at your token, you can hit spacebar to move the token. Elevation is modified at the end of each waypoint segment move. This may allow you, for example, to jump over a wall if that wall has a maximum height under your current elevation as can be set up using the Wall Height module (or Levels + Wall Height).

# Token controls

Elevation Ruler adds two token controls. The "Use Pathfinding" control toggles pathfinding on/off. The "Prefer Token Elevation" control, when enabled, will not adjust the destination elevation when hovering over other tokens. Typically, without this enabled, the ruler will change the destination elevation to match the elevation of a token at the destination point.

# Key bindings

Elevation Ruler defines certain keybindings:
- Decrement Ruler Elevation (`[`): When measuring or dragging tokens, decrease the destination elevation by one grid unit. If you trigger a token move, its elevation will be adjusted accordingly.
- Increment Ruler Elevation (`]`): See Decrement.
- 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.

# Settings

- Add token elevation control: Add the "Prefer Token Elevation Control" to the Token controls.
- Tokens Block: When pathfinding, select whether none, hostile, or all tokens block the path.
- Limit Pathfinding to Explored Areas: For users, should the pathfinding stop working when they move the ruler destination into an unexplored area?
- Use Token Ruler: Display the ruler when dragging tokens.
- Use Token Speed Highlighting: Highlight grid squares under the ruler based on the token's speed. See API, below, for how to modify colors and speed categories.
- Track Combat Move: When displaying the speed highlighting during combat, count any movement already made by the token this combat round.
- Round Distance to Multiple: Round the measurement display by this multiple. For example, "10" will round 111.23 to 110.
- Token as Terrain Multiplier: How much does a token penalize movement through that token?
- Terrain Grid Measurement: When measuring movement through terrain on a gridded map, how should the terrain be accounted for?
- Center Point: if the terrain overlaps the grid square/hex center point, that grid square/hex will have penalized movement.
- Percent Area: if the terrain area excees some threshold coverage of the grid square/hex center point, that grid square/hex will have penalized movement.
- Euclidean: A line moving through the terrain will be proportionally penalized based on the percentage of that line within the terrain.
- Percent Area Threshold: Defines the threshold in Terrain Grid Measurement: Percent Area.

# API

You can modify the system attributes used for walk/fly/burrow as well as the colors used in `CONFIG.elevationruler.SPEED`. You can modify the Token HUD icons in `CONFIG.elevationruler.MOVEMENT_BUTTONS`.
You can access defined properties used by Elevation Ruler at `CONFIG.elevationruler`. You can access some of this module's classes and advanced data at `game.modules.get("elevationruler").api`.

Elevation Ruler adds token properties to track the last movement made by the token:
- `_token.lastMoveDistance`: Movement units expended on the last move. May not be physical distance; this instead accounts for additional movement due to difficult terrain. If the token has not moved this combat round, this value will be 0.
- `_token._lastMoveDistance`: Same as above, but does not account for combat rounds.

## Setting speed colors

To change how speed highlighting works, you will need to change the array of speed categories in `CONFIG.elevationruler.SPEED.CATEGORIES`. A speed category is defined as:
```js
/**
* @typedef {object} SpeedCategory
*
* Object that stores the name, multiplier, and color of a given speed category.
* Custom properties are permitted. The SpeedCategory is passed to SPEED.maximumCategoryDistance,
* which in turn can be defined to use custom properties to calculate the maximum distance for the category.
*
* @prop {Color} color Color used with ruler highlighting
* @prop {string} name Unique name of the category (relative to other SpeedCategories)
* @prop {number} [multiplier] This times the token movement equals the distance for this category
*/
```
The default categories are as follows, although these properties may vary by system:
```js
const WalkSpeedCategory = {
name: "Walk",
color: Color.from(0x00ff00),
multiplier: 1
};

const DashSpeedCategory = {
name: "Dash",
color: Color.from(0xffff00),
multiplier: 2
};
```
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 byond that highlighted with the maximum color.

There is a also a "Maximum" property for when the distances for the categories above are exceeded. You can set the default color at `SPEED.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.

## Advanced speed modifications

For more complex options, you can replace two functions that control token speed measurements. You may also want to add additional properties to the `SpeedCategory` for your use case.
```js
/**
* Given a token, get the maximum distance the token can travel for a given type.
* Distance measured from 0, so types overlap. E.g.
* WALK (x1): Token speed 25, distance = 25.
* DASH (x2): Token speed 25, distance = 50.
*
* @param {Token} token Token whose speed should be used
* @param {SpeedCategory} speedCategory Category for which the maximum distance is desired
* @param {number} [tokenSpeed] Optional token speed to avoid repeated lookups
* @returns {number}
*/
SPEED.maximumCategoryDistance = function(token, speedCategory, tokenSpeed) {
tokenSpeed ??= SPEED.tokenSpeed(token);
return speedCategory.multiplier * tokenSpeed;
};

/**
* Given a token, retrieve its base speed.
* @param {Token} token Token whose speed is required
* @returns {number} Distance, in grid units
*/
SPEED.tokenSpeed = function(token) {
const speedAttribute = SPEED.ATTRIBUTES[token.movementType] ?? SPEED.ATTRIBUTES.WALK;
return Number(foundry.utils.getProperty(token, speedAttribute));
};
```
## Controlling movement buttons
You can modify the system attributes used for walk/fly/burrow in `CONFIG.elevationruler.SPEED.ATTRIBUTES`. You can modify the Token HUD icons in `CONFIG.elevationruler.MOVEMENT_BUTTONS`.
Elevation Ruler adds a token property to get the token movement type: `_token.movementType`. You may also want the enumerated movement types: `game.modules.get("elevationruler").api.MOVEMENT_TYPES`.
## Controlling terrain display
You can modify the icon used when hovering over difficult terrain:
- `CONFIG.elevationruler.SPEED.terrainSymbol`: You can use any text string here. Paste in a unicode symbol if you want a different symbol. For Font Awesome icons, use, e.g., "\uf0e7". (This is the code for [FA lightning bolt](https://fontawesome.com/icons/bolt?f=classic&s=solid).)
- `CONFIG.elevationruler.SPEED.useFontAwesome`: Set to true to interpet the `terrainSymbol` as FA unicode.
## Controlling pathfinding
If you set `CONFIG.elevationruler.pathfindingCheckTerrains` to `true`, it will test for Terrain Mapper terrains (including Tiles), Drawings, and Tokens for terrain penalties. This is currently a serious performance hit and so is not enabled by default. (By default, tokens can block pathfinding per user settings but advanced terrain penalties are not considered.) This may change depending on Foundry VTT v12's approach to scene regions.
You can tell the pathfinding algorithm to ignore certain tokens. By default it ignores dead tokens for dnd5e. To change this, set the string in `CONFIG.elevationruler.SPEED.tokenHPAttribute` (or set it to "" to pathfind around dead tokens). If you want default support for a system, open a git issue and preferably tell me how to find the HP value for that system's tokens.
You can also tell the pathfinding algorithm to ignore tokens with certain statuses. The default Set is at `CONFIG.elevationruler.pathfindingIgnoreStatuses`.
Elevation Ruler adds a token property to get the token movement type: `_token.movementType`. You may also want the enumerated movement types: `game.modules.get("elevationruler").api.MOVEMENT_TYPES`.
Elevation Ruler adds token properties to track the last movement made by the token:
- `_token.lastMoveDistance`: Movement units expended on the last move. May not be physical distance; this instead accounts for additional movement due to difficult terrain. If the token has not moved this combat round, this value will be 0.
- `_token._lastMoveDistance`: Same as above, but does not account for combat rounds.
11 changes: 7 additions & 4 deletions languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
"elevationruler.keybindings.removeWaypointTokenRuler.name": "Remove Token Ruler Waypoint",
"elevationruler.keybindings.removeWaypointTokenRuler.hint": "When token ruler is enabled, remove a waypoint while dragging the token.",

"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.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",
"elevationruler.settings.levels-labels-ui": "Only when Levels UI is active",
"elevationruler.settings.levels-labels-always": "Always",

"elevationruler.settings.prefer-token-elevation.name": "Add token elevation control",
"elevationruler.settings.prefer-token-elevation.hint": "Add a control to the token toolbar to prefer the token elevation when measuring. When toggled on, the control will keep the ruler at the elevation of the measuring token unless another token is encountered or the grid space is at a higher elevation than the token. Otherwise, the ruler will assume the elevation of the grid space or the token encountered.",
"elevationruler.settings.prefer-token-elevation.name": "Add Token Elevation Control",
"elevationruler.settings.prefer-token-elevation.hint": "Add a control to the token toolbar. When toggled on, the control will keep the ruler at the elevation of the measuring token. Otherwise, the ruler destination will assume the elevation of the grid space or the token encountered at the destination point of the ruler.",

"elevationruler.settings.enable-token-ruler.name": "Use Token Ruler",
"elevationruler.settings.enable-token-ruler.hint": "Display the ruler when dragging tokens.",
Expand All @@ -43,9 +46,9 @@
"elevationruler.settings.token-speed-multiplier.hint": "For token speed highlighting, this is the multiplier used to go from walk to run (dash) to maximum. For example, with a multiplier of 2, a token with 25 walk speed will have its ruler highlight orange after the first 25 units of distance, and red after the first 50.",

"elevationruler.settings.round-to-multiple.name": "Round Distance to Multiple",
"elevationruler.settings.round-to-multiple.hint": "For gridless distance measurement, round the distance displayed to the nearest multiple of the number entered. Leave blank to disable rounding.",
"elevationruler.settings.round-to-multiple.hint": "For gridless distance measurement, round the distance displayed to the nearest multiple of the number entered. Set to 0 to disable rounding.",

"elevationruler.settings.token-terrain-multiplier.name": "Token As Terrain Multiplier",
"elevationruler.settings.token-terrain-multiplier.name": "Token as Terrain Multiplier",
"elevationruler.settings.token-terrain-multiplier.hint": "Multiplier to use to calculate movement speed when moving through other tokens. Set to 1 to ignore. Values less than 1 treat token spaces as faster than normal; values greater than 1 penalize movement through token spaces.",

"elevationruler.settings.pathfinding_tokens_block.name": "Tokens Block",
Expand Down
28 changes: 14 additions & 14 deletions scripts/BaseGrid.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
/* globals
canvas
CONFIG
*/
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */

// Patches for the BaseGrid class
// Patches for BaseGrid class
import { MODULE_ID } from "./const.js";
import { log } from "./util.js";


export const PATCHES = {};
PATCHES.BASIC = {};

/**
* Mix BaseGrid.prototype._getRulerDestination
* If the ruler is not snapped, then return the actual token position, adjusted for token dimensions.
* Override BaseGrid.prototype._getRulerDestination.
* Don't return the top left corner, so that the token stays on the path through
* waypoints not at the center of the grid.
* @param {Ray} ray The ray being moved along.
* @param {Point} offset The offset of the ruler's origin relative to the token's position.
* @param {Token} token The token placeable being moved.
* @return {Point}
*/
function _getRulerDestination(wrapped, ray, offset, token) {
if ( canvas.controls.ruler._unsnap || ray.pathfinding ) return ray.B.add(offset);

// We are moving from the token center, so add back 1/2 width/height to offset.
if ( !canvas.controls.ruler._unsnappedOrigin ) {
offset.x += canvas.scene.dimensions.size * 0.5;
offset.y += canvas.scene.dimensions.size * 0.5;
}
return wrapped(ray, offset, token);
function _getRulerDestination(ray, offset, _token) {
log(`Offsetting destination for ${_token.name}`, { dest: ray.B, offset });
return ray.B.add(offset).roundDecimals();
}

PATCHES.BASIC.MIXES = { _getRulerDestination };
PATCHES.BASIC.OVERRIDES = { _getRulerDestination };
7 changes: 4 additions & 3 deletions scripts/ClientKeybindings.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* globals
canvas,
ui
*/
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */

Expand All @@ -25,8 +26,8 @@ function _onMeasuredRulerMovement(wrapped, context) {

// For each controlled token, end the drag.
canvas.tokens.clearPreviewContainer();
ruler.moveToken().then((response) => ruler._endMeasurement());
ruler.moveToken().then(_response => ruler._endMeasurement());
return true;
}

PATCHES.TOKEN_RULER.STATIC_MIXES = { _onMeasuredRulerMovement }
PATCHES.TOKEN_RULER.STATIC_MIXES = { _onMeasuredRulerMovement };
35 changes: 0 additions & 35 deletions scripts/GridLayer.js

This file was deleted.

Loading

0 comments on commit f353a47

Please sign in to comment.