From bfec5ce888248f3264227a61ee7e7d9e92fcb427 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 07:14:23 -0700 Subject: [PATCH 01/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Deprecations|Switch=20?= =?UTF-8?q?to=20Math.clamp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/pathfinding/WallTracer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/pathfinding/WallTracer.js b/scripts/pathfinding/WallTracer.js index bc6f54b..d719e35 100644 --- a/scripts/pathfinding/WallTracer.js +++ b/scripts/pathfinding/WallTracer.js @@ -222,8 +222,8 @@ export class WallTracerEdge extends GraphEdge { * @returns {SegmentTracerEdge} */ static fromObjects(edgeA, edgeB, objects, tA = 0, tB = 1) { - tA = Math.clamped(tA, 0, 1); - tB = Math.clamped(tB, 0, 1); + tA = Math.clamp(tA, 0, 1); + tB = Math.clamp(tB, 0, 1); edgeA = PIXI.Point.fromObject(edgeA); edgeB = PIXI.Point.fromObject(edgeB); const eA = this.pointAtEdgeRatio(edgeA, edgeB, tA); @@ -302,7 +302,7 @@ export class WallTracerEdge extends GraphEdge { * @returns {WallTracerEdge[]|null} Array of two edge tracer edges that share t endpoint. */ splitAtT(edgeT) { - edgeT = Math.clamped(edgeT, 0, 1); + edgeT = Math.clamp(edgeT, 0, 1); if ( edgeT.almostEqual(0) || edgeT.almostEqual(1) ) return null; // Construct two new edges, divided at the edgeT location. From 26b7e2dbafd9907dc00d2d20e172699d29a0a803 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 07:15:12 -0700 Subject: [PATCH 02/15] =?UTF-8?q?=F0=9F=92=A1=20refactor|Settings|Rename?= =?UTF-8?q?=20to=20`setTokenBlocksPathfinding`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Toggle" makes it sound like it is switching it on/off, which is no longer the case. --- scripts/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/settings.js b/scripts/settings.js index 179713d..e56da30 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -125,7 +125,7 @@ export class Settings extends ModuleSettingsAbstract { [KEYS.PATHFINDING.TOKENS_BLOCK_CHOICES.HOSTILE]: localize(`${KEYS.PATHFINDING.TOKENS_BLOCK_CHOICES.HOSTILE}`), [KEYS.PATHFINDING.TOKENS_BLOCK_CHOICES.ALL]: localize(`${KEYS.PATHFINDING.TOKENS_BLOCK_CHOICES.ALL}`) }, - onChange: value => this.toggleTokenBlocksPathfinding(value) + onChange: value => this.setTokenBlocksPathfinding(value) }); register(KEYS.PATHFINDING.LIMIT_TOKEN_LOS, { @@ -320,7 +320,7 @@ export class Settings extends ModuleSettingsAbstract { }); } - static toggleTokenBlocksPathfinding(blockSetting) { + static setTokenBlocksPathfinding(blockSetting) { const C = this.KEYS.PATHFINDING.TOKENS_BLOCK_CHOICES; blockSetting ??= Settings.get(Settings.KEYS.PATHFINDING.TOKENS_BLOCK); if ( blockSetting === C.NO ) { // Disable From 8f13514d99c3e92ace52a0c9cb481d63785e9842 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 07:50:47 -0700 Subject: [PATCH 03/15] =?UTF-8?q?=F0=9F=90=9B=20fix|libGeometry|Fix=20`tok?= =?UTF-8?q?enShape`=20and=20address=20deprecations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/geometry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/geometry b/scripts/geometry index 63cf692..e83109a 160000 --- a/scripts/geometry +++ b/scripts/geometry @@ -1 +1 @@ -Subproject commit 63cf692aec85196768f035d72ab2604ae0c66f78 +Subproject commit e83109a56265221deac7aefa8ef3186e2995bdd7 From 19d5362f8eda5eee583a9193ac43ebec56bffa10 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 07:51:19 -0700 Subject: [PATCH 04/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Deprecation|Use=20`wal?= =?UTF-8?q?l.edge.a`=20instead=20of=20`wall.A`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/pathfinding/WallTracer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/pathfinding/WallTracer.js b/scripts/pathfinding/WallTracer.js index d719e35..20c74dd 100644 --- a/scripts/pathfinding/WallTracer.js +++ b/scripts/pathfinding/WallTracer.js @@ -242,7 +242,7 @@ export class WallTracerEdge extends GraphEdge { * @param {Wall} wall Wall represented by this edge * @returns {WallTracerEdge} */ - static fromWall(wall) { return this.fromObject(wall.A, wall.B, [wall]); } + static fromWall(wall) { return this.fromObject(wall.edge.a, wall.edge.b, [wall]); } /** * Construct an array of edges form the constrained token border. @@ -348,7 +348,7 @@ export class WallTracerEdge extends GraphEdge { if ( !wall.document.move || wall.isOpen ) return false; // Ignore one-directional walls which are facing away from the center - const side = wall.orientPoint(origin); + const side = wall.edge.orientPoint(origin); /* Unneeded? const wdm = PointSourcePolygon.WALL_DIRECTION_MODES; @@ -652,7 +652,7 @@ export class WallTracer extends Graph { if ( this.edges.has(wallId) ) return; // Construct a new wall edge set. - this.addObjectEdge(PIXI.Point.fromObject(wall.A), PIXI.Point.fromObject(wall.B), wall); + this.addObjectEdge(PIXI.Point.fromObject(wall.edge.a), PIXI.Point.fromObject(wall.edge.b), wall); this.wallIds.add(wallId); } From aa6cd166d8331cc3bf3b6d6bcc838e555b9fbf32 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 07:51:42 -0700 Subject: [PATCH 05/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Pathfinding|Use=20canv?= =?UTF-8?q?as=20edge=20hook=20to=20set=20up=20pathfinding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/patching.js | 5 +-- scripts/pathfinding/CanvasEdges.js | 47 ++++++++++++++++++++++++++++ scripts/pathfinding/PriorityQueue.js | 1 + scripts/pathfinding/Token.js | 4 --- scripts/pathfinding/Wall.js | 29 ----------------- 5 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 scripts/pathfinding/CanvasEdges.js diff --git a/scripts/patching.js b/scripts/patching.js index 1d144fa..4147023 100644 --- a/scripts/patching.js +++ b/scripts/patching.js @@ -8,12 +8,12 @@ import { Patcher } from "./Patcher.js"; import { PATCHES as PATCHES_Ruler } from "./Ruler.js"; import { PATCHES as PATCHES_Token } from "./Token.js"; import { PATCHES as PATCHES_ClientKeybindings } from "./ClientKeybindings.js"; -import { PATCHES as PATCHES_TokenPF } from "./pathfinding/Token.js"; import { PATCHES as PATCHES_DrawingConfig } from "./DrawingConfig.js"; - // Pathfinding import { PATCHES as PATCHES_Wall } from "./pathfinding/Wall.js"; +import { PATCHES as PATCHES_CanvasEdges } from "./pathfinding/CanvasEdges.js"; +import { PATCHES as PATCHES_TokenPF } from "./pathfinding/Token.js"; // Movement tracking import { PATCHES as PATCHES_TokenHUD } from "./token_hud.js"; @@ -26,6 +26,7 @@ const mergeObject = foundry.utils.mergeObject; const PATCHES = { ClientKeybindings: PATCHES_ClientKeybindings, ClientSettings: PATCHES_ClientSettings, + ["foundry.canvas.edges.CanvasEdges"]: PATCHES_CanvasEdges, DrawingConfig: PATCHES_DrawingConfig, Ruler: PATCHES_Ruler, Token: mergeObject(mergeObject(PATCHES_Token, PATCHES_TokenPF), PATCHES_TokenHUD), diff --git a/scripts/pathfinding/CanvasEdges.js b/scripts/pathfinding/CanvasEdges.js new file mode 100644 index 0000000..f77591f --- /dev/null +++ b/scripts/pathfinding/CanvasEdges.js @@ -0,0 +1,47 @@ +/* globals +canvas, +Wall +*/ +/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ +"use strict"; + +import { MODULE_ID } from "../const.js"; +import { SCENE_GRAPH } from "./WallTracer.js"; +import { Pathfinder } from "./pathfinding.js"; +import { Settings } from "../settings.js"; + +// Track wall creation, update, and deletion, constructing WallTracerEdges as we go. +// Use to update the pathfinding triangulation. + +export const PATCHES = {}; +PATCHES.PATHFINDING = {}; + +// ----- NOTE: Hooks ----- // + +/** + * Hook initializeEdges + * Set up the SCENE GRAPH with all wall edges. + */ +function initializeEdges() { + const t0 = performance.now(); + SCENE_GRAPH.clear(); + let numWalls = 0; + for ( const edge of canvas.edges.values() ) { + if ( edge.object instanceof Wall ) { + SCENE_GRAPH.addWall(edge.object); + numWalls += 1; + } + } + + Settings.setTokenBlocksPathfinding(); + const t1 = performance.now(); + + // Use the scene graph to initialize Pathfinder triangulation. + Pathfinder.initialize(); + const t2 = performance.now(); + + console.debug(`${MODULE_ID}|Tracked ${numWalls} walls in ${t1 - t0} ms.`); + console.debug(`${MODULE_ID}|Initialized pathfinding in ${t2 - t1} ms.`); +} + +PATCHES.PATHFINDING.HOOKS = { initializeEdges }; diff --git a/scripts/pathfinding/PriorityQueue.js b/scripts/pathfinding/PriorityQueue.js index 40e7c45..662ecde 100644 --- a/scripts/pathfinding/PriorityQueue.js +++ b/scripts/pathfinding/PriorityQueue.js @@ -1,5 +1,6 @@ // Priority queue using a heap // from https://www.digitalocean.com/community/tutorials/js-binary-heaps +import { radixSortObj } from "../geometry/RadixSort.js"; class Node { /** @param {object} */ diff --git a/scripts/pathfinding/Token.js b/scripts/pathfinding/Token.js index 15c91b1..bdad284 100644 --- a/scripts/pathfinding/Token.js +++ b/scripts/pathfinding/Token.js @@ -1,12 +1,8 @@ /* globals -canvas, -Hooks */ "use strict"; - /* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ -import { MODULE_ID } from "../const.js"; import { SCENE_GRAPH } from "./WallTracer.js"; import { Pathfinder } from "./pathfinding.js"; diff --git a/scripts/pathfinding/Wall.js b/scripts/pathfinding/Wall.js index 8317ace..397c30b 100644 --- a/scripts/pathfinding/Wall.js +++ b/scripts/pathfinding/Wall.js @@ -1,15 +1,11 @@ /* globals -canvas, -Hooks */ "use strict"; /* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ -import { MODULE_ID } from "../const.js"; import { SCENE_GRAPH } from "./WallTracer.js"; import { Pathfinder } from "./pathfinding.js"; -import { Settings } from "../settings.js"; // Track wall creation, update, and deletion, constructing WallTracerEdges as we go. // Use to update the pathfinding triangulation. @@ -17,31 +13,6 @@ import { Settings } from "../settings.js"; export const PATCHES = {}; PATCHES.PATHFINDING = {}; - -// When canvas is ready, the existing walls are not created, so must re-do here. -Hooks.on("canvasReady", async function() { - const t0 = performance.now(); - SCENE_GRAPH.clear(); - const walls = [ - ...canvas.walls.placeables, - ...canvas.walls.outerBounds, - ...canvas.walls.innerBounds - ]; - for ( const wall of walls ) SCENE_GRAPH.addWall(wall); - - // Must happen when the canvas is set so tokens (and walls) are available. - Settings.toggleTokenBlocksPathfinding(); - const t1 = performance.now(); - - // Use the scene graph to initialize Pathfinder triangulation. - Pathfinder.initialize(); - const t2 = performance.now(); - - console.debug(`${MODULE_ID}|Tracked ${walls.length} walls in ${t1 - t0} ms.`); - console.debug(`${MODULE_ID}|Initialized pathfinding in ${t2 - t1} ms.`); -}); - - /** * Hook createWall to update the scene graph and triangulation. * @param {Document} document The new Document instance which has been created From 1cdbce117d0c770623de1e84326a2a0aab531791 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 07:52:38 -0700 Subject: [PATCH 06/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Deprecation|Use=20`fou?= =?UTF-8?q?ndry.utils.getProperty`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/pathfinding/WallTracer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pathfinding/WallTracer.js b/scripts/pathfinding/WallTracer.js index 20c74dd..f930914 100644 --- a/scripts/pathfinding/WallTracer.js +++ b/scripts/pathfinding/WallTracer.js @@ -2,7 +2,7 @@ CanvasQuadtree, CONFIG, CONST, -getProperty, +foundry, PIXI, Token, Wall @@ -379,7 +379,7 @@ export class WallTracerEdge extends GraphEdge { // Don't block dead tokens (HP <= 0). const { tokenHPAttribute, pathfindingIgnoreStatuses } = CONFIG[MODULE_ID]; - const tokenHP = Number(getProperty(token, tokenHPAttribute)); + const tokenHP = Number(foundry.utils.getProperty(token, tokenHPAttribute)); if ( Number.isFinite(tokenHP) && tokenHP <= 0 ) return false; // Don't block tokens with certain status. From 99b15d515f1c60d241e70687d2836d1ef6d24674 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 15:35:07 -0700 Subject: [PATCH 07/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Pathfinding|Working=20?= =?UTF-8?q?pathfinding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed error when a canvas edge resulted in the triangle being undefined either cw or ccw. Return the edge as if you kept going all the way round. Don't test for blocking for triangles that are undefined. --- scripts/pathfinding/BorderTriangle.js | 64 ++++++++++++++++++++---- scripts/pathfinding/CanvasEdges.js | 3 +- scripts/pathfinding/Token.js | 37 ++++++++++++-- scripts/pathfinding/WallTracer.js | 70 ++++++++++++++++++++++++--- scripts/pathfinding/pathfinding.js | 38 ++++++++++++++- 5 files changed, 188 insertions(+), 24 deletions(-) diff --git a/scripts/pathfinding/BorderTriangle.js b/scripts/pathfinding/BorderTriangle.js index de3dfa4..6894160 100644 --- a/scripts/pathfinding/BorderTriangle.js +++ b/scripts/pathfinding/BorderTriangle.js @@ -15,6 +15,16 @@ import { PhysicalDistance } from "../PhysicalDistance.js"; import { Draw } from "../geometry/Draw.js"; import { WallTracerEdge } from "./WallTracer.js"; +const OTHER_DIRECTION = { + ccw: "cw", + cw: "ccw" +}; + +const OTHER_TRIANGLE = { + cwTriangle: "ccwTriangle", + ccwTriangle: "cwTriangle" +}; + /** * An edge that makes up the triangle-shaped polygon */ @@ -86,8 +96,14 @@ export class BorderEdge { */ findTriangleFromVertexKey(vertexKey, dir = "ccw") { const [a, b] = this.a.key === vertexKey ? [this.a, this.b] : [this.b, this.a]; - const cCCW = this._nonSharedVertex(this.ccwTriangle); - return (foundry.utils.orient2dFast(a, b, cCCW) > 0) ^ (dir !== "ccw") ? this.ccwTriangle : this.cwTriangle; + + if ( this.ccwTriangle ) { + const cCCW = this._nonSharedVertex(this.ccwTriangle); + return (foundry.utils.orient2dFast(a, b, cCCW) > 0) ^ (dir !== "ccw") ? this.ccwTriangle : this.cwTriangle; + } else { + const cCW = this._nonSharedVertex(this.cwTriangle); + return (foundry.utils.orient2dFast(a, b, cCW) < 0) ^ (dir !== "cw") ? this.cwTriangle : this.ccwTriangle; + } } /** @@ -171,12 +187,13 @@ export class BorderEdge { */ edgeBlocks(origin, elevation = 0) { if ( !origin ) { - if ( !this.ccwTriangle.center || !this.cwTriangle.center) { - console.warn("edgeBlocks|Triangle centers not defined."); - return false; - } - return this.edgeBlocks(this.ccwTriangle.center, elevation) - || this.edgeBlocks(this.cwTriangle.center, elevation); +// if ( !this.ccwTriangle || !this.cwTriangle || !this.ccwTriangle.center || !this.cwTriangle.center) { +// console.warn("edgeBlocks|Triangle centers not defined."); +// return false; +// } + const ccwBlocks = this.ccwTriangle ? this.edgeBlocks(this.ccwTriangle.center, elevation) : false; + const cwBlocks = this.cwTriangle ? this.edgeBlocks(this.cwTriangle.center, elevation) : false; + return ccwBlocks || cwBlocks; } const { moveToken, tokenBlockType } = this.constructor; @@ -199,7 +216,18 @@ export class BorderEdge { const otherEndpoint = !this.endpointKeys.has(aTri.key) ? aTri : !this.endpointKeys.has(bTri.key) ? bTri : cTri; + + // Debugging + if ( !this.endpointKeys.has(aTri.key) + && !this.endpointKeys.has(bTri.key) + && !this.endpointKeys.has(cTri.key) ) console.error(`Triangle ${triangle.id} keys not found ${aTri.key}, ${bTri.key}, ${cTri.key}`, this); + const orient2d = foundry.utils.orient2dFast; + const oABE = orient2d(a, b, otherEndpoint); + + // Debugging + if ( oABE === 0 ) console.error(`Triangle ${triangle.id} collinear to this edge at ${otherEndpoint.x},${otherEndpoint.y}`, this); + if ( orient2d(a, b, otherEndpoint) > 0 ) this.ccwTriangle = triangle; else this.cwTriangle = triangle; } @@ -215,6 +243,7 @@ export class BorderEdge { vertexBlocks(vertexKey, elevation = 0) { const iter = this.sharedVertexEdges(vertexKey); for ( const edge of iter ) { + // if ( !edge.ccwTriangle || !edge.cwTriangle ) console.warn("vertexBlocks|Edge triangles not defined."); // Debugging. if ( edge === this ) continue; // Could break here b/c this edge implicitly is always last. if ( edge.edgeBlocks(undefined, elevation) ) return true; } @@ -247,9 +276,24 @@ export class BorderEdge { * @param {string} [direction] Either ccw or c. * @returns {BorderEdge} */ - _nextEdge(vertexKey, dir = "ccw") { + _nextEdge(vertexKey, dir = "ccw", _recurse = true) { const tri = this.findTriangleFromVertexKey(vertexKey, dir); - return Object.values(tri.edges).find(e => e !== this && e.endpointKeys.has(vertexKey)); + if ( tri ) return Object.values(tri.edges).find(e => e !== this && e.endpointKeys.has(vertexKey)); + + // Edge is at a border, vertex at the corner of the border. + // Need to run the opposite direction until we get undefined in that direction. + if ( !_recurse ) return null; + const maxIter = 100; + let iter = 0; + let edge = this; + let prevEdge; + const otherDir = OTHER_DIRECTION[dir]; + do { + prevEdge = edge; + iter += 1; + edge = prevEdge._nextEdge(vertexKey, otherDir, false); + } while ( iter < maxIter && edge && edge !== this ); + return prevEdge; } /** diff --git a/scripts/pathfinding/CanvasEdges.js b/scripts/pathfinding/CanvasEdges.js index f77591f..2774451 100644 --- a/scripts/pathfinding/CanvasEdges.js +++ b/scripts/pathfinding/CanvasEdges.js @@ -30,7 +30,8 @@ function initializeEdges() { if ( edge.object instanceof Wall ) { SCENE_GRAPH.addWall(edge.object); numWalls += 1; - } + } else if ( edge.type === "outerBounds" + || edge.type === "innerBounds" ) SCENE_GRAPH.addCanvasEdge(edge); } Settings.setTokenBlocksPathfinding(); diff --git a/scripts/pathfinding/Token.js b/scripts/pathfinding/Token.js index bdad284..bbc24fb 100644 --- a/scripts/pathfinding/Token.js +++ b/scripts/pathfinding/Token.js @@ -5,6 +5,7 @@ import { SCENE_GRAPH } from "./WallTracer.js"; import { Pathfinder } from "./pathfinding.js"; +import { log } from "../util.js"; // Track wall creation, update, and deletion, constructing WallTracerEdges as we go. // Use to update the pathfinding triangulation. @@ -34,8 +35,38 @@ function updateToken(document, changes, _options, _userId) { // Only update the edges if the coordinates have changed. if ( !(Object.hasOwn(changes, "x") || Object.hasOwn(changes, "y")) ) return; + log(`updateToken hook|token moved.`); + +// // Easiest approach is to trash the edges for the wall and re-create them. +// SCENE_GRAPH.removeToken(document.id); +// +// /* Debugging: None of the edges should have this token. +// if ( CONFIG[MODULE_ID].debug ) { +// const token = document.object; +// SCENE_GRAPH.edges.forEach((edge, key) => { +// if ( edge.objects.has(token) ) console.debug(`Edge ${key} has ${token.name} ${token.id} after deletion.`); +// }) +// } +// */ +// +// SCENE_GRAPH.addToken(document.object); +// +// // Need to re-do the triangulation because the change to the wall could have added edges if intersected. +// Pathfinder.dirty = true; +} + +/** + * Hook refresh token to update the scene graph and triangulation. + * Cannot use updateToken hook b/c the token position is not correctly updated by that point. + * @param {PlaceableObject} object The object instance being refreshed + */ +function refreshToken(object, flags) { + if ( !flags.refreshPosition || object.isPreview ) return; + + log(`refreshToken hook|original token moved.`); + // Easiest approach is to trash the edges for the wall and re-create them. - SCENE_GRAPH.removeToken(document.id); + SCENE_GRAPH.removeToken(object.id); /* Debugging: None of the edges should have this token. if ( CONFIG[MODULE_ID].debug ) { @@ -46,7 +77,7 @@ function updateToken(document, changes, _options, _userId) { } */ - SCENE_GRAPH.addToken(document.object); + SCENE_GRAPH.addToken(object); // Need to re-do the triangulation because the change to the wall could have added edges if intersected. Pathfinder.dirty = true; @@ -63,4 +94,4 @@ function deleteToken(document, _options, _userId) { Pathfinder.dirty = true; } -PATCHES.PATHFINDING_TOKENS.HOOKS = { createToken, updateToken, deleteToken }; +PATCHES.PATHFINDING_TOKENS.HOOKS = { createToken, updateToken, deleteToken, refreshToken }; diff --git a/scripts/pathfinding/WallTracer.js b/scripts/pathfinding/WallTracer.js index f930914..755818d 100644 --- a/scripts/pathfinding/WallTracer.js +++ b/scripts/pathfinding/WallTracer.js @@ -211,12 +211,17 @@ export class WallTracerEdge extends GraphEdge { */ get tokens() { return this.objects.filter(o => o instanceof Token); } + /** + * Filter set for CanvasEdges + */ + get canvasEdges() { return this.objects.filter(o => o instanceof foundry.canvas.edges.Edge); } + /** * Construct an edge. * To be used instead of constructor in most cases. * @param {Point} edgeA First object edge endpoint * @param {Point} edgeB Other object edge endpoint - * @param {PlaceableObject} [object[]] Object(s) that contains this edge + * @param {PlaceableObject[]} [objects] Object(s) that contains this edge, if any * @param {number} [tA=0] Where the A endpoint of this edge falls on the object * @param {number} [tB=1] Where the B endpoint of this edge falls on the object * @returns {SegmentTracerEdge} @@ -232,7 +237,7 @@ export class WallTracerEdge extends GraphEdge { const B = new WallTracerVertex(eB.x, eB.y); const dist = PIXI.Point.distanceSquaredBetween(A.point, B.point); const edge = new this(A, B, dist); - if ( objects ) objects.forEach(obj => edge.objects.add(obj)); + objects.forEach(obj => edge.objects.add(obj)); return edge; } @@ -244,6 +249,14 @@ export class WallTracerEdge extends GraphEdge { */ static fromWall(wall) { return this.fromObject(wall.edge.a, wall.edge.b, [wall]); } + /** + * Construct an edge from a Canvas Edge + * Used for boundary walls. + * @param {Edge} edge Canvas edge + * @returns {WallTracerEdge} + */ + static fromCanvasEdge(edge) { return this.fromObject(edge.a, edge.b, [edge]); } + /** * Construct an array of edges form the constrained token border. * To be used instead of constructor in most cases. @@ -331,9 +344,9 @@ export class WallTracerEdge extends GraphEdge { */ edgeBlocks(origin, moveToken, tokenBlockType, elevation = 0) { return this.objects.some(obj => - (obj instanceof Wall) ? this.constructor.wallBlocks(obj, origin, elevation) - : (obj instanceof Token) ? this.constructor.tokenEdgeBlocks(obj, moveToken, tokenBlockType, elevation) - : false); + (obj instanceof Wall) ? this.constructor.wallBlocks(obj, origin, elevation) + : (obj instanceof Token) ? this.constructor.tokenEdgeBlocks(obj, moveToken, tokenBlockType, elevation) + : false); } /** @@ -431,6 +444,12 @@ export class WallTracer extends Graph { */ objectEdges = new Map(); + /** + * Set of canvas edge ids represented in this graph. + * @type {Set} + */ + canvasEdgeIds = new Set(); + /** * Set of wall ids represented in this graph. * @type {Set} @@ -454,6 +473,7 @@ export class WallTracer extends Graph { this.objectEdges.clear(); this.wallIds.clear(); this.tokenIds.clear(); + this.canvasEdgeIds.clear(); super.clear(); } @@ -527,7 +547,8 @@ export class WallTracer extends Graph { // If no collisions, then a single edge can represent this edge object. const collisions = this.findEdgeCollisions(edgeA, edgeB); if ( !collisions.size ) { - const edge = WallTracerEdge.fromObjects(edgeA, edgeB, [object]); + const objects = object ? [object] : []; + const edge = WallTracerEdge.fromObjects(edgeA, edgeB, objects); this.addEdge(edge); return; } @@ -539,7 +560,7 @@ export class WallTracer extends Graph { * @param {SegmentIntersection[]} collisions * @param {PIXI.Point} edgeA First edge endpoint * @param {PIXI.Point} edgeB Other edge endpoint - * @param {Wall|Token} object + * @param {Wall|Token|Edge} object */ #processCollisions(collisions, edgeA, edgeB, object) { // Sort the keys so we can progress from A --> B along the edge. @@ -656,6 +677,19 @@ export class WallTracer extends Graph { this.wallIds.add(wallId); } + /** + * Split the canvas edge by edges already in this graph. + * @param {Edge} edge Canvas edge to convert to edge(s) + */ + addCanvasEdge(edge) { + const id = edge.id; + if ( this.edges.has(id) ) return; + + // Construct a new canvas edge set + this.addObjectEdge(PIXI.Point.fromObject(edge.a), PIXI.Point.fromObject(edge.b), edge); + this.canvasEdgeIds.add(id); + } + /** * Remove all associated edges with this edge set and object id. * @param {string} id Id of the edge object to remove @@ -685,7 +719,6 @@ export class WallTracer extends Graph { // This will remove unnecessary vertices and recombine edges. if ( _recurse ) { const remainingObjects = edgesArr.reduce((acc, curr) => acc = acc.union(curr.objects), new Set()); - if ( !remainingObjects.size ) return; remainingObjects.forEach(obj => obj instanceof Wall ? this.removeWall(obj.id, false) : this.removeToken(obj.id, false)); remainingObjects.forEach(obj => obj instanceof Wall @@ -713,6 +746,16 @@ export class WallTracer extends Graph { return this.removeObject(tokenId, _recurse); } + /** + * Remove all associated edges with this canvas edge. + * @param {string|Edge} edgeId + */ + removeCanvasEdge(edgeId, _recurse = true) { + if ( edgeId instanceof foundry.canvas.edges.Edge ) edgeId = edgeId.id; + this.canvasEdgeIds.delete(edgeId); + return this.removeObject(edgeId, _recurse); + } + /** * Locate collision points for any edges that collide with this edge. * Skips edges that simply share a single endpoint. @@ -767,6 +810,17 @@ canvas.tokens.placeables.filter(t => !SCENE_GRAPH.tokenIds.has(t.id)) // do we have all the walls? canvas.walls.placeables.filter(w => !SCENE_GRAPH.wallIds.has(w.id)) +// Every object edge id should be in one of the three sets and vice versa. +objectEdgeKeys = new Set(SCENE_GRAPH.objectEdges.keys()) +SCENE_GRAPH.canvasEdgeIds.difference(objectEdgeKeys).size +SCENE_GRAPH.tokenIds.difference(objectEdgeKeys).size +SCENE_GRAPH.wallIds.difference(objectEdgeKeys).size +objectEdgeKeys.equals(SCENE_GRAPH.canvasEdgeIds.union(SCENE_GRAPH.tokenIds).union(SCENE_GRAPH.wallIds)) + + + + + // Draw all edges SCENE_GRAPH.drawEdges() diff --git a/scripts/pathfinding/pathfinding.js b/scripts/pathfinding/pathfinding.js index f5c3cd3..1ea136d 100644 --- a/scripts/pathfinding/pathfinding.js +++ b/scripts/pathfinding/pathfinding.js @@ -26,6 +26,7 @@ api = game.modules.get("elevationruler").api Pathfinder = api.pathfinding.Pathfinder SCENE_GRAPH = api.pathfinding.SCENE_GRAPH BorderEdge = api.pathfinding.BorderEdge +BorderTriangle = api.pathfinding.BorderTriangle PriorityQueueArray = api.pathfinding.PriorityQueueArray; PriorityQueue = api.pathfinding.PriorityQueue; @@ -44,14 +45,47 @@ pq.enqueue({"C": 3}, 3); pq.enqueue({"B": 2}, 2); pq.data +// Test SCENE_GRAPH +SCENE_GRAPH.drawEdges() + +// Ensure the token edges get updated after moving +SCENE_GRAPH.drawEdges() + // Test pathfinding Pathfinder.initialize() - Draw.clearDrawings() - BorderEdge.moveToken = _token; + + Pathfinder.drawTriangles(); +for ( const tri of Pathfinder.borderTriangles ) { + for ( const edgeLabel of ["AB", "BC", "CA"] ) { + const edge = tri.edges[edgeLabel]; + if ( !(edge instanceof BorderEdge) ) { + console.log(`Tri ${tri.id}, edge ${edgeLabel} is not a BorderEdge.`); + continue; + } + if ( !(edge.ccwTriangle instanceof BorderTriangle) + || !(edge.cwTriangle instanceof BorderTriangle) ) console.log(`Tri ${tri.id}, edge ${edgeLabel} cw/ccw Triangle is not a BorderTriangle.`); + } +} + +edges = [] +for ( const edge of Pathfinder.triangleEdges ) { + if ( !edge.ccwTriangle ) { + console.log(`ccw Triangle is not a BorderTriangle.`, edge); + edges.push(edge); + } else if ( !edge.cwTriangle ) { + console.log(`cw Triangle is not a BorderTriangle.`, edge); + edges.push(edge); + } +} + + +pf = _token.elevationruler.pathfinder + + endPoint = _token.center From 53ba6b0188f46f03bd7740e558c5e1db7df0b734 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 16:15:46 -0700 Subject: [PATCH 08/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Ruler|Catch=20if=20way?= =?UTF-8?q?point=20never=20defined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index b37f1e8..afc3e3e 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -152,6 +152,10 @@ function update(wrapper, data) { function _addWaypoint(wrapper, point) { wrapper(point); + // In case the waypoint was never added. + if ( (this.state !== Ruler.STATES.STARTING) && (this.state !== Ruler.STATES.MEASURING ) ) return; + if ( !this.waypoints.length ) return; + // If shift was held, use the precise point. if ( this._unsnap ) { const lastWaypoint = this.waypoints.at(-1); From 2105b51ee0c1a64103fef39af110dc994f3e68ce Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 16:45:44 -0700 Subject: [PATCH 09/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Teleport|Switch=20to?= =?UTF-8?q?=20holding=20F=20down=20to=20trigger=20teleport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hitting the teleport key was not ideal b/c it interrupted the drag in weird ways, causing the drag to still continue after the teleport. Instead, use a modifier key when trigging the token move. Don't use arrow key because it screws up use of arrow keys on the canvas. Wrap _onMoveKeyDown to handle the event from the drag left drop. --- languages/en.json | 2 +- scripts/Ruler.js | 14 +++++++++++++- scripts/Token.js | 3 +-- scripts/settings.js | 14 +------------- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/languages/en.json b/languages/en.json index 77f8856..3d6d902 100644 --- a/languages/en.json +++ b/languages/en.json @@ -19,7 +19,7 @@ "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.keybindings.teleport.hint": "When measuring, hold this key when you release the dragged token or hit the spacebar 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.", diff --git a/scripts/Ruler.js b/scripts/Ruler.js index afc3e3e..77ae7f0 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -612,6 +612,17 @@ function _onMouseUp(wrapped, event) { return wrapped(event); } +/** + * Wrap Ruler.prototype._onMoveKeyDown + * If the teleport key is held, teleport the token. + * @param {KeyboardEventContext} context + */ +function _onMoveKeyDown(wrapped, context) { + const teleportKeys = new Set(game.keybindings.get(MODULE_ID, Settings.KEYBINDINGS.TELEPORT).map(binding => binding.key)); + if ( teleportKeys.intersects(game.keyboard.downKeys) ) this.segments.forEach(s => s.teleport = true); + wrapped(context); +} + PATCHES.BASIC.WRAPS = { _getMeasurementData, update, @@ -627,7 +638,8 @@ PATCHES.BASIC.WRAPS = { _onClickLeft, _onClickRight, _onMouseMove, - _canMove + _canMove, + _onMoveKeyDown }; PATCHES.BASIC.MIXES = { _animateMovement, _getMeasurementSegments, _onMouseUp }; diff --git a/scripts/Token.js b/scripts/Token.js index ba66125..2d5b906 100644 --- a/scripts/Token.js +++ b/scripts/Token.js @@ -76,8 +76,7 @@ async function _onDragLeftDrop(wrapped, event) { // } // ruler._state = Ruler.STATES.MOVING; // Do NOT set state to MOVING here in v12, as it will break the canvas. - await ruler.moveToken(); - ruler._onMouseUp(event); + ruler._onMoveKeyDown(event); // Movement is async here but not awaited in _onMoveKeyDown. } /** diff --git a/scripts/settings.js b/scripts/settings.js index e56da30..549c804 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -302,20 +302,8 @@ export class Settings extends ModuleSettingsAbstract { name: game.i18n.localize(`${MODULE_ID}.keybindings.${KEYBINDINGS.TELEPORT}.name`), hint: game.i18n.localize(`${MODULE_ID}.keybindings.${KEYBINDINGS.TELEPORT}.hint`), editable: [ - { key: "ArrowRight" } + { key: "keyF" } ], - 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 }); } From cf58847dff61e2e2108fe83c7102686c759ee00b Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 17:01:39 -0700 Subject: [PATCH 10/15] =?UTF-8?q?=F0=9F=90=9B=20fix|User|Fix=20getting=20i?= =?UTF-8?q?nformation=20from=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changelog.md | 5 +++++ scripts/Ruler.js | 2 ++ scripts/settings.js | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 350c433..ea3e851 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,8 @@ +# 0.9.2 +Fix pathfinding. +Change the keybind for teleport to "F" (fast-forward) to avoid collision with arrow-key usage on the canvas. Switch to requiring "F" to be held when the user triggers the move to get teleport, to avoid weirdness with the drag still being active when using a separate trigger key. +Catch error if waypoint is not added in `_addWaypoint`. + # 0.9.1 Fix errors when using the ruler on gridless scenes. Correct speed highlighting on gridless scenes. diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 77ae7f0..83626c6 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -103,6 +103,7 @@ function _getMeasurementData(wrapper) { return newObj; }); + myObj._userElevationIncrements = this._userElevationIncrements; myObj._unsnap = this._unsnap; myObj._unsnappedOrigin = this._unsnappedOrigin; @@ -117,6 +118,7 @@ function _getMeasurementData(wrapper) { * Retrieve the current snap status. */ function update(wrapper, data) { + if ( !data || (data.state === Ruler.STATES.INACTIVE) ) return wrapper(data); const myData = data[MODULE_ID]; if ( !myData ) return wrapper(data); // Just in case. diff --git a/scripts/settings.js b/scripts/settings.js index 549c804..12e7558 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -302,7 +302,7 @@ export class Settings extends ModuleSettingsAbstract { name: game.i18n.localize(`${MODULE_ID}.keybindings.${KEYBINDINGS.TELEPORT}.name`), hint: game.i18n.localize(`${MODULE_ID}.keybindings.${KEYBINDINGS.TELEPORT}.hint`), editable: [ - { key: "keyF" } + { key: "KeyF" } ], precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL }); From f30d10923a01c0370e559e1286ce2109ca17f0e0 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 17:14:04 -0700 Subject: [PATCH 11/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Speed|Fix=20display=20?= =?UTF-8?q?of=20maximum=20speed=20on=20others?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changelog.md | 1 + scripts/Ruler.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index ea3e851..1c90aef 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Fix pathfinding. Change the keybind for teleport to "F" (fast-forward) to avoid collision with arrow-key usage on the canvas. Switch to requiring "F" to be held when the user triggers the move to get teleport, to avoid weirdness with the drag still being active when using a separate trigger key. Catch error if waypoint is not added in `_addWaypoint`. +Correct error when sending ruler data from one user to another. # 0.9.1 Fix errors when using the ruler on gridless scenes. diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 83626c6..11183dc 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -131,6 +131,8 @@ function update(wrapper, data) { // Reconstruct segments. if ( myData._segments ) this.segments = myData._segments.map(s => { s.ray = new Ray3d(s.ray.A, s.ray.B); + if ( s.speed ) s.speed.color = s.speed.name === "Maximum" + ? MaximumSpeedCategory.color : Color.from(s.speed.color); return s; }); @@ -406,7 +408,7 @@ function _computeTokenSpeed() { // 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}`); + if ( this.segments[0].moveDistance > 60 ) log(`${this.segments[0].moveDistance}`); // Progress through each speed attribute in turn. const categoryIter = [...SPEED.CATEGORIES, MaximumSpeedCategory].values(); From a4053e35353828ec23ceddbf129c100e00cccda6 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 17:26:40 -0700 Subject: [PATCH 12/15] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docs|Notes|Comment?= =?UTF-8?q?=20out=20debug,=20notes=20on=20animateSegment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 10 ++++++---- scripts/segments.js | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 11183dc..61ee1fb 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -405,10 +405,12 @@ function _computeTokenSpeed() { 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}`); + if ( CONFIG[MODULE_ID].debug ) { + 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(); diff --git a/scripts/segments.js b/scripts/segments.js index bc78a5a..3a4d2e7 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -229,6 +229,8 @@ export async function _animateSegment(token, segment, destination) { 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. + // Doesn't work because _animateMovement stops the movement if the token does not make it to the + // next waypoint. // 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; From 7e23d1b33ad175ddbc6382b58e24f88a77ad7a20 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 18:49:37 -0700 Subject: [PATCH 13/15] =?UTF-8?q?=F0=9F=92=A1=20refactor|SpeedColor|Move?= =?UTF-8?q?=20Maximum=20to=20a=20speed=20category?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify speed color by moving maximum to a speed category. Should now be able to set colors for specific users in CONFIG. --- scripts/Ruler.js | 23 ++++++++++------------- scripts/const.js | 24 +++++++----------------- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 61ee1fb..fa9c161 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -1,5 +1,6 @@ /* globals canvas, +Color, CONFIG, CONST, foundry, @@ -15,7 +16,7 @@ export const PATCHES = {}; PATCHES.BASIC = {}; PATCHES.SPEED_HIGHLIGHTING = {}; -import { SPEED, MODULE_ID, MaximumSpeedCategory } from "./const.js"; +import { SPEED, MODULE_ID } from "./const.js"; import { Settings } from "./settings.js"; import { Ray3d } from "./geometry/3d/Ray3d.js"; import { Point3d } from "./geometry/3d/Point3d.js"; @@ -100,6 +101,7 @@ function _getMeasurementData(wrapper) { B: s.ray.B }; newObj.label = Boolean(s.label); + newObj.speed ??= newObj.speed.name; return newObj; }); @@ -131,8 +133,7 @@ function update(wrapper, data) { // Reconstruct segments. if ( myData._segments ) this.segments = myData._segments.map(s => { s.ray = new Ray3d(s.ray.A, s.ray.B); - if ( s.speed ) s.speed.color = s.speed.name === "Maximum" - ? MaximumSpeedCategory.color : Color.from(s.speed.color); + s.speed ??= SPEED.CATEGORIES.find(s => s.name === s.speed); return s; }); @@ -413,14 +414,9 @@ function _computeTokenSpeed() { } // Progress through each speed attribute in turn. - const categoryIter = [...SPEED.CATEGORIES, MaximumSpeedCategory].values(); - const maxDistFn = (token, speedCategory, tokenSpeed) => { - if ( speedCategory.name === "Maximum" ) return Number.POSITIVE_INFINITY; - return SPEED.maximumCategoryDistance(token, speedCategory, tokenSpeed); - }; - + const categoryIter = [...SPEED.CATEGORIES].values(); let speedCategory = categoryIter.next().value; - let maxDistance = maxDistFn(token, speedCategory, tokenSpeed); + let maxDistance = SPEED.maximumCategoryDistance(token, speedCategory, tokenSpeed); // Determine which speed category we are starting with // Add in already moved combat distance and determine the starting category @@ -434,10 +430,11 @@ function _computeTokenSpeed() { while ( (segment = this.segments[s]) ) { // Skip speed categories that do not provide a distance larger than the last. - while ( speedCategory.name !== "Maximum" && maxDistance <= minDistance ) { + while ( speedCategory && maxDistance <= minDistance ) { speedCategory = categoryIter.next().value; - maxDistance = maxDistFn(token, speedCategory, tokenSpeed); + maxDistance = SPEED.maximumCategoryDistance(token, speedCategory, tokenSpeed); } + if ( !speedCategory ) speedCategory = SPEED.CATEGORIES.at(-1); segment.speed = speedCategory; let newPrevDiagonal = _measureSegment(segment, token, numPrevDiagonal); @@ -699,7 +696,7 @@ function decrementElevation() { * Move the token and stop the ruler measurement * @returns {boolean} False if the movement did not occur */ -async function teleport(context) { +async function teleport(_context) { if ( this._state !== this.constructor.STATES.MEASURING ) return false; if ( !this._canMove(this.token) ) return false; diff --git a/scripts/const.js b/scripts/const.js index 1ac80a9..027d3ba 100644 --- a/scripts/const.js +++ b/scripts/const.js @@ -97,6 +97,12 @@ const DashSpeedCategory = { multiplier: 2 }; +const MaximumSpeedCategory = { + Name: "Maximum", + color: Color.from(0xff0000), + multiplier: Number.POSITIVE_INFINITY +} + export const SPEED = { /** * Object of strings indicating where on the actor to locate the given attribute. @@ -110,13 +116,7 @@ export const SPEED = { * in the first category is the next category considered. * @type {SpeedCategory[]} */ - CATEGORIES: [WalkSpeedCategory, DashSpeedCategory], - - /** - * Color to use once all SpeedCategory distances have been exceeded. - * @type {Color} - */ - MAXIMUM_COLOR: Color.from(0xff0000), + CATEGORIES: [WalkSpeedCategory, DashSpeedCategory, MaximumSpeedCategory], // Use Font Awesome font unicode instead of basic unicode for displaying terrain symbol. @@ -134,14 +134,6 @@ export const SPEED = { terrainSymbol: "🥾" }; -export const MaximumSpeedCategory = { - name: "Maximum", - multiplier: Number.POSITIVE_INFINITY -}; - -Object.defineProperty(MaximumSpeedCategory, "color", { - get: () => SPEED.MAXIMUM_COLOR -}); /** * Given a token, get the maximum distance the token can travel for a given type. @@ -179,8 +171,6 @@ Hooks.once("init", function() { DashSpeedCategory.multiplier = defaultDashMultiplier(); }); - -/* eslint-disable no-multi-spaces */ export function defaultHPAttribute() { switch ( game.system.id ) { case "dnd5e": return "actor.system.attributes.hp.value"; From 26f844690438c03f86c56efe33e9246d0e15cbcf Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 21:29:41 -0700 Subject: [PATCH 14/15] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docs|Changelog|Notes?= =?UTF-8?q?=20re=20speed=20category=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 1c90aef..50789ec 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,7 @@ Fix pathfinding. Change the keybind for teleport to "F" (fast-forward) to avoid collision with arrow-key usage on the canvas. Switch to requiring "F" to be held when the user triggers the move to get teleport, to avoid weirdness with the drag still being active when using a separate trigger key. Catch error if waypoint is not added in `_addWaypoint`. Correct error when sending ruler data from one user to another. +Move Maximum speed category to `CONFIG.elevationruler.SPEED.CATEGORIES`. Should now be possible to define specific colors per user in the CONFIG, so long as category names are same. # 0.9.1 Fix errors when using the ruler on gridless scenes. From 0194bbb042d37835113cab8a48c75a0fd29c4611 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 31 May 2024 21:30:30 -0700 Subject: [PATCH 15/15] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docs|Module.json|Upd?= =?UTF-8?q?ate=20verified=20to=2012.325?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index b9cbbfe..5417691 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "manifestPlusVersion": "1.0.0", "compatibility": { "minimum": "12", - "verified": "12.324" + "verified": "12.325" }, "authors": [ {