From 7740ea0af320846460debeda728110b1dc9e1848 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 30 May 2024 13:04:55 -0700 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=90=9B=20fix|Gridless|Remove=20`copyF?= =?UTF-8?q?rom`=20b/c=20it=20throws=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index e2b5d70..7499810 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -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]); @@ -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; } From 2799c1938794691af02e1d839647dc773403f795 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 30 May 2024 13:40:31 -0700 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=90=9B=20fix|Gridless|Don't=20require?= =?UTF-8?q?=20PIXI.Point=20for=20perpendicularPoint=20fn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/util.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/util.js b/scripts/util.js index 0917895..a31133f 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -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) ]; } From 908547925a60e99db5b5763b4676846baa158bbd Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 30 May 2024 13:41:31 -0700 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=90=9B=20fix|Gridless|Fix=20speed=20h?= =?UTF-8?q?ighlighting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For gridless, need to pass `highlightPosition` a valid x, y. Otherwise, the highlight will not appear. (Technically, it appears once, for the green. But it gets canceled out after that.) --- scripts/Ruler.js | 6 ++++++ scripts/segments.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 7499810..d81194a 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -395,6 +395,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) => { diff --git a/scripts/segments.js b/scripts/segments.js index 2a08e16..1c7a122 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -312,7 +312,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. From c9725d3d623a1f8758d981df3e2279a32b8a3dd1 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 30 May 2024 14:02:56 -0700 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=8E=B8=20feat|Gridless|Add=20config?= =?UTF-8?q?=20for=20gridless=20size?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/module.js | 44 ++++++++++++++++++++++++++++++++++++-------- scripts/segments.js | 2 +- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/scripts/module.js b/scripts/module.js index 5671d70..b2af8b9 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -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} + */ pathfindingIgnoreStatuses: new Set([ "sleeping", "unconscious", @@ -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 }; diff --git a/scripts/segments.js b/scripts/segments.js index 1c7a122..d31172c 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -303,7 +303,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([ From cedec8be27ba47dafbccf4aac1afe980f5ed9267 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 30 May 2024 14:34:09 -0700 Subject: [PATCH 5/8] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docs|Changelog|v0.9.1?= =?UTF-8?q?=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Changelog.md b/Changelog.md index ec33651..bb8c0b6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,10 @@ +# 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. + # 0.9.0 Initial support for Foundry v12. From 6015fa3206787aac8665c263e58335884ea9c439 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 30 May 2024 17:06:52 -0700 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=8E=B8=20feat|Teleport|Add=20teleport?= =?UTF-8?q?=20key=20binding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a teleport method that marks all segments as teleport. Segments that do not end at a waypoint or destination are skipped so the teleport does not go through fake waypoints. --- languages/en.json | 3 +++ scripts/Ruler.js | 19 ++++++++++++++++++- scripts/segments.js | 40 +++++++++++++++++++++++----------------- scripts/settings.js | 24 +++++++++++++++++++++++- 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/languages/en.json b/languages/en.json index 27b2937..77f8856 100644 --- a/languages/en.json +++ b/languages/en.json @@ -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", diff --git a/scripts/Ruler.js b/scripts/Ruler.js index d81194a..b37f1e8 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -226,6 +226,7 @@ async function _animateMovement(wrapped, token) { return Promise.allSettled(promises); } + /** * Recalculate the offset used by _getRulerDestination. * Needed for hex grids. @@ -671,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 = { diff --git a/scripts/segments.js b/scripts/segments.js index d31172c..bc78a5a 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -218,6 +218,7 @@ 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 @@ -225,24 +226,29 @@ export function _getDistanceLabels(segmentDistance, moveDistance, totalDistance) * 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 ) { diff --git a/scripts/settings.js b/scripts/settings.js index e3ce0f1..179713d 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -64,7 +64,8 @@ const KEYBINDINGS = { REMOVE_WAYPOINT: "removeWaypointTokenRuler" }, TOGGLE_PATHFINDING: "togglePathfinding", - FORCE_TO_GROUND: "forceToGround" + FORCE_TO_GROUND: "forceToGround", + TELEPORT: "teleport" }; @@ -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) { From e05033b75b191ad0cf892df29a1c661d32c5cea6 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 30 May 2024 17:09:51 -0700 Subject: [PATCH 7/8] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docs|Changelog|Add=20n?= =?UTF-8?q?otes=20re=20teleport=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index bb8c0b6..350c433 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,8 @@ 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. From a583c8c05c5daa295cbe828a76391f032b4cfc9b Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 30 May 2024 17:13:33 -0700 Subject: [PATCH 8/8] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docs|Readme|Add=20disc?= =?UTF-8?q?ussion=20of=20waypoint=20keybind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 152adbc..1091b58 100644 --- a/README.md +++ b/README.md @@ -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