Compare commits

...

8 Commits

Author SHA1 Message Date
Manuel Vögele 9d5dccb504 Release v1.11.2 2022-02-01 11:35:46 +01:00
Manuel Vögele a252da620a Always allow the GM to use pathfinding 2022-02-01 11:33:33 +01:00
Johannes Loher 41c8979925 Add support for Dungeonslayers 4 (#146) 2022-02-01 10:32:54 +01:00
Manuel Vögele 39f9204fa7 Destroy labels that are no longer used 2022-02-01 02:58:55 +01:00
Manuel Vögele 10633e4e2a Add .gitignore 2022-01-30 14:28:02 +01:00
Manuel Vögele f3e1492edd Release v1.11.1 2022-01-30 10:55:10 +01:00
Manuel Vögele 7ac1c828b6 Perform pathfinding on grid-corner to grid-corner basis for tokens with size divisible by two (fixes #144) 2022-01-30 10:51:58 +01:00
Manuel Vögele 1972498b05 Avoid unnecessary diagonal calculation on hex grids 2022-01-30 10:16:19 +01:00
12 changed files with 76 additions and 37 deletions
+1
View File
@@ -0,0 +1 @@
foundry-*.js
+16
View File
@@ -1,3 +1,19 @@
## 1.11.2
### Bugfixes
- Fixed a memory leak that could cause the rule to slow down after using the pathfinding functionality for a while
### Misc
- GMs are now always allowed to use the pathfinding tool. The setting now only prevents players from using it.
### Compatibility
- Drag Ruler's generic speed provider is now aware of good defaults for Dungeonslayers 4
## 1.11.1
### Bugfixes
- Fixed a bug that would cause the pathfinding algorithm to make tokens of size 2 and 4 to take an unnecessary step
## 1.11.0
### New features
- Drag Ruler now supports pathfinding. Pressing the assigned button will automatically calculate the shortest route to the point you're dragging your token to and add the necessary waypoints to the ruler.
+2 -2
View File
@@ -48,8 +48,8 @@
},
"settings": {
"allowPathfinding": {
"name": "Wegfindung aktivieren",
"hint": "Aktiviert Drag Ruler's Wegfindungsfunktion. Bitte beachte, dass die Wegfindung Wege durch unerkundeten Nebel des Kriegs und Ätherische Wände berechnen kann. Dies kann deinen Spielern Geheimnisse lüften, von denen sie noch nicht erfahren sollten."
"name": "Wegfindung für Spieler erlauben",
"hint": "Erlaubt es Spielern die Wegfindungs zu benutzen. Bitte beachte, dass die Wegfindung Wege durch unerkundeten Nebel des Kriegs und Ätherische Wände berechnen kann. Dies kann deinen Spielern Geheimnisse lüften, von denen sie noch nicht erfahren sollten."
},
"alwaysShowSpeedForPCs": {
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen",
+2 -2
View File
@@ -48,8 +48,8 @@
},
"settings": {
"allowPathfinding": {
"name": "Enable pathfinding feature",
"hint": "Enables Drag Ruler's pathfinding functionality in this world. Be aware that pathfinding can route through unexplored fog of war and Ethereal Walls, which might reveal secrets to your players ahead of time."
"name": "Allow pathfinding for players",
"hint": "Allows players to use Drag Ruler's pathfinding functionality in this world. Be aware that pathfinding can route through unexplored fog of war and Ethereal Walls, which might reveal secrets to your players ahead of time."
},
"alwaysShowSpeedForPCs": {
"name": "Show PC speed to everyone",
+2 -2
View File
@@ -2,7 +2,7 @@
"name": "drag-ruler",
"title": "Drag Ruler",
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
"version": "1.11.0",
"version": "1.11.2",
"minimumCoreVersion" : "9.245",
"compatibleCoreVersion" : "9",
"authors": [
@@ -65,7 +65,7 @@
],
"socket": true,
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.11.0.zip",
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.11.2.zip",
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
+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)
+8 -9
View File
@@ -52,15 +52,14 @@ export function registerKeybindings() {
precedence: -1,
});
if (game.settings.get(settingsKey, "allowPathfinding")) {
game.keybindings.register(settingsKey, "togglePathfinding", {
name: "drag-ruler.keybindings.togglePathfinding.name",
hint: "drag-ruler.keybindings.togglePathfinding.hint",
onDown: handleTogglePathfinding,
onUp: handleTogglePathfinding,
precedence: -1,
});
}
game.keybindings.register(settingsKey, "togglePathfinding", {
name: "drag-ruler.keybindings.togglePathfinding.name",
hint: "drag-ruler.keybindings.togglePathfinding.hint",
onDown: handleTogglePathfinding,
onUp: handleTogglePathfinding,
precedence: -1,
restricted: !game.settings.get(settingsKey, "allowPathfinding"),
});
}
function handleDeleteWaypoint() {
+22 -16
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;
@@ -10,21 +10,21 @@ let use5105 = false;
export function isPathfindingEnabled() {
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
return false;
if (!game.settings.get(settingsKey, "allowPathfinding"))
if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding"))
return false;
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,17 +77,17 @@ 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")
use5105 = true;
let startLayer = 0;
if (use5105) {
if (use5105 && canvas.grid.type === CONST.GRID_TYPES.SQUARE) {
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;
+1 -1
View File
@@ -116,7 +116,7 @@ export function extendRuler() {
}
dragRulerRemovePathfindingWaypoints() {
this.waypoints.filter(waypoint => waypoint.isPathfinding).forEach(_ => this.labels.removeChild(this.labels.children.pop()));
this.waypoints.filter(waypoint => waypoint.isPathfinding).forEach(_ => this.labels.removeChild(this.labels.children[this.labels.children.length - 1]).destroy());
this.waypoints = this.waypoints.filter(waypoint => !waypoint.isPathfinding);
}
+2 -1
View File
@@ -1,5 +1,6 @@
import {availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider} from "./api.js";
import {SpeedProvider} from "./speed_provider.js"
import { early_isGM } from "./util.js";
export const settingsKey = "drag-ruler";
@@ -96,7 +97,7 @@ export function registerSettings() {
name: "drag-ruler.settings.autoPathfinding.name",
hint: "drag-ruler.settings.autoPathfinding.hint",
scpoe: "client",
config: true,
config: early_isGM(),
type: Boolean,
defualt: false,
});
+3
View File
@@ -17,6 +17,8 @@ export function getDefaultSpeedAttribute() {
return "actor.data.data.movement.walk.value";
case "swade":
return "actor.data.data.stats.speed.adjusted";
case "ds4":
return "actor.data.data.combatValues.movement.total";
}
return ""
}
@@ -32,6 +34,7 @@ export function getDefaultDashMultiplier() {
case "D35E":
case "sfrpg":
case "shadowrun5e":
case "ds4":
return 2
case "CoC7":
return 5;
+11
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);
@@ -268,3 +272,10 @@ export function getMeasurePosition() {
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
return measurePosition;
}
// isGM function for use during loading when game.user isn't available yet
export function early_isGM() {
const level = game.data.users.find(u => u._id == game.data.userId).role;
const gmLevel = CONST.USER_ROLES.ASSISTANT;
return level >= gmLevel;
}