On 5/10/5, start pathfinding on the correct layer
This commit is contained in:
@@ -169,7 +169,7 @@ 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));
|
let path = findPath(getGridPositionFromPixelsObj(this.waypoints[this.waypoints.length - 1]), getGridPositionFromPixelsObj(destination), this.waypoints);
|
||||||
if (path) {
|
if (path) {
|
||||||
path = path.map(point => getCenterFromGridPositionObj(point))
|
path = path.map(point => getCenterFromGridPositionObj(point))
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import {recalculate} from "./socket.js";
|
|||||||
import {SpeedProvider} from "./speed_provider.js"
|
import {SpeedProvider} from "./speed_provider.js"
|
||||||
import {setSnapParameterOnOptions} from "./util.js";
|
import {setSnapParameterOnOptions} from "./util.js";
|
||||||
|
|
||||||
|
CONFIG.debug.dragRuler = false;
|
||||||
|
export let debugGraphics = undefined;
|
||||||
|
|
||||||
Hooks.once("init", () => {
|
Hooks.once("init", () => {
|
||||||
registerSettings()
|
registerSettings()
|
||||||
registerKeybindings()
|
registerKeybindings()
|
||||||
@@ -37,6 +40,8 @@ Hooks.once("ready", () => {
|
|||||||
performMigrations()
|
performMigrations()
|
||||||
checkDependencies();
|
checkDependencies();
|
||||||
Hooks.callAll("dragRuler.ready", SpeedProvider)
|
Hooks.callAll("dragRuler.ready", SpeedProvider)
|
||||||
|
if (CONFIG.debug.dragRuler)
|
||||||
|
debugGraphics = canvas.controls.addChild(new PIXI.Container());
|
||||||
})
|
})
|
||||||
|
|
||||||
Hooks.on("canvasReady", () => {
|
Hooks.on("canvasReady", () => {
|
||||||
|
|||||||
+45
-14
@@ -1,6 +1,8 @@
|
|||||||
import {getCenterFromGridPositionObj} from "./foundry_fixes.js";
|
import {getCenterFromGridPositionObj, getGridPositionFromPixelsObj} from "./foundry_fixes.js";
|
||||||
import {togglePathfinding} from "./keybindings.js";
|
import {togglePathfinding} from "./keybindings.js";
|
||||||
|
import {debugGraphics} from "./main.js";
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
|
import {iterPairs} from "./util.js";
|
||||||
|
|
||||||
let cachedNodes = undefined;
|
let cachedNodes = undefined;
|
||||||
let use5105 = false;
|
let use5105 = false;
|
||||||
@@ -13,10 +15,11 @@ export function isPathfindingEnabled() {
|
|||||||
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
|
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findPath(from, to) {
|
export function findPath(from, to, previousWaypoints) {
|
||||||
const lastNode = calculatePath(from, to);
|
const lastNode = calculatePath(from, to, previousWaypoints);
|
||||||
if (!lastNode)
|
if (!lastNode)
|
||||||
return null;
|
return null;
|
||||||
|
paintPathfindingDebug(lastNode);
|
||||||
const path = [];
|
const path = [];
|
||||||
let currentNode = lastNode;
|
let currentNode = lastNode;
|
||||||
while (currentNode) {
|
while (currentNode) {
|
||||||
@@ -35,19 +38,20 @@ export function wipePathfindingCache() {
|
|||||||
cachedNodes = undefined;
|
cachedNodes = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNode(pos, layer=0, initialize=true) {
|
function getNode(pos, initialize=true) {
|
||||||
|
pos = {layer: 0, ...pos}; // Copy pos and set pos.layer to the default value if it's unset
|
||||||
if (!cachedNodes)
|
if (!cachedNodes)
|
||||||
cachedNodes = new Map();
|
cachedNodes = new Map();
|
||||||
let cachedLayer = cachedNodes.get(layer);
|
let cachedLayer = cachedNodes.get(pos.layer);
|
||||||
if (!cachedLayer) {
|
if (!cachedLayer) {
|
||||||
// TODO Check if ceil is the right thing to do here
|
// TODO Check if ceil is the right thing to do here
|
||||||
cachedLayer = new Array(Math.ceil(canvas.dimensions.sceneHeight / canvas.dimensions.size));
|
cachedLayer = new Array(Math.ceil(canvas.dimensions.sceneHeight / canvas.dimensions.size));
|
||||||
cachedNodes.set(layer, cachedLayer);
|
cachedNodes.set(pos.layer, cachedLayer);
|
||||||
}
|
}
|
||||||
if (!cachedLayer[pos.y])
|
if (!cachedLayer[pos.y])
|
||||||
cachedLayer[pos.y] = new Array(Math.ceil(canvas.dimensions.sceneWidth / canvas.dimensions.size));
|
cachedLayer[pos.y] = new Array(Math.ceil(canvas.dimensions.sceneWidth / canvas.dimensions.size));
|
||||||
if (!cachedLayer[pos.y][pos.x]) {
|
if (!cachedLayer[pos.y][pos.x]) {
|
||||||
cachedLayer[pos.y][pos.x] = {x: pos.x, y: pos.y, layer: layer};
|
cachedLayer[pos.y][pos.x] = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = cachedLayer[pos.y][pos.x];
|
const node = cachedLayer[pos.y][pos.x];
|
||||||
@@ -57,16 +61,16 @@ function getNode(pos, layer=0, initialize=true) {
|
|||||||
// 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 (!canvas.walls.checkCollision(new Ray(getCenterFromGridPositionObj(pos), getCenterFromGridPositionObj(neighborPos)))) {
|
||||||
const isDiagonal = node.x !== neighborPos.x && node.y !== neighborPos.y;
|
const isDiagonal = node.x !== neighborPos.x && node.y !== neighborPos.y;
|
||||||
let targetLayer = layer;
|
let targetLayer = pos.layer;
|
||||||
if (use5105 && isDiagonal)
|
if (use5105 && isDiagonal)
|
||||||
targetLayer = 1 - targetLayer;
|
targetLayer = 1 - targetLayer;
|
||||||
const neighbor = getNode(neighborPos, targetLayer, false);
|
const neighbor = getNode({...neighborPos, layer: targetLayer}, false);
|
||||||
// TODO We currently assume a cost of one for all transitions. Change this for 5/10/5 or difficult terrain support
|
// TODO We currently assume a cost of one for all transitions. Change this for 5/10/5 or difficult terrain support
|
||||||
|
|
||||||
let edgeCost = 1;
|
let edgeCost = 1;
|
||||||
if (isDiagonal) {
|
if (isDiagonal) {
|
||||||
// We charge 0.0001 more for edges to avoid unnecessary diagonal steps
|
// We charge 0.0001 more for edges to avoid unnecessary diagonal steps
|
||||||
edgeCost = layer === 0 ? 1.0001 : 2;
|
edgeCost = targetLayer === 1 ? 1.0001 : 2;
|
||||||
}
|
}
|
||||||
node.edges.push({target: neighbor, cost: edgeCost});
|
node.edges.push({target: neighbor, cost: edgeCost});
|
||||||
}
|
}
|
||||||
@@ -84,14 +88,17 @@ function* neighbors(pos) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculatePath(from, to) {
|
function calculatePath(from, to, 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")
|
||||||
use5105 = true;
|
use5105 = true;
|
||||||
// On 5/10/5 it's possible that we'd need to start on layer 1 if there is a previous route
|
let startLayer = 0;
|
||||||
// However I cannot think of any case where not doing it would lead to a non-optimal path, so I've ommited that
|
if (use5105) {
|
||||||
const nextNodes = [{node: getNode(to), cost: 0, estimated: estimateCost(to, from), previous: null}];
|
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 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
|
||||||
@@ -121,6 +128,30 @@ function calculatePath(from, to) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calcNoDiagonals(waypoints) {
|
||||||
|
let diagonals = 0;
|
||||||
|
for (const [p1, p2] of iterPairs(waypoints)) {
|
||||||
|
diagonals += Math.min(Math.abs(p1.x - p2.x), Math.abs(p1.y - p2.y));
|
||||||
|
}
|
||||||
|
return diagonals;
|
||||||
|
}
|
||||||
|
|
||||||
function estimateCost(pos, target) {
|
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) {
|
||||||
|
if (CONFIG.debug.dragRuler) {
|
||||||
|
debugGraphics.removeChildren();
|
||||||
|
}
|
||||||
|
let currentNode = lastNode;
|
||||||
|
while (currentNode) {
|
||||||
|
let text = new PIXI.Text(currentNode.cost.toFixed(0));
|
||||||
|
let pixels = getCenterFromGridPositionObj(currentNode.node);
|
||||||
|
text.anchor.set(0.5, 1.0);
|
||||||
|
text.x = pixels.x;
|
||||||
|
text.y = pixels.y;
|
||||||
|
debugGraphics.addChild(text);
|
||||||
|
currentNode = currentNode.previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ export function* enumeratedZip(it1, it2) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* iterPairs(l) {
|
||||||
|
for (let i = 1;i < l.length;i++) {
|
||||||
|
yield [l[i - 1], l[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function sum(arr) {
|
export function sum(arr) {
|
||||||
return arr.reduce((a, b) => a + b, 0);
|
return arr.reduce((a, b) => a + b, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user