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 {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 {Line} from "./geometry.js";
import {disableSnap, moveWithoutAnimation} from "./keybindings.js"; import {disableSnap, moveWithoutAnimation} from "./keybindings.js";
import {trackRays} from "./movement_tracking.js" import {trackRays} from "./movement_tracking.js"
import {findPath, isPathfindingEnabled} from "./pathfinding.js"; import {findPath, isPathfindingEnabled} from "./pathfinding.js";
import {settingsKey} from "./settings.js"; import {settingsKey} from "./settings.js";
import {recalculate} from "./socket.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 // This is a modified version of Ruler.moveToken from foundry 0.7.9
export async function moveEntities(draggedEntity, selectedEntities) { export async function moveEntities(draggedEntity, selectedEntities) {
@@ -169,9 +169,11 @@ export function measure(destination, options={}) {
this.dragRulerRemovePathfindingWaypoints(); this.dragRulerRemovePathfindingWaypoints();
if (isToken && isPathfindingEnabled()) { 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) { 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 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) 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 {togglePathfinding} from "./keybindings.js";
import {debugGraphics} from "./main.js"; import {debugGraphics} from "./main.js";
import {settingsKey} from "./settings.js"; import {settingsKey} from "./settings.js";
import {iterPairs} from "./util.js"; import {getSnapPointForTokenObj, iterPairs} from "./util.js";
let cachedNodes = undefined; let cachedNodes = undefined;
let use5105 = false; let use5105 = false;
@@ -15,16 +15,16 @@ export function isPathfindingEnabled() {
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding; return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
} }
export function findPath(from, to, previousWaypoints) { export function findPath(from, to, token, previousWaypoints) {
const lastNode = calculatePath(from, to, previousWaypoints); const lastNode = calculatePath(from, to, token, previousWaypoints);
if (!lastNode) if (!lastNode)
return null; return null;
paintPathfindingDebug(lastNode); paintPathfindingDebug(lastNode, token);
const path = []; const path = [];
let currentNode = lastNode; let currentNode = lastNode;
while (currentNode) { while (currentNode) {
// TODO Check if the distance doesn't change // 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 // Replace last waypoint if the current waypoint leads to a valid path
path[path.length - 1] = {x: currentNode.node.x, y: currentNode.node.y}; path[path.length - 1] = {x: currentNode.node.x, y: currentNode.node.y};
else else
@@ -38,7 +38,7 @@ export function wipePathfindingCache() {
cachedNodes = undefined; 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 pos = {layer: 0, ...pos}; // Copy pos and set pos.layer to the default value if it's unset
if (!cachedNodes) if (!cachedNodes)
cachedNodes = new Array(2); cachedNodes = new Array(2);
@@ -57,12 +57,12 @@ function getNode(pos, initialize=true) {
if (neighborPos.x < 0 || neighborPos.y < 0) if (neighborPos.x < 0 || neighborPos.y < 0)
continue; continue;
// TODO Work with pixels instead of grid locations // 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; const isDiagonal = node.x !== neighborPos.x && node.y !== neighborPos.y && canvas.grid.type === CONST.GRID_TYPES.SQUARE;
let targetLayer = pos.layer; let targetLayer = pos.layer;
if (use5105 && isDiagonal) if (use5105 && isDiagonal)
targetLayer = 1 - targetLayer; 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 // TODO We currently assume a cost of one or two for all transitions. Change this for difficult terrain support
let edgeCost = 1; let edgeCost = 1;
@@ -77,7 +77,7 @@ function getNode(pos, initialize=true) {
return node; return node;
} }
function calculatePath(from, to, previousWaypoints) { function calculatePath(from, to, token, previousWaypoints) {
if (game.system.id === "pf2e") if (game.system.id === "pf2e")
use5105 = true; use5105 = true;
if (canvas.grid.diagonalRule === "5105") if (canvas.grid.diagonalRule === "5105")
@@ -87,7 +87,7 @@ function calculatePath(from, to, previousWaypoints) {
previousWaypoints = previousWaypoints.map(w => getGridPositionFromPixelsObj(w)); previousWaypoints = previousWaypoints.map(w => getGridPositionFromPixelsObj(w));
startLayer = calcNoDiagonals(previousWaypoints) % 2; 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(); const previousNodes = new Set();
while (nextNodes.length > 0) { while (nextNodes.length > 0) {
// Sort by estimated cost, high to low // Sort by estimated cost, high to low
@@ -99,7 +99,7 @@ function calculatePath(from, to, previousWaypoints) {
return currentNode; return currentNode;
previousNodes.add(currentNode.node); previousNodes.add(currentNode.node);
for (const edge of currentNode.node.edges) { for (const edge of currentNode.node.edges) {
const neighborNode = getNode(edge.target); const neighborNode = getNode(edge.target, token);
if (previousNodes.has(neighborNode)) if (previousNodes.has(neighborNode))
continue; continue;
const neighbor = {node: neighborNode, cost: currentNode.cost + edge.cost, estimated: currentNode.cost + edge.cost + estimateCost(neighborNode, from), previous: currentNode}; 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)); 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) if (!CONFIG.debug.dragRuler)
return; return;
@@ -137,7 +143,7 @@ function paintPathfindingDebug(lastNode) {
let currentNode = lastNode; let currentNode = lastNode;
while (currentNode) { while (currentNode) {
let text = new PIXI.Text(currentNode.cost.toFixed(0)); 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.anchor.set(0.5, 1.0);
text.x = pixels.x; text.x = pixels.x;
text.y = pixels.y; text.y = pixels.y;
+4
View File
@@ -86,6 +86,10 @@ export function getSnapPointForToken(x, y, token) {
return new PIXI.Point(snapX, snapY); return new PIXI.Point(snapX, snapY);
} }
export function getSnapPointForTokenObj(pos, token) {
return getSnapPointForToken(pos.x, pos.y, token);
}
export function getSnapPointForMeasuredTemplate(x, y) { export function getSnapPointForMeasuredTemplate(x, y) {
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
return new PIXI.Point(x, y); return new PIXI.Point(x, y);