Perform pathfinding on grid-corner to grid-corner basis for tokens with size divisible by two (fixes #144)
This commit is contained in:
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user