Perform pathfinding on grid-corner to grid-corner basis for tokens with size divisible by two (fixes #144)

This commit is contained in:
Manuel Vögele
2022-01-30 10:51:58 +01:00
parent 1972498b05
commit 7ac1c828b6
3 changed files with 30 additions and 18 deletions
+6 -4
View File
@@ -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)
+20 -14
View File
@@ -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;
+4
View File
@@ -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);