Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad3fdf4d18 | |||
| 58946096a6 | |||
| 22112ddfd5 | |||
| f59b0f9d2d | |||
| 443cfd4317 | |||
| fb3665d758 | |||
| 96cb690431 | |||
| fb7202d5f0 | |||
| fa581ddfc3 | |||
| 56e15be4ca | |||
| 1509307313 | |||
| 68885385ea | |||
| 6748617f6a | |||
| 9d5dccb504 | |||
| a252da620a | |||
| 41c8979925 | |||
| 39f9204fa7 | |||
| 10633e4e2a | |||
| f3e1492edd | |||
| 7ac1c828b6 | |||
| 1972498b05 | |||
| 4a5b9bbb11 | |||
| d467fe5bcf |
@@ -0,0 +1 @@
|
|||||||
|
foundry-*.js
|
||||||
@@ -1,3 +1,49 @@
|
|||||||
|
## 1.11.5
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that was causing Drag Ruler to spam useless warnings into the console (this was a regression introduced in 1.11.4)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.11.4
|
||||||
|
### Bugfixes
|
||||||
|
- When changing the measurement mode via a keybinding (toggle snaping or toggle pathfinding) the updated ruler will now be sent to other players immediately
|
||||||
|
- Fixed a bug that incorrectly showed a ruler to be snapped to other players despite the ruler not being snapped
|
||||||
|
- Fixed a bug that could cause a token to move to an incorrect location if the token was being dragged and dropped very rapidly
|
||||||
|
- Drag Ruler's token movement animations can now be properly waited for (this improves the interaction with modules like sequencer)
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
- Updated Spanish translation (thanks to Viriato139ac#342)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.11.3
|
||||||
|
### Bugfixes
|
||||||
|
- The setting to automatically start pathfinding is now visible to players again (this was a regression introduced in 1.11.2)
|
||||||
|
- Fixed a bug that would show the measurements of other players as if they were using the pathfinder, even if they were not using it.
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
- This feature is only available for gridded maps
|
||||||
|
- This feature can only be used if it's enabled by the GM in the module settings
|
||||||
|
- The routing algorithm *does not* take difficult terrain into account
|
||||||
|
|
||||||
|
|
||||||
## 1.10.3
|
## 1.10.3
|
||||||
### Compatibility
|
### Compatibility
|
||||||
- This release contains changes required to be compatible with Foundry 9.245
|
- This release contains changes required to be compatible with Foundry 9.245
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ Drag Ruler will work with all Foundry VTT game systems. However, some game syste
|
|||||||
|
|
||||||
The game systems that offer Drag Ruler integration are:
|
The game systems that offer Drag Ruler integration are:
|
||||||
- Cypher System (starting with version 1.13.0)
|
- Cypher System (starting with version 1.13.0)
|
||||||
|
- DnD5e (via the module [DnD5e Drag Ruler Integration](https://foundryvtt.com/packages/elevation-drag-ruler))
|
||||||
- GURPS 4th Edition Game Aid (Unofficial) (starting with version 0.9.1)
|
- GURPS 4th Edition Game Aid (Unofficial) (starting with version 0.9.1)
|
||||||
- Ironclaw Second Edition (starting with version 0.2.2)
|
- Ironclaw Second Edition (starting with version 0.2.2)
|
||||||
- Lancer (via the module [Lancer Speed Provider](https://foundryvtt.com/packages/lancer-speed-provider))
|
- Lancer (via the module [Lancer Speed Provider](https://foundryvtt.com/packages/lancer-speed-provider))
|
||||||
@@ -44,6 +45,7 @@ The game systems that offer Drag Ruler integration are:
|
|||||||
- Pathfinder 1 (starting with version 0.77.3)
|
- Pathfinder 1 (starting with version 0.77.3)
|
||||||
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
|
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
|
||||||
- Shadowrun 5th Edition (via the module [Drag Ruler Integration for Shadowrun 5E](https://foundryvtt.com/packages/drag-ruler-integration-for-shadowrun-5e))
|
- Shadowrun 5th Edition (via the module [Drag Ruler Integration for Shadowrun 5E](https://foundryvtt.com/packages/drag-ruler-integration-for-shadowrun-5e))
|
||||||
|
- Shadow of the Demon Lord (starting with version 1.7.15)
|
||||||
- Starfinder (via the module [Starfinder Drag Ruler Integration](https://foundryvtt.com/packages/starfinder-drag-ruler))
|
- Starfinder (via the module [Starfinder Drag Ruler Integration](https://foundryvtt.com/packages/starfinder-drag-ruler))
|
||||||
- Stargate RPG (starting with version 1.6.0)
|
- Stargate RPG (starting with version 1.6.0)
|
||||||
- Symbaroum (via the module [Symbaroum drag ruler integration](https://foundryvtt.com/packages/symbaroum-drag-ruler-integration))
|
- Symbaroum (via the module [Symbaroum drag ruler integration](https://foundryvtt.com/packages/symbaroum-drag-ruler-integration))
|
||||||
|
|||||||
@@ -40,9 +40,17 @@
|
|||||||
"moveWithoutAnimation": {
|
"moveWithoutAnimation": {
|
||||||
"name": "Token animation deaktivieren",
|
"name": "Token animation deaktivieren",
|
||||||
"hint": "Wenn diese Taste gedrückt wird, während ein Token fallen gelassen wird, bewegt es sich ohne Animation zum Zielort."
|
"hint": "Wenn diese Taste gedrückt wird, während ein Token fallen gelassen wird, bewegt es sich ohne Animation zum Zielort."
|
||||||
|
},
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "Wegfindung umschalten",
|
||||||
|
"hint": "Wenn diese Taste gedrückt gehalten wird, während ein Token gezogen wird, wird die Wegfindung vorübergehend aktiviert/deaktiviert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"allowPathfinding": {
|
||||||
|
"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": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen",
|
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen",
|
||||||
"hint": "Wenn diese Einstellung aktiviert ist wird die Färbung der hervorgehobenen Felder bei Spielercharakteren allen Spielern angezeigt, selbst wenn diese keinen Zugriff auf den Charakterbogen haben."
|
"hint": "Wenn diese Einstellung aktiviert ist wird die Färbung der hervorgehobenen Felder bei Spielercharakteren allen Spielern angezeigt, selbst wenn diese keinen Zugriff auf den Charakterbogen haben."
|
||||||
|
|||||||
@@ -40,9 +40,17 @@
|
|||||||
"moveWithoutAnimation": {
|
"moveWithoutAnimation": {
|
||||||
"name": "Disable token animation",
|
"name": "Disable token animation",
|
||||||
"hint": "When being held while dropping a token, the token will move to the target location without animating"
|
"hint": "When being held while dropping a token, the token will move to the target location without animating"
|
||||||
|
},
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "Toggle pathfinding",
|
||||||
|
"hint": "When being held while dragging a token, the pathfinding functionality will be temporarily enabled/disabled"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"allowPathfinding": {
|
||||||
|
"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": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"name": "Show PC speed to everyone",
|
"name": "Show PC speed to everyone",
|
||||||
"hint": "If enabled the coloring based on actor speed for player characters will shown to everyone, even if they don't have observer permission for the character sheet."
|
"hint": "If enabled the coloring based on actor speed for player characters will shown to everyone, even if they don't have observer permission for the character sheet."
|
||||||
@@ -51,6 +59,10 @@
|
|||||||
"name": "Automatically start measuring",
|
"name": "Automatically start measuring",
|
||||||
"hint": "If enabled, Drag Ruler will start measuring as soon as the token is being dragged. If disabled, Drag Ruler will remain inactive and will only start measuring once the button to add a waypoint is being pressed."
|
"hint": "If enabled, Drag Ruler will start measuring as soon as the token is being dragged. If disabled, Drag Ruler will remain inactive and will only start measuring once the button to add a waypoint is being pressed."
|
||||||
},
|
},
|
||||||
|
"autoPathfinding": {
|
||||||
|
"name": "Pathfinding by default",
|
||||||
|
"hint": "If enabled, dragging a token will automatically use a pathfinding ruler"
|
||||||
|
},
|
||||||
"enableMovementHistory": {
|
"enableMovementHistory": {
|
||||||
"name": "Enable movement history during combat",
|
"name": "Enable movement history during combat",
|
||||||
"hint": "If enabled, Drag Ruler will remember the path a token took during it's turn in combat and will display it when you pick the token back up."
|
"hint": "If enabled, Drag Ruler will remember the path a token took during it's turn in combat and will display it when you pick the token back up."
|
||||||
|
|||||||
@@ -44,9 +44,17 @@
|
|||||||
"moveWithoutAnimation": {
|
"moveWithoutAnimation": {
|
||||||
"name": "Deshabilitar animación de icono",
|
"name": "Deshabilitar animación de icono",
|
||||||
"hint": "Si al soltar un icono se presiona esta tecla, se deshabilitará la animación del icono al moverse al destino"
|
"hint": "Si al soltar un icono se presiona esta tecla, se deshabilitará la animación del icono al moverse al destino"
|
||||||
|
},
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "Conmutar búsqueda de camino",
|
||||||
|
"hint": "Cuando se presione al arrastrar un icono, la funcionalidad de búsqueda de camino será temporalmente habilitada/deshabilitada"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"allowPathfinding": {
|
||||||
|
"name": "Permitir búsqueda de camino a los jugadores",
|
||||||
|
"hint": "Permite a los jugadores usar en este mundo la funcionalidad de búsqueda de camino. Tenga cuidado porque la ruta puede transcurrir por lugares con niebla de guerra o muros invisibles, lo que podrá revelar algunos secretos a los jugadores antes de tiempo"
|
||||||
|
},
|
||||||
"alwaysShowSpeedForPCs": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"name": "Mostrar velocidad de los PJs a todo el mundo",
|
"name": "Mostrar velocidad de los PJs a todo el mundo",
|
||||||
"hint": "Si se habilita, se mostrará a todo el mundo los códigos de colores de las rutas de los PJs, incluso si no tienen permisos de observador para ese personaje"
|
"hint": "Si se habilita, se mostrará a todo el mundo los códigos de colores de las rutas de los PJs, incluso si no tienen permisos de observador para ese personaje"
|
||||||
@@ -55,6 +63,10 @@
|
|||||||
"name": "Comenzar a medir automáticamente",
|
"name": "Comenzar a medir automáticamente",
|
||||||
"hint": "Si se habilita, Drag Ruler comenzará a medir en cuanto se comience a arrastrar un icono. Si se deshabilita, Drag Ruler permanecerá inactivo y comenzará a medir únicamente cuando se presione el botón configurado para añadir un nuevo punto de ruta"
|
"hint": "Si se habilita, Drag Ruler comenzará a medir en cuanto se comience a arrastrar un icono. Si se deshabilita, Drag Ruler permanecerá inactivo y comenzará a medir únicamente cuando se presione el botón configurado para añadir un nuevo punto de ruta"
|
||||||
},
|
},
|
||||||
|
"autoPathfinding": {
|
||||||
|
"name": "Búsqueda de camino por defecto",
|
||||||
|
"hint": "Si se habilita, al arrastrar un icono se usará automáticamente la regla de búsqueda de camino"
|
||||||
|
},
|
||||||
"enableMovementHistory": {
|
"enableMovementHistory": {
|
||||||
"name": "Habilitar historial de movimiento durante el combate",
|
"name": "Habilitar historial de movimiento durante el combate",
|
||||||
"hint": "Si se habilita, Drag Ruler recordará la ruta que ha seguido un icono en su turno y la mostrará al seleccionarlo de nuevo"
|
"hint": "Si se habilita, Drag Ruler recordará la ruta que ha seguido un icono en su turno y la mostrará al seleccionarlo de nuevo"
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
"name": "drag-ruler",
|
"name": "drag-ruler",
|
||||||
"title": "Drag Ruler",
|
"title": "Drag Ruler",
|
||||||
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
||||||
"version": "1.10.3",
|
"version": "1.11.5",
|
||||||
"minimumCoreVersion" : "9.245",
|
"minimumCoreVersion" : "9.245",
|
||||||
"compatibleCoreVersion" : "9",
|
"compatibleCoreVersion" : "9",
|
||||||
"authors": [
|
"authors": [
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
],
|
],
|
||||||
"socket": true,
|
"socket": true,
|
||||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.10.3.zip",
|
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.11.5.zip",
|
||||||
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
|
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
|
||||||
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
|
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
|
||||||
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
||||||
|
|||||||
@@ -16,3 +16,21 @@ export function getGridPositionFromPixels(xPixel, yPixel) {
|
|||||||
return [y, x]
|
return [y, x]
|
||||||
return [x, y]
|
return [x, y]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getGridPositionFromPixelsObj(o) {
|
||||||
|
const r = {};
|
||||||
|
[r.x, r.y] = getGridPositionFromPixels(o.x, o.y);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPixelsFromGridPositionObj(o) {
|
||||||
|
const r = {};
|
||||||
|
[r.x, r.y] = getPixelsFromGridPosition(o.x, o.y);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCenterFromGridPositionObj(o) {
|
||||||
|
const r = getPixelsFromGridPositionObj(o);
|
||||||
|
[r.x, r.y] = canvas.grid.getCenter(r.x, r.y);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|||||||
+37
-3
@@ -1,10 +1,12 @@
|
|||||||
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
||||||
import {getGridPositionFromPixels} 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 {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) {
|
||||||
@@ -89,7 +91,7 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
|
|||||||
});
|
});
|
||||||
await draggedEntity.scene.updateEmbeddedDocuments(draggedEntity.constructor.embeddedName, updates, {animate});
|
await draggedEntity.scene.updateEmbeddedDocuments(draggedEntity.constructor.embeddedName, updates, {animate});
|
||||||
if (animate)
|
if (animate)
|
||||||
await Promise.all(entityPaths.map(({entity, path}) => entity.animateMovement(path)));
|
await Promise.all(entityPaths.map(({entity}) => CanvasAnimation.getAnimation(entity.movementAnimationName)?.promise));
|
||||||
|
|
||||||
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
||||||
if (this.cancelMovement) {
|
if (this.cancelMovement) {
|
||||||
@@ -153,14 +155,46 @@ export function cancelScheduledMeasurement() {
|
|||||||
|
|
||||||
// This is a modified version of Ruler.measure form foundry 0.7.9
|
// This is a modified version of Ruler.measure form foundry 0.7.9
|
||||||
export function measure(destination, options={}) {
|
export function measure(destination, options={}) {
|
||||||
|
|
||||||
const isToken = this.draggedEntity instanceof Token;
|
const isToken = this.draggedEntity instanceof Token;
|
||||||
if (isToken && !this.draggedEntity.isVisible)
|
if (isToken && !this.draggedEntity.isVisible)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
options.snap = options.snap ?? !disableSnap;
|
||||||
|
|
||||||
if (options.snap) {
|
if (options.snap) {
|
||||||
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dragRulerRemovePathfindingWaypoints();
|
||||||
|
|
||||||
|
if (isToken && isPathfindingEnabled.call(this)) {
|
||||||
|
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 => 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)
|
||||||
|
path = path.slice(1);
|
||||||
|
|
||||||
|
// If snapping is enabled, the last point of the path is already handled by the ruler
|
||||||
|
if (options.snap)
|
||||||
|
path = path.slice(0, path.length - 1);
|
||||||
|
|
||||||
|
for (const point of path) {
|
||||||
|
point.isPathfinding = true;
|
||||||
|
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||||
|
}
|
||||||
|
this.waypoints = this.waypoints.concat(path);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Don't show a path if the pathfinding yields no result to show the user that the destination is unreachable
|
||||||
|
destination = this.waypoints[this.waypoints.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(options.gridSpaces === undefined) {
|
if(options.gridSpaces === undefined) {
|
||||||
options.gridSpaces = canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
options.gridSpaces = canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {getMeasurePosition, setSnapParameterOnOptions} from "./util.js";
|
|||||||
|
|
||||||
export let disableSnap = false;
|
export let disableSnap = false;
|
||||||
export let moveWithoutAnimation = false;
|
export let moveWithoutAnimation = false;
|
||||||
|
export let togglePathfinding = false;
|
||||||
|
|
||||||
export function registerKeybindings() {
|
export function registerKeybindings() {
|
||||||
game.keybindings.register(settingsKey, "cancelDrag", {
|
game.keybindings.register(settingsKey, "cancelDrag", {
|
||||||
@@ -50,6 +51,15 @@ export function registerKeybindings() {
|
|||||||
}],
|
}],
|
||||||
precedence: -1,
|
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() {
|
function handleDeleteWaypoint() {
|
||||||
@@ -97,9 +107,24 @@ function handleDisableSnap(event) {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
ruler.measure(getMeasurePosition(), {snap: !disableSnap});
|
ruler.measure(getMeasurePosition(), {snap: !disableSnap});
|
||||||
|
ruler.dragRulerSendState();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMoveWithoutAnimation(event) {
|
function handleMoveWithoutAnimation(event) {
|
||||||
moveWithoutAnimation = !event.up;
|
moveWithoutAnimation = !event.up;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleTogglePathfinding(event) {
|
||||||
|
togglePathfinding = !event.up;
|
||||||
|
|
||||||
|
const ruler = canvas.controls.ruler;
|
||||||
|
if (!ruler?.isDragRuler)
|
||||||
|
return false;
|
||||||
|
if (ruler._state !== Ruler.STATES.MEASURING)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ruler.measure(getMeasurePosition(), {snap: !disableSnap});
|
||||||
|
ruler.dragRulerSendState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
@@ -132,6 +137,9 @@ function onEntityDragLeftDrop(event) {
|
|||||||
// This can happen if the user presses ESC during drag (maybe there are other ways too)
|
// This can happen if the user presses ESC during drag (maybe there are other ways too)
|
||||||
if (selectedTokens.length === 0)
|
if (selectedTokens.length === 0)
|
||||||
selectedTokens.push(ruler.draggedEntity);
|
selectedTokens.push(ruler.draggedEntity);
|
||||||
|
// This can happen if the ruler is being dragged so rapidly that the drag move handler hasn't been called before dropping
|
||||||
|
if (ruler._state === Ruler.STATES.STARTING)
|
||||||
|
onMouseMove.call(ruler, event);
|
||||||
ruler._state = Ruler.STATES.MOVING
|
ruler._state = Ruler.STATES.MOVING
|
||||||
moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
|
moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import {getGridPositionFromPixelsObj, getPixelsFromGridPositionObj} from "./foundry_fixes.js";
|
||||||
|
import {togglePathfinding} from "./keybindings.js";
|
||||||
|
import {debugGraphics} from "./main.js";
|
||||||
|
import {settingsKey} from "./settings.js";
|
||||||
|
import {getSnapPointForTokenObj, iterPairs} from "./util.js";
|
||||||
|
|
||||||
|
let cachedNodes = undefined;
|
||||||
|
let use5105 = false;
|
||||||
|
|
||||||
|
export function isPathfindingEnabled() {
|
||||||
|
if (this.user !== game.user)
|
||||||
|
return false;
|
||||||
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
||||||
|
return false;
|
||||||
|
if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding"))
|
||||||
|
return false;
|
||||||
|
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findPath(from, to, token, previousWaypoints) {
|
||||||
|
const lastNode = calculatePath(from, to, token, previousWaypoints);
|
||||||
|
if (!lastNode)
|
||||||
|
return null;
|
||||||
|
paintPathfindingDebug(lastNode, token);
|
||||||
|
const path = [];
|
||||||
|
let currentNode = lastNode;
|
||||||
|
while (currentNode) {
|
||||||
|
// TODO Check if the distance doesn't change
|
||||||
|
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
|
||||||
|
path.push({x: currentNode.node.x, y: currentNode.node.y});
|
||||||
|
currentNode = currentNode.previous;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wipePathfindingCache() {
|
||||||
|
cachedNodes = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (!cachedNodes[pos.layer])
|
||||||
|
cachedNodes[pos.layer] = new Array(Math.ceil(canvas.dimensions.height / canvas.grid.h));
|
||||||
|
if (!cachedNodes[pos.layer][pos.y])
|
||||||
|
cachedNodes[pos.layer][pos.y] = new Array(Math.ceil(canvas.dimensions.width / canvas.grid.w));
|
||||||
|
if (!cachedNodes[pos.layer][pos.y][pos.x]) {
|
||||||
|
cachedNodes[pos.layer][pos.y][pos.x] = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = cachedNodes[pos.layer][pos.y][pos.x];
|
||||||
|
if (initialize && !node.edges) {
|
||||||
|
node.edges = [];
|
||||||
|
for (const neighborPos of canvas.grid.grid.getNeighbors(pos.y, pos.x).map(([y, x]) => {return {x, y};})) {
|
||||||
|
if (neighborPos.x < 0 || neighborPos.y < 0)
|
||||||
|
continue;
|
||||||
|
// TODO Work with pixels instead of grid locations
|
||||||
|
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}, token, false);
|
||||||
|
|
||||||
|
// TODO We currently assume a cost of one or two for all transitions. Change this for difficult terrain support
|
||||||
|
let edgeCost = 1;
|
||||||
|
if (isDiagonal) {
|
||||||
|
// We charge 0.0001 more for edges to avoid unnecessary diagonal steps
|
||||||
|
edgeCost = pos.layer === 1 && targetLayer === 0 ? 2 : 1.0001;
|
||||||
|
}
|
||||||
|
node.edges.push({target: neighbor, cost: edgeCost});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 && canvas.grid.type === CONST.GRID_TYPES.SQUARE) {
|
||||||
|
previousWaypoints = previousWaypoints.map(w => getGridPositionFromPixelsObj(w));
|
||||||
|
startLayer = calcNoDiagonals(previousWaypoints) % 2;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
// TODO Re-sorting every iteration is expensive. Think of something better
|
||||||
|
nextNodes.sort((a, b) => b.estimated - a.estimated);
|
||||||
|
// Get node with cheapest estimate
|
||||||
|
const currentNode = nextNodes.pop();
|
||||||
|
if (currentNode.node.x === from.x && currentNode.node.y === from.y)
|
||||||
|
return currentNode;
|
||||||
|
previousNodes.add(currentNode.node);
|
||||||
|
for (const edge of currentNode.node.edges) {
|
||||||
|
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};
|
||||||
|
const neighborIndex = nextNodes.findIndex(node => node.node === neighbor.node);
|
||||||
|
if (neighborIndex >= 0) {
|
||||||
|
// If the neighbor is cheaper to reach via the current route than through previously discovered routes, replace it
|
||||||
|
if (nextNodes[neighborIndex].cost > neighbor.cost) {
|
||||||
|
nextNodes[neighborIndex] = neighbor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nextNodes.push(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return Math.max(Math.abs(pos.x - target.x), Math.abs(pos.y - target.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
debugGraphics.removeChildren();
|
||||||
|
let currentNode = lastNode;
|
||||||
|
while (currentNode) {
|
||||||
|
let text = new PIXI.Text(currentNode.cost.toFixed(0));
|
||||||
|
let pixels = getSnapPointForTokenObj(getPixelsFromGridPositionObj(currentNode.node), token);
|
||||||
|
text.anchor.set(0.5, 1.0);
|
||||||
|
text.x = pixels.x;
|
||||||
|
text.y = pixels.y;
|
||||||
|
debugGraphics.addChild(text);
|
||||||
|
currentNode = currentNode.previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-2
@@ -2,6 +2,7 @@ import {currentSpeedProvider, getColorForDistanceAndToken, getRangesFromSpeedPro
|
|||||||
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
||||||
import {cancelScheduledMeasurement, measure} from "./foundry_imports.js"
|
import {cancelScheduledMeasurement, measure} from "./foundry_imports.js"
|
||||||
import {getMovementHistory} from "./movement_tracking.js";
|
import {getMovementHistory} from "./movement_tracking.js";
|
||||||
|
import {wipePathfindingCache} from "./pathfinding.js";
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {getSnapPointForEntity} from "./util.js";
|
import {getSnapPointForEntity} from "./util.js";
|
||||||
|
|
||||||
@@ -32,8 +33,13 @@ export function extendRuler() {
|
|||||||
const json = super.toJSON();
|
const json = super.toJSON();
|
||||||
if (this.draggedEntity) {
|
if (this.draggedEntity) {
|
||||||
const isToken = this.draggedEntity instanceof Token;
|
const isToken = this.draggedEntity instanceof Token;
|
||||||
json["draggedEntityIsToken"] = isToken;
|
json.draggedEntityIsToken = isToken;
|
||||||
json["draggedEntity"] = this.draggedEntity.id;
|
json.draggedEntity = this.draggedEntity.id;
|
||||||
|
json.waypoints = json.waypoints.map(old => {
|
||||||
|
let w = duplicate(old);
|
||||||
|
w.isPathfinding = undefined;
|
||||||
|
return w;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -55,6 +61,9 @@ export function extendRuler() {
|
|||||||
|
|
||||||
measure(destination, options={}) {
|
measure(destination, options={}) {
|
||||||
if (this.isDragRuler) {
|
if (this.isDragRuler) {
|
||||||
|
// If this is the ruler of a remote user take the waypoints as they were transmitted and don't apply any additional snapping to them
|
||||||
|
if (this.user !== game.user)
|
||||||
|
options.snap = false;
|
||||||
return measure.call(this, destination, options);
|
return measure.call(this, destination, options);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -75,6 +84,7 @@ export function extendRuler() {
|
|||||||
}
|
}
|
||||||
this.waypoints.push(new PIXI.Point(point.x, point.y));
|
this.waypoints.push(new PIXI.Point(point.x, point.y));
|
||||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||||
|
this.waypoints.filter(waypoint => waypoint.isPathfinding).forEach(waypoint => waypoint.isPathfinding = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
dragRulerAddWaypointHistory(waypoints) {
|
dragRulerAddWaypointHistory(waypoints) {
|
||||||
@@ -91,6 +101,7 @@ export function extendRuler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) {
|
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) {
|
||||||
|
this.dragRulerRemovePathfindingWaypoints();
|
||||||
options.snap = options.snap ?? true;
|
options.snap = options.snap ?? true;
|
||||||
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -107,6 +118,11 @@ export function extendRuler() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dragRulerRemovePathfindingWaypoints() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
dragRulerAbortDrag(event={preventDefault: () => {return}}) {
|
dragRulerAbortDrag(event={preventDefault: () => {return}}) {
|
||||||
const token = this.draggedEntity;
|
const token = this.draggedEntity;
|
||||||
this._endMeasurement();
|
this._endMeasurement();
|
||||||
@@ -171,6 +187,7 @@ export function extendRuler() {
|
|||||||
return;
|
return;
|
||||||
const ruler = canvas.controls.ruler;
|
const ruler = canvas.controls.ruler;
|
||||||
ruler.clear();
|
ruler.clear();
|
||||||
|
wipePathfindingCache();
|
||||||
ruler._state = Ruler.STATES.STARTING;
|
ruler._state = Ruler.STATES.STARTING;
|
||||||
let entityCenter;
|
let entityCenter;
|
||||||
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(entity))
|
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(entity))
|
||||||
@@ -185,6 +202,12 @@ export function extendRuler() {
|
|||||||
if (measureImmediately)
|
if (measureImmediately)
|
||||||
ruler.measure(destination, options);
|
ruler.measure(destination, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dragRulerSendState() {
|
||||||
|
game.user.broadcastActivity({
|
||||||
|
ruler: this.toJSON()
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ruler = DragRulerRuler;
|
Ruler = DragRulerRuler;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider} from "./api.js";
|
import {availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider} from "./api.js";
|
||||||
import {SpeedProvider} from "./speed_provider.js"
|
import {SpeedProvider} from "./speed_provider.js"
|
||||||
|
import { early_isGM } from "./util.js";
|
||||||
|
|
||||||
export const settingsKey = "drag-ruler";
|
export const settingsKey = "drag-ruler";
|
||||||
|
|
||||||
@@ -82,6 +83,25 @@ export function registerSettings() {
|
|||||||
default: true,
|
default: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.settings.register(settingsKey, "allowPathfinding", {
|
||||||
|
name: "drag-ruler.settings.allowPathfinding.name",
|
||||||
|
hint: "drag-ruler.settings.allowPathfinding.hint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
onChange: () => location.reload(),
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(settingsKey, "autoPathfinding", {
|
||||||
|
name: "drag-ruler.settings.autoPathfinding.name",
|
||||||
|
hint: "drag-ruler.settings.autoPathfinding.hint",
|
||||||
|
scpoe: "client",
|
||||||
|
config: early_isGM() || game.settings.get(settingsKey, "allowPathfinding"),
|
||||||
|
type: Boolean,
|
||||||
|
defualt: false,
|
||||||
|
});
|
||||||
|
|
||||||
game.settings.register(settingsKey, "lastTerrainRulerHintTime", {
|
game.settings.register(settingsKey, "lastTerrainRulerHintTime", {
|
||||||
config: false,
|
config: false,
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export function getDefaultSpeedAttribute() {
|
|||||||
return "actor.data.data.movement.walk.value";
|
return "actor.data.data.movement.walk.value";
|
||||||
case "swade":
|
case "swade":
|
||||||
return "actor.data.data.stats.speed.adjusted";
|
return "actor.data.data.stats.speed.adjusted";
|
||||||
|
case "ds4":
|
||||||
|
return "actor.data.data.combatValues.movement.total";
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -32,6 +34,7 @@ export function getDefaultDashMultiplier() {
|
|||||||
case "D35E":
|
case "D35E":
|
||||||
case "sfrpg":
|
case "sfrpg":
|
||||||
case "shadowrun5e":
|
case "shadowrun5e":
|
||||||
|
case "ds4":
|
||||||
return 2
|
return 2
|
||||||
case "CoC7":
|
case "CoC7":
|
||||||
return 5;
|
return 5;
|
||||||
|
|||||||
+17
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -80,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);
|
||||||
@@ -262,3 +272,10 @@ export function getMeasurePosition() {
|
|||||||
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
|
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
|
||||||
return measurePosition;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user