Skip to content

Commit

Permalink
Merge branch 'release/0.9.1' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
caewok committed May 31, 2024
2 parents f2e5a21 + a583c8c commit 13837ff
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 37 deletions.
9 changes: 9 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# 0.9.1
Fix errors when using the ruler on gridless scenes.
Correct speed highlighting on gridless scenes.

## New features
Add a configuration option (`CONFIG.elevationruler.gridlessHighlightWidthMultiplier`) to adjust the width of the gridless highlight.

Add a "teleport" keybind and associated `Ruler.prototype.method`. If a ruler is active, this will set all segments to "teleport" and will jump to any user-set waypoints and then the destination, very quickly.

# 0.9.0
Initial support for Foundry v12.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ Elevation Ruler defines certain keybindings:
- 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.
- Teleport (``): (Foundry v12) If you hit `` (right arrow key) while the ruler is active, it will jump the token to the end destination without the full slow animation. Instead, it will quickly animate to each user-defined waypoint and then the destination. So the token is updating and "stopping" at each waypoint in turn, just very fast!

# Settings

Expand Down
3 changes: 3 additions & 0 deletions languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"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.keybindings.teleport.name": "Teleport along ruler",
"elevationruler.keybindings.teleport.hint": "When measuring, press this key to move the token to the ruler destination without animation",

"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
36 changes: 33 additions & 3 deletions scripts/Ruler.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ function _addWaypoint(wrapper, point) {
wrapper(point);

// If shift was held, use the precise point.
if ( this._unsnap ) this.waypoints.at(-1).copyFrom(point);
if ( this._unsnap ) {
const lastWaypoint = this.waypoints.at(-1);
lastWaypoint.x = point.x;
lastWaypoint.y = point.y;
}
else if ( this.waypoints.length === 1 ) {
// Move the waypoint to find unsnapped token.
const oldWaypoint = foundry.utils.duplicate(this.waypoints[0]);
Expand Down Expand Up @@ -191,7 +195,10 @@ function _removeWaypoint(wrapper, point, { snap = true } = {}) {
*/
function _getMeasurementDestination(wrapped, destination) {
const pt = wrapped(destination);
if ( this._unsnap ) pt.copyFrom(destination);
if ( this._unsnap ) {
pt.x = destination.x;
pt.y = destination.y;
}
return pt;
}

Expand Down Expand Up @@ -219,6 +226,7 @@ async function _animateMovement(wrapped, token) {
return Promise.allSettled(promises);
}


/**
* Recalculate the offset used by _getRulerDestination.
* Needed for hex grids.
Expand Down Expand Up @@ -388,6 +396,12 @@ function _computeTokenSpeed() {
let s = 0;
let segment;

// Debugging
// if ( this.segments[0].moveDistance > 25 ) log(`${this.segments[0].moveDistance}`);
// if ( this.segments[0].moveDistance > 30 ) log(`${this.segments[0].moveDistance}`);
// if ( this.segments[0].moveDistance > 50 ) log(`${this.segments[0].moveDistance}`);
// if ( this.segments[0].moveDistance > 60 ) log(`${this.segments[0].moveDistance}`);

// Progress through each speed attribute in turn.
const categoryIter = [...SPEED.CATEGORIES, MaximumSpeedCategory].values();
const maxDistFn = (token, speedCategory, tokenSpeed) => {
Expand Down Expand Up @@ -658,11 +672,27 @@ function decrementElevation() {
game.user.broadcastActivity({ ruler: ruler.toJSON() });
}

/**
* Add Ruler.prototype.moveWithoutAnimation
* Move the token and stop the ruler measurement
* @returns {boolean} False if the movement did not occur
*/
async function teleport(context) {
if ( this._state !== this.constructor.STATES.MEASURING ) return false;
if ( !this._canMove(this.token) ) return false;

// Change all segments to teleport.
this.segments.forEach(s => s.teleport = true);
return this.moveToken();
}


PATCHES.BASIC.METHODS = {
incrementElevation,
decrementElevation,
elevationAtLocation,
_computeTokenSpeed
_computeTokenSpeed,
teleport
};

PATCHES.BASIC.GETTERS = {
Expand Down
44 changes: 36 additions & 8 deletions scripts/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,41 @@ Hooks.once("init", function() {

// Configuration
CONFIG[MODULE_ID] = {
// Configurations related to measuring token speed for ruler highlighting.
/**
* Configurations related to measuring token speed for ruler highlighting. See const.js.
* @type { object }
*/
SPEED,

// Font awesome identifiers for the Token HUD speed selection.
/**
* Font awesome identifiers for the Token HUD speed selection. See const.js.
* @type {object}
*/
MOVEMENT_BUTTONS,

// Types of movement.
/**
* Types of movement. See const.js.
* @type {enum}
*/
MOVEMENT_TYPES,

// Account for terrains/tokens in pathfinding.
// Can be a serious performance hit.
/**
* Account for terrains/tokens in pathfinding.
* Can be a serious performance hit.
* @type {boolean}
*/
pathfindingCheckTerrains: false,

// Where to find token HP, used to ignore dead tokens when pathfinding.
/**
* Where to find token HP, used to ignore dead tokens when pathfinding.
* @type {string}
*/
tokenHPAttribute: defaultHPAttribute(),

// ID of Token statuses to ignore when pathfinding.
/**
* ID of Token statuses to ignore when pathfinding.
* @type {Set<string>}
*/
pathfindingIgnoreStatuses: new Set([
"sleeping",
"unconscious",
Expand All @@ -84,7 +102,17 @@ Hooks.once("init", function() {
"petrified",
"restrained"]),

// Enable certain debug console logging and tests.
/**
* Adjust the width of the highlighting in gridless maps.
* Percentage of `canvas.scene.dimensions.size` that determines the width.
* @type {number}
*/
gridlessHighlightWidthMultiplier: 0.2,

/**
* Enable certain debug console logging and tests.
* @type {boolean}
*/
debug: false
};

Expand Down
44 changes: 25 additions & 19 deletions scripts/segments.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,31 +218,37 @@ export function _getDistanceLabels(segmentDistance, moveDistance, totalDistance)
return { newSegmentDistance, newMoveDistance, newTotalDistance };
}


/**
* Mixed wrap Ruler.prototype._animateSegment
* When moving the token along the segments, update the token elevation to the destination + increment
* for the given segment.
* Mark the token update if pathfinding for this segment.
*/
export async function _animateSegment(token, segment, destination) {
// If the token is already at the destination, _animateSegment will throw an error when the animation is undefined.
// This can happen when setting artificial segments for highlighting or pathfinding.
if ( token.document.x !== destination.x
|| token.document.y !== destination.y ) {

log(`Updating ${token.name} destination from ({${token.document.x},${token.document.y}) to (${destination.x},${destination.y}) for segment (${segment.ray.A.x},${segment.ray.A.y})|(${segment.ray.B.x},${segment.ray.B.y})`);

// Same as wrapped but pass an option.
await token.document.update(destination, {
rulerSegment: this.segments.length > 1,
firstRulerSegment: segment.first,
lastRulerSegment: segment.last,
rulerSegmentOrigin: segment.ray.A,
rulerSegmentDestination: segment.ray.B
});
const anim = CanvasAnimation.getAnimation(token.animationName);
await anim.promise;
log(`Updating ${token.name} destination from ({${token.document.x},${token.document.y}) to (${destination.x},${destination.y}) for segment (${segment.ray.A.x},${segment.ray.A.y})|(${segment.ray.B.x},${segment.ray.B.y})`);

// If the segment is teleporting and the segment destination is not a waypoint or ruler destination, skip.
// if ( segment.teleport
// && !(segment.B.x === this.destination.x && segment.B.y === this.destination.y )
// && !this.waypoints.some(w => segment.B.x === w.x && segment.B.y === w.y) ) return;

let name;
if ( segment.animation?.name === undefined ) name = token.animationName;
else name ||= Symbol(token.animationName);
const updateOptions = {
rulerSegment: this.segments.length > 1,
firstRulerSegment: segment.first,
lastRulerSegment: segment.last,
rulerSegmentOrigin: segment.ray.A,
rulerSegmentDestination: segment.ray.B,
teleport: segment.teleport,
animation: {...segment.animation, name}
}
const {x, y} = token.document._source;
await token.animate({x, y}, {name, duration: 0});
await token.document.update(destination, updateOptions);
await CanvasAnimation.getAnimation(name)?.promise;

// Update elevation after the token move.
if ( segment.ray.A.z !== segment.ray.B.z ) {
Expand Down Expand Up @@ -303,7 +309,7 @@ export function _highlightMeasurementSegment(wrapped, segment) {
// If gridless, highlight a rectangular shaped portion of the line.
if ( canvas.grid.type === CONST.GRID_TYPES.GRIDLESS ) {
const { A, B } = segment.ray;
const width = Math.floor(canvas.scene.dimensions.size * 0.2);
const width = Math.floor(canvas.scene.dimensions.size * (CONFIG[MODULE_ID].gridlessHighlightWidthMultiplier ?? 0.2));
const ptsA = perpendicularPoints(A, B, width * 0.5);
const ptsB = perpendicularPoints(B, A, width * 0.5);
const shape = new PIXI.Polygon([
Expand All @@ -312,7 +318,7 @@ export function _highlightMeasurementSegment(wrapped, segment) {
ptsB[0],
ptsB[1]
]);
canvas.grid.highlightPosition(this.name, {color: this.color, shape});
canvas.interface.grid.highlightPosition(this.name, { x: A.x, y: A.y, color: this.color, shape});
}

// Reset to the default color.
Expand Down
24 changes: 23 additions & 1 deletion scripts/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ const KEYBINDINGS = {
REMOVE_WAYPOINT: "removeWaypointTokenRuler"
},
TOGGLE_PATHFINDING: "togglePathfinding",
FORCE_TO_GROUND: "forceToGround"
FORCE_TO_GROUND: "forceToGround",
TELEPORT: "teleport"
};


Expand Down Expand Up @@ -296,6 +297,27 @@ export class Settings extends ModuleSettingsAbstract {
},
precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL
});

game.keybindings.register(MODULE_ID, KEYBINDINGS.TELEPORT, {
name: game.i18n.localize(`${MODULE_ID}.keybindings.${KEYBINDINGS.TELEPORT}.name`),
hint: game.i18n.localize(`${MODULE_ID}.keybindings.${KEYBINDINGS.TELEPORT}.hint`),
editable: [
{ key: "ArrowRight" }
],
onDown: async function(context) {
const ruler = canvas.controls.ruler;
if ( !ruler.active ) return;
canvas.mouseInteractionManager.cancel(context.event); // Unclear if this is doing anything.
const token = ruler.token;
await ruler.teleport(context);
if ( token ) {
token._preview?._onDragEnd(); // Unclear if this is doing anything.
token._onDragEnd(); // Unclear if this is doing anything.
}
canvas.mouseInteractionManager.reset(); // Unclear if this is doing anything.
},
precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL
});
}

static toggleTokenBlocksPathfinding(blockSetting) {
Expand Down
14 changes: 8 additions & 6 deletions scripts/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@ export function log(...args) {

/**
* Get the two points perpendicular to line A --> B at A, a given distance from the line A --> B
* @param {PIXI.Point} A
* @param {PIXI.Point} B
* @param {PIXI.Point|Point} A
* @param {PIXI.Point|Point} B
* @param {number} distance
* @returns {[PIXI.Point, PIXI.Point]} Points on either side of A.
*/
export function perpendicularPoints(A, B, distance = 1) {
const delta = B.subtract(A);
const pt0 = new PIXI.Point(A.x - delta.y, A.y + delta.x);
A = PIXI.Point._tmp.copyFrom(A);
B = PIXI.Point._tmp2.copyFrom(B);
const delta = B.subtract(A, PIXI.Point._tmp3);
const normal = new PIXI.Point(A.x - delta.y, A.y + delta.x);
return [
A.towardsPoint(pt0, distance),
A.towardsPoint(pt0, -distance)
A.towardsPoint(normal, distance),
A.towardsPoint(normal, -distance)
];
}

Expand Down

0 comments on commit 13837ff

Please sign in to comment.