From 7ac1c828b69622d252270eca28d28ada176eadaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20V=C3=B6gele?= Date: Sun, 30 Jan 2022 10:51:58 +0100 Subject: [PATCH] Perform pathfinding on grid-corner to grid-corner basis for tokens with size divisible by two (fixes #144) --- src/foundry_imports.js | 10 ++++++---- src/pathfinding.js | 34 ++++++++++++++++++++-------------- src/util.js | 4 ++++ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/foundry_imports.js b/src/foundry_imports.js index 4eca1d7..a9c90dc 100644 --- a/src/foundry_imports.js +++ b/src/foundry_imports.js @@ -1,12 +1,12 @@ import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js"; -import {getCenterFromGridPositionObj, getGridPositionFromPixels, getGridPositionFromPixelsObj} from "./foundry_fixes.js"; +import {getGridPositionFromPixels, getGridPositionFromPixelsObj, getPixelsFromGridPositionObj} from "./foundry_fixes.js"; import {Line} from "./geometry.js"; import {disableSnap, moveWithoutAnimation} from "./keybindings.js"; import {trackRays} from "./movement_tracking.js" import {findPath, isPathfindingEnabled} from "./pathfinding.js"; import {settingsKey} from "./settings.js"; import {recalculate} from "./socket.js"; -import {applyTokenSizeOffset, enumeratedZip, getSnapPointForEntity, getSnapPointForToken, getTokenShape, highlightTokenShape, sum} from "./util.js"; +import {applyTokenSizeOffset, enumeratedZip, getSnapPointForEntity, getSnapPointForToken, getSnapPointForTokenObj, getTokenShape, highlightTokenShape, sum} from "./util.js"; // This is a modified version of Ruler.moveToken from foundry 0.7.9 export async function moveEntities(draggedEntity, selectedEntities) { @@ -169,9 +169,11 @@ export function measure(destination, options={}) { this.dragRulerRemovePathfindingWaypoints(); if (isToken && isPathfindingEnabled()) { - let path = findPath(getGridPositionFromPixelsObj(this.waypoints[this.waypoints.length - 1]), getGridPositionFromPixelsObj(destination), this.waypoints); + const from = getGridPositionFromPixelsObj(this.waypoints[this.waypoints.length - 1]); + const to = getGridPositionFromPixelsObj(destination); + let path = findPath(from, to, this.draggedEntity, this.waypoints); if (path) { - path = path.map(point => getCenterFromGridPositionObj(point)) + path = path.map(point => getSnapPointForTokenObj(getPixelsFromGridPositionObj(point), this.draggedEntity)); // If the token is snapped to the grid, the first point of the path is already handled by the ruler if (path[0].x === this.waypoints[this.waypoints.length - 1].x && path[0].y === this.waypoints[this.waypoints.length - 1].y) diff --git a/src/pathfinding.js b/src/pathfinding.js index 97e82ae..35e533f 100644 --- a/src/pathfinding.js +++ b/src/pathfinding.js @@ -1,8 +1,8 @@ -import {getCenterFromGridPositionObj, getGridPositionFromPixelsObj} from "./foundry_fixes.js"; +import {getGridPositionFromPixelsObj, getPixelsFromGridPositionObj} from "./foundry_fixes.js"; import {togglePathfinding} from "./keybindings.js"; import {debugGraphics} from "./main.js"; import {settingsKey} from "./settings.js"; -import {iterPairs} from "./util.js"; +import {getSnapPointForTokenObj, iterPairs} from "./util.js"; let cachedNodes = undefined; let use5105 = false; @@ -15,16 +15,16 @@ export function isPathfindingEnabled() { return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding; } -export function findPath(from, to, previousWaypoints) { - const lastNode = calculatePath(from, to, previousWaypoints); +export function findPath(from, to, token, previousWaypoints) { + const lastNode = calculatePath(from, to, token, previousWaypoints); if (!lastNode) return null; - paintPathfindingDebug(lastNode); + paintPathfindingDebug(lastNode, token); const path = []; let currentNode = lastNode; while (currentNode) { // TODO Check if the distance doesn't change - if (path.length >= 2 && !canvas.walls.checkCollision(new Ray(getCenterFromGridPositionObj(currentNode.node), getCenterFromGridPositionObj(path[path.length - 2])))) + if (path.length >= 2 && !stepCollidesWithWall(currentNode.node, path[path.length - 2], token)) // Replace last waypoint if the current waypoint leads to a valid path path[path.length - 1] = {x: currentNode.node.x, y: currentNode.node.y}; else @@ -38,7 +38,7 @@ export function wipePathfindingCache() { cachedNodes = undefined; } -function getNode(pos, initialize=true) { +function getNode(pos, token, initialize=true) { pos = {layer: 0, ...pos}; // Copy pos and set pos.layer to the default value if it's unset if (!cachedNodes) cachedNodes = new Array(2); @@ -57,12 +57,12 @@ function getNode(pos, initialize=true) { if (neighborPos.x < 0 || neighborPos.y < 0) continue; // TODO Work with pixels instead of grid locations - if (!canvas.walls.checkCollision(new Ray(getCenterFromGridPositionObj(pos), getCenterFromGridPositionObj(neighborPos)))) { + if (!stepCollidesWithWall(pos, neighborPos, token)) { const isDiagonal = node.x !== neighborPos.x && node.y !== neighborPos.y && canvas.grid.type === CONST.GRID_TYPES.SQUARE; let targetLayer = pos.layer; if (use5105 && isDiagonal) targetLayer = 1 - targetLayer; - const neighbor = getNode({...neighborPos, layer: targetLayer}, false); + const neighbor = getNode({...neighborPos, layer: targetLayer}, token, false); // TODO We currently assume a cost of one or two for all transitions. Change this for difficult terrain support let edgeCost = 1; @@ -77,7 +77,7 @@ function getNode(pos, initialize=true) { return node; } -function calculatePath(from, to, previousWaypoints) { +function calculatePath(from, to, token, previousWaypoints) { if (game.system.id === "pf2e") use5105 = true; if (canvas.grid.diagonalRule === "5105") @@ -87,7 +87,7 @@ function calculatePath(from, to, previousWaypoints) { previousWaypoints = previousWaypoints.map(w => getGridPositionFromPixelsObj(w)); startLayer = calcNoDiagonals(previousWaypoints) % 2; } - const nextNodes = [{node: getNode({...to, layer: startLayer}), cost: 0, estimated: estimateCost(to, from), previous: null}]; + const nextNodes = [{node: getNode({...to, layer: startLayer}, token), cost: 0, estimated: estimateCost(to, from), previous: null}]; const previousNodes = new Set(); while (nextNodes.length > 0) { // Sort by estimated cost, high to low @@ -99,7 +99,7 @@ function calculatePath(from, to, previousWaypoints) { return currentNode; previousNodes.add(currentNode.node); for (const edge of currentNode.node.edges) { - const neighborNode = getNode(edge.target); + const neighborNode = getNode(edge.target, token); if (previousNodes.has(neighborNode)) continue; const neighbor = {node: neighborNode, cost: currentNode.cost + edge.cost, estimated: currentNode.cost + edge.cost + estimateCost(neighborNode, from), previous: currentNode}; @@ -129,7 +129,13 @@ function estimateCost(pos, target) { return Math.max(Math.abs(pos.x - target.x), Math.abs(pos.y - target.y)); } -function paintPathfindingDebug(lastNode) { +function stepCollidesWithWall(from, to, token) { + const stepStart = getSnapPointForTokenObj(getPixelsFromGridPositionObj(from), token); + const stepEnd = getSnapPointForTokenObj(getPixelsFromGridPositionObj(to), token); + return canvas.walls.checkCollision(new Ray(stepStart, stepEnd)); +} + +function paintPathfindingDebug(lastNode, token) { if (!CONFIG.debug.dragRuler) return; @@ -137,7 +143,7 @@ function paintPathfindingDebug(lastNode) { let currentNode = lastNode; while (currentNode) { let text = new PIXI.Text(currentNode.cost.toFixed(0)); - let pixels = getCenterFromGridPositionObj(currentNode.node); + let pixels = getSnapPointForTokenObj(getPixelsFromGridPositionObj(currentNode.node), token); text.anchor.set(0.5, 1.0); text.x = pixels.x; text.y = pixels.y; diff --git a/src/util.js b/src/util.js index dd0eee1..f9bf828 100644 --- a/src/util.js +++ b/src/util.js @@ -86,6 +86,10 @@ export function getSnapPointForToken(x, y, token) { return new PIXI.Point(snapX, snapY); } +export function getSnapPointForTokenObj(pos, token) { + return getSnapPointForToken(pos.x, pos.y, token); +} + export function getSnapPointForMeasuredTemplate(x, y) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { return new PIXI.Point(x, y);