Compare commits

...

29 Commits

Author SHA1 Message Date
Crovaxon 0372b6a3b9 Removed duplicates from merging, fuck VS Code. 2025-05-11 16:43:52 +02:00
Crovaxon 3bd945b6e2 Merge branch 'develop' of https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler into develop 2025-05-11 16:42:37 +02:00
Crovaxon 63b946051a Updated URLs and prepared for test release. 2025-05-11 16:41:13 +02:00
Crovaxon 179cc73fa1 Merge branch 'develop' of https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler into develop 2025-05-11 16:39:19 +02:00
Crovaxon 9eed5d4245 Updated download URL for upcoming test release. 2025-05-11 16:38:27 +02:00
Crovaxon f31c2c0f8a Updated URLs
Updated the download URL
2025-05-11 16:36:44 +02:00
Crovaxon 72a646ab89 Updated URLs 2025-05-11 16:35:28 +02:00
Crovaxon e0c57ce78b Changed socketlib dependency to my own hosted instance and upped version compatibility so Foundry v13 will be able to install this. 2025-05-11 16:22:17 +02:00
Jonas Karlsson a21085ca5b Add dragonbane support (#333) 2024-08-03 21:19:05 +02:00
Joe 21533a0075 Update lancer speed attribute (#301) 2024-08-03 21:18:48 +02:00
Manuel Vögele 8d67ca01c3 Release v1.14.2 2024-07-28 23:26:33 +02:00
Manuel Vögele 80ff8832fd Add a word of warning about bugs to the readme 2024-07-28 23:24:40 +02:00
Manuel Vögele c787b18fcb Add 1 to hex positions before snapping to avoid rounding issues (fixes #334) 2024-07-28 23:13:23 +02:00
Manuel Vögele c03dbc09ff Always use the token's center as origin for gridless snapping (fixes #336) 2024-07-28 23:03:37 +02:00
Manuel Vögele 27a2b46a8a Revert "Restore rulerOffset for all uses except hex grids (fixes #332)"
This reverts commit ab1f5b4c9b.
2024-07-28 22:47:26 +02:00
Manuel Vögele 94a8e6f147 Release v1.14.1 2024-07-28 09:29:39 +02:00
Manuel Vögele ab1f5b4c9b Restore rulerOffset for all uses except hex grids (fixes #332)
This restores the functinality of gridless, but it is not a proper fix. Most likely all the functionality in foundry_imports.js has to be re-imported at this point
2024-07-28 09:28:13 +02:00
Manuel Vögele 1974e6e4a3 Revert to pre-v12 maeasureDistances to restore gridless functionality 2024-07-28 09:04:41 +02:00
Manuel Vögele cb40c8de50 Release v1.14.0 2024-07-27 23:18:55 +02:00
Manuel Vögele 6da3e65301 Fix some deprecation warnings 2024-07-27 23:14:40 +02:00
Manuel Vögele a7d06eaed3 Make use of new foundry functions for highlightMeasurementNative 2024-07-27 23:14:40 +02:00
Manuel Vögele 18cf63895c Remove v11 compatibility code 2024-07-27 22:03:48 +02:00
n0q 0e2ab35db7 fix hex calculations in v12 2024-07-27 21:55:29 +02:00
Nils 0808f17ee3 Update foundry for v12 2024-07-27 21:55:29 +02:00
Manuel Vögele b649dacdb5 Add World of Darkness 20th ed to supported Game System list 2024-03-25 16:38:38 +01:00
KitCat c490550ed9 Add Support for 5x5 Hex Tokens (#315) 2024-03-06 23:28:16 +01:00
Manuel Vögele f12dbf0e61 Update wall colission error string to it's new location (it's a foundry native string that was moved to a new key) 2024-03-06 22:31:57 +01:00
Manuel Vögele f1fcc52867 Remove v10 support code 2023-06-13 19:53:30 +02:00
Manuel Vögele af03341638 Use token.center instead of calculating the center manually 2023-06-13 18:25:42 +02:00
14 changed files with 156 additions and 143 deletions
+19
View File
@@ -1,3 +1,22 @@
## 1.14.2
### Bug fixes
- Fixed a bug where tokens would snap to inconsistent distances on gridless scenes
- Fixed a bug that caused the highlighted path on hex grids to be incorrect
## 1.14.1
### Bug fixes
- The functionality Drag Ruler on gridless has been restored
## 1.14.0
### New features
- Drag Ruler now supports hex tokens of size 5 (thanks KitCat!)
### Compatibility
- Drag Ruler has been updated to work with Foundry v12 (thanks TPNils and N0q!)
## 1.13.8
### Bugfixes
- `getMovedDistanceFromToken` no longer returns incorrect values on gridless maps
+7 -2
View File
@@ -1,13 +1,17 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/staebchenfisch)
## Beware of bugs in v12 and onward
Foundry v12 has introduced significant changes to how the grid and the ruler are implemented. While Drag Ruler has now been updated to generally work with v12, quite a lot of bugs remain. This is espeically true for Hex grids, but some bugs affect all the grid types. Some of the issues require a large code rewrite to address them properly. Unfortunately this means that Drag Ruler will not provide the same quality experience (snappyness, bugfreeness) you're used to in v12 (and possibly onward). If you're planning to update to v12 and depend on Drag Ruler, proceed with caution.
# Drag Ruler
This module shows a ruler when you drag a token or measurement template to inform you how far you've dragged it from its start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. By default, three colors are being used: green for spaces that your token can reach by walking normally are colored green, spaces that can only be reached by dashing will be colored yellow and spaces that cannot be reached with the token's speed will be colored red. If you're using a gridless map the ruler color will change to convey this information.
![Drag Ruler being used on a square grids](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/basic_square.webp)
![Drag Ruler being used on a gridless scene](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/basic_gridless.webp)
![Drag Ruler while dragging a measurement template](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/measurement_template.webp)
## Supports Tokens of all sizes
Terrain ruler has excellent support for tokens of all sizes. The Ruler will always originate from the token's center and will always highlight all the squares that tokens move over.
@@ -51,7 +55,7 @@ The game systems that offer Drag Ruler integration are:
- GURPS 4th Edition Game Aid (Unofficial) (starting with version 0.9.1)
- Hackmaster (starting with version 0.2.11)
- 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 Ruler Integration](https://foundryvtt.com/packages/lancer-speed-provider))
- Level Up: Advanced 5th Edition (Official) (via the module [A5E Drag Ruler Integration](https://foundryvtt.com/packages/a5edragruler))
- Pathfinder 1 (starting with version 0.77.3)
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
@@ -66,6 +70,7 @@ The game systems that offer Drag Ruler integration are:
- The Dark Eye 5 / Das Schwarze Auge 5 (via the module [TDE5/DSA5 Drag Ruler Integration](https://foundryvtt.com/packages/dsa5-drag-ruler))
- Shadow of the Demon Lord (starting with version 1.7.15)
- Wasteland Ventures (starting with version 0.1.0)
- World of Darkness 20th ed (starting with version 3.3.0)
- WWII:OWB (starting with version 1.0.4)
+11 -10
View File
@@ -2,10 +2,11 @@
"id": "drag-ruler",
"title": "Drag Ruler",
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
"version": "1.13.8",
"version": "1.14.2",
"compatibility": {
"minimum": "11",
"verified": "11"
"minimum": "12",
"verified": "12",
"maximum": "13"
},
"authors": [
{
@@ -74,16 +75,16 @@
{
"id": "socketlib",
"type": "module",
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-socketlib/master/module.json"
"manifest": "https://gitea.crovaxon.de/crovaxon/foundryvtt-socketlib/raw/branch/develop/module.json"
}
]
},
"socket": true,
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.13.8.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",
"bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues",
"url": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler",
"download": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/archive/v1.14.2-test.zip",
"manifest": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/raw/branch/develop/module.json",
"readme": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/blob/master/README.md",
"changelog": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
"bugs": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/issues",
"allowBugReporter": true
}
+2 -3
View File
@@ -133,10 +133,9 @@ export function getColorForDistanceAndToken(distance, token, ranges = null) {
export function getMovedDistanceFromToken(token) {
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
const history = getMovementHistory(token);
const tokenPos = {x: token.x, y: token.y};
let tokenPos = {x: token.x, y: token.y};
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
tokenPos.x += token.w / 2;
tokenPos.y += token.h / 2;
tokenPos = token.center;
}
const segments = CONFIG.Canvas.rulerClass
.dragRulerGetRaysFromWaypoints(history, tokenPos)
+2 -2
View File
@@ -15,7 +15,7 @@ export function highlightMeasurementTerrainRuler(
}
export function measureDistances(segments, entity, shape, options = {}) {
const opts = duplicate(options);
const opts = foundry.utils.duplicate(options);
if (canvas.grid.diagonalRule === "EUCL") {
opts.ignoreGrid = true;
opts.gridSpaces = false;
@@ -31,7 +31,7 @@ export function measureDistances(segments, entity, shape, options = {}) {
);
previousSegments.forEach(
segment =>
(segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces)),
(segment.ray.terrainRulerVisitedSpaces = foundry.utils.duplicate(segment.ray.dragRulerVisitedSpaces)),
);
opts.costFunction = buildCostFunction(entity, shape);
if (previousSegments.length > 0)
+11 -16
View File
@@ -1,31 +1,26 @@
// Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
// This code could be phased out. The bug that caused the creation of these functions is now fixed, so this is only a wrapper function now
export function getPixelsFromGridPosition(xGrid, yGrid) {
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid);
}
return canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid);
let coord = getPixelsFromGridPositionObj({x: xGrid, y: yGrid});
return [coord.x, coord.y];
}
// Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
// This code could be phased out. The bug that caused the creation of these functions is now fixed, so this is only a wrapper function now
export function getGridPositionFromPixels(xPixel, yPixel) {
const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel);
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return [y, x];
return [x, y];
let coord = getGridPositionFromPixelsObj({x: xPixel, y: yPixel});
return [coord.x, coord.y];
}
// This code could be phased out. The bug that caused the creation of these functions is now fixed, so this is only a wrapper function now
export function getGridPositionFromPixelsObj(o) {
const r = {};
[r.x, r.y] = getGridPositionFromPixels(o.x, o.y);
return r;
const coord = canvas.grid.getOffset(o);
return {x: coord.j, y: coord.i};
}
// This code could be phased out. The bug that caused the creation of these functions is now fixed, so this is only a wrapper function now
export function getPixelsFromGridPositionObj(o) {
const r = {};
[r.x, r.y] = getPixelsFromGridPosition(o.x, o.y);
return r;
return canvas.grid.getTopLeftPoint({j: o.x, i: o.y});
}
export function getCenterFromGridPositionObj(o) {
+13 -46
View File
@@ -35,7 +35,8 @@ export async function moveEntities(draggedEntity, selectedEntities) {
);
});
if (hasCollision) {
ui.notifications.error(game.i18n.localize("ERROR.TokenCollide"));
ui.notifications.error(game.i18n.localize("RULER.MovementCollision"));
this._state = Ruler.STATES.MEASURING;
this._endMeasurement();
return true;
}
@@ -47,7 +48,10 @@ export async function moveEntities(draggedEntity, selectedEntities) {
await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused);
// Once all animations are complete we can clear the ruler
if (this.draggedEntity?.id === draggedEntity.id) this._endMeasurement();
if (this.draggedEntity?.id === draggedEntity.id) {
this._state = Ruler.STATES.MEASURING;
this._endMeasurement();
}
}
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
@@ -133,8 +137,8 @@ export function onMouseMove(event) {
// Extract event data
const destination = {
x: event.interactionData.destination.x + this.rulerOffset.x,
y: event.interactionData.destination.y + this.rulerOffset.y,
x: event.interactionData.destination.x,
y: event.interactionData.destination.y,
};
// Hide any existing Token HUD
@@ -185,51 +189,14 @@ export function highlightMeasurementNative(
tokenShape = [{x: 0, y: 0}],
alpha = 1,
) {
const spacer = canvas.scene.grid.type === CONST.GRID_TYPES.SQUARE ? 1.41 : 1;
const nMax = Math.max(
Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))),
1,
);
const tMax = Array.fromRange(nMax + 1).map(t => t / nMax);
// Track prior position
let prior = null;
// Iterate over ray portions
for (let [i, t] of tMax.reverse().entries()) {
let {x, y} = ray.project(t);
// Get grid position
let [x0, y0] = i === 0 ? [null, null] : prior;
let [x1, y1] = canvas.grid.grid.getGridPositionFromPixels(x, y);
if (x0 === x1 && y0 === y1) continue;
// Highlight the grid position
let [xgtl, ygtl] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
let [xg, yg] = canvas.grid.grid.getCenter(xgtl, ygtl);
const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, {x: xg, y: yg})}]);
for (const offset of canvas.grid.getDirectPath([ray.A, ray.B]).reverse()) {
const point = canvas.grid.getTopLeftPoint(offset);
const center = canvas.grid.getCenterPoint(offset);
const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, center)}]);
const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true}));
const color = this.dragRulerGetColorForDistance(distance);
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
const snapPoint = getSnapPointForToken(point.x + 1, point.y + 1, this.draggedEntity);
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
prior = [x1, y1];
// If the positions are not neighbors, also highlight their halfway point
if (i > 0 && !canvas.grid.isNeighbor(x0, y0, x1, y1)) {
let th = tMax[i - 1] - 0.5 / nMax;
let {x, y} = ray.project(th);
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
let [xghtl, yghtl] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
let [xgh, ygh] = canvas.grid.grid.getCenter(xghtl, yghtl);
const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, {x: xgh, y: ygh})}]);
const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true}));
const color = this.dragRulerGetColorForDistance(distance);
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
}
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
}
}
+3 -3
View File
@@ -9,11 +9,11 @@
* - Instead of taking a grid parameter, get the grid value from the globas canvas
*/
export function findVertexSnapPoint(x, y, altOrientationFlag) {
const grid = canvas.grid.grid;
const grid = canvas.grid;
if (grid.columnar) {
return findSnapPointCols(x, y, grid.h, grid.w, altOrientationFlag);
return findSnapPointCols(x, y, grid.sizeY, grid.sizeX, altOrientationFlag);
} else {
return findSnapPointRows(x, y, grid.h, grid.w, altOrientationFlag);
return findSnapPointRows(x, y, grid.sizeY, grid.sizeX, altOrientationFlag);
}
}
+6 -10
View File
@@ -137,10 +137,6 @@ function onEntityLeftDragStart(wrapped, event) {
const ruler = canvas.controls.ruler;
ruler.draggedEntity = this;
const entityCenter = getEntityCenter(this);
ruler.rulerOffset = {
x: entityCenter.x - event.interactionData.origin.x,
y: entityCenter.y - event.interactionData.origin.y,
};
if (game.settings.get(settingsKey, "autoStartMeasurement")) {
let options = {};
setSnapParameterOnOptions(ruler, options);
@@ -149,7 +145,7 @@ function onEntityLeftDragStart(wrapped, event) {
}
function onEntityLeftDragMoveSnap(wrapped, event) {
applyGridlessSnapping.call(this, event);
applyGridlessSnapping.call(canvas.controls.ruler, event);
onEntityLeftDragMove.call(this, wrapped, event);
}
@@ -217,7 +213,7 @@ function applyGridlessSnapping(event) {
const rasterWidth = 35 / canvas.stage.scale.x;
const tokenX = event.interactionData.destination.x;
const tokenY = event.interactionData.destination.y;
const destination = {x: tokenX + ruler.rulerOffset.x, y: tokenY + ruler.rulerOffset.y};
const destination = {x: tokenX, y: tokenY};
const ranges = getRangesFromSpeedProvider(ruler.draggedEntity);
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
@@ -241,13 +237,13 @@ function applyGridlessSnapping(event) {
const deltaY = destination.y - rasterLocation.y;
const rasterDistance = Math.hypot(deltaX, deltaY);
if (rasterDistance < rasterWidth) {
event.interactionData.destination.x = rasterLocation.x - ruler.rulerOffset.x;
event.interactionData.destination.y = rasterLocation.y - ruler.rulerOffset.y;
event.interactionData.destination.x = rasterLocation.x;
event.interactionData.destination.y = rasterLocation.y;
}
}
} else {
let waypointDistance = 0;
let origin = event.interactionData.origin;
let origin = this.draggedEntity.getCenterPoint();
if (ruler.waypoints.length > 1) {
const segments = ruler.constructor
.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination)
@@ -256,7 +252,7 @@ function applyGridlessSnapping(event) {
});
origin = segments.pop().ray.A;
waypointDistance = canvas.grid.measureDistances(segments).reduce((a, b) => a + b);
origin = {x: origin.x - ruler.rulerOffset.x, y: origin.y - ruler.rulerOffset.y};
origin = {x: origin.x, y: origin.y};
}
const deltaX = tokenX - origin.x;
+34 -19
View File
@@ -41,14 +41,17 @@ export function extendRuler() {
if (!this.isDragRuler) return await super.moveToken(event);
}
toJSON() {
const json = super.toJSON();
_getMeasurementData() {
const json =
typeof super._getMeasurementData === "function"
? super._getMeasurementData()
: super.toJSON();
if (this.draggedEntity) {
const isToken = this.draggedEntity instanceof Token;
json.draggedEntityIsToken = isToken;
json.draggedEntity = this.draggedEntity.id;
json.waypoints = json.waypoints.map(old => {
let w = duplicate(old);
let w = foundry.utils.duplicate(old);
w.isPathfinding = undefined;
return w;
});
@@ -56,7 +59,13 @@ export function extendRuler() {
return json;
}
/** @deprecated since V12 */
toJSON() {
return this._getMeasurementData();
}
update(data) {
if (!data || data.state === Ruler.STATES.INACTIVE) return this.clear();
// Don't show a GMs drag ruler to non GM players
if (
data.draggedEntity &&
@@ -105,7 +114,10 @@ export function extendRuler() {
// Compute the measurement destination, segments, and distance
const d = this._getMeasurementDestination(destination);
if (d.x === this.destination.x && d.y === this.destination.y) return;
if (this.destination && d.x === this.destination.x && d.y === this.destination.y) {
this.performPostPathfindingActions(options);
return;
}
this.destination = d;
// TODO Check if we can reuse the old path
@@ -206,6 +218,7 @@ export function extendRuler() {
}
}
}
this.dragRulerSendState();
return this.segments;
}
@@ -227,7 +240,7 @@ export function extendRuler() {
const waypoints =
this.draggedEntity instanceof Token
? applyTokenSizeOffset(unsnappedWaypoints, this.draggedEntity)
: duplicate(unsnappedWaypoints);
: foundry.utils.duplicate(unsnappedWaypoints);
const unsnappedSegments = [];
const segments = [];
for (const [i, p1] of waypoints.entries()) {
@@ -252,6 +265,9 @@ export function extendRuler() {
unsnappedSegments.push({ray: unsnappedRay, label});
}
this.dragRulerUnsnappedSegments = unsnappedSegments;
if (this.labels.children.length > segments.length) {
this.labels.removeChildren(segments.length).forEach(c => c.destroy());
}
return segments;
} else {
return super._getMeasurementSegments();
@@ -269,14 +285,14 @@ export function extendRuler() {
enableTerrainRuler: this.dragRulerEnableTerrainRuler,
};
const distances = measureDistances(this.segments, this.draggedEntity, shape, options);
let totalDistance = 0;
this.totalDistance = 0;
for (const [i, d] of distances.entries()) {
let s = this.segments[i];
s.startDistance = totalDistance;
totalDistance += d;
s.startDistance = this.totalDistance;
this.totalDistance += d;
s.last = i === this.segments.length - 1;
s.distance = d;
s.text = this._getSegmentLabel(s, totalDistance);
s.text = this._getSegmentLabel(s);
}
for (const [i, segment] of this.segments.entries()) {
@@ -375,15 +391,11 @@ export function extendRuler() {
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
event.preventDefault();
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const rulerOffset = this.rulerOffset;
// Options are not passed to _removeWaypoint in vanilla Foundry.
// Send them in case other modules have overriden that behavior and accept an options parameter (Toggle Snap to Grid)
this._removeWaypoint(
{x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y},
options,
);
game.user.broadcastActivity({ruler: this});
this._removeWaypoint({x: mousePosition.x, y: mousePosition.y}, options);
this.performPostPathfindingActions(options);
} else {
this.dragRulerAbortDrag(event);
}
@@ -428,7 +440,7 @@ export function extendRuler() {
this.dragRulerAddWaypoint(waypoint, {snap: false});
}
this.measure(this.destination);
game.user.broadcastActivity({ruler: this});
this.dragRulerSendState();
}
static dragRulerGetRaysFromWaypoints(waypoints, destination) {
@@ -478,15 +490,18 @@ export function extendRuler() {
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const destination = {
x: mousePosition.x + ruler.rulerOffset.x,
y: mousePosition.y + ruler.rulerOffset.y,
x: mousePosition.x,
y: mousePosition.y,
};
if (measureImmediately) ruler.measure(destination, options);
}
dragRulerSendState() {
if (this.user !== game.user) {
return;
}
game.user.broadcastActivity({
ruler: this.toJSON(),
ruler: this._getMeasurementData(),
});
}
}
+1 -1
View File
@@ -298,7 +298,7 @@ function enumerateProviderSettings(provider) {
for (const setting of provider.settings) {
try {
if (setting.scope === "world" && !game.user.isGM) continue;
const s = duplicate(setting);
const s = foundry.utils.duplicate(setting);
s.id = `${provider.id}.setting.${s.id}`;
s.name = game.i18n.localize(s.name);
s.hint = game.i18n.localize(s.hint);
+1 -1
View File
@@ -115,7 +115,7 @@ export class GenericSpeedProvider extends SpeedProvider {
getRanges(token) {
const speedAttribute = this.getSetting("speedAttribute");
if (!speedAttribute) return [];
const tokenSpeed = parseFloat(getProperty(token, speedAttribute));
const tokenSpeed = parseFloat(foundry.utils.getProperty(token, speedAttribute));
if (tokenSpeed === undefined) {
console.warn(
`Drag Ruler (Generic Speed Provider) | The configured token speed attribute "${speedAttribute}" didn't return a speed value. To use colors based on drag distance set the setting to the correct value (or clear the box to disable this feature).`,
+4 -1
View File
@@ -9,7 +9,7 @@ export function getDefaultSpeedAttribute() {
case "dnd5e":
return "actor.system.attributes.movement.walk";
case "lancer":
return "actor.system.derived.speed";
return "actor.system.speed";
case "pf1":
case "D35E":
return "actor.system.attributes.speed.land.total";
@@ -27,6 +27,8 @@ export function getDefaultSpeedAttribute() {
return "actor.system.details.move.walk";
case "crucible":
return "actor.system.movement.stride";
case "dragonbane":
return "actor.system.movement.value";
}
return "";
}
@@ -44,6 +46,7 @@ export function getDefaultDashMultiplier() {
case "sfrpg":
case "shadowrun5e":
case "ds4":
case "dragonbane":
return 2;
case "CoC7":
return 5;
+42 -29
View File
@@ -37,20 +37,20 @@ export function getHexTokenSize(token) {
}
export function getEntityCenter(token) {
if (token instanceof Token && canvas.grid.isHex) {
if (token instanceof Token && isCanvasHex()) {
const center = token.center;
const size = getHexTokenSize(token);
if (size % 2 === 0) {
let offset;
if (canvas.grid.grid.columnar) {
offset = canvas.grid.grid.w - canvas.grid.grid.h;
if (canvas.grid.columnar) {
offset = canvas.grid.sizeX - canvas.grid.sizeY;
} else {
offset = canvas.grid.grid.h - canvas.grid.grid.w;
offset = canvas.grid.sizeY - canvas.grid.sizeX;
}
if (getAltOrientationFlagForToken(token, size)) {
offset *= -1;
}
if (canvas.grid.grid.columnar) {
if (canvas.grid.columnar) {
center.x -= offset;
return center;
} else {
@@ -79,7 +79,7 @@ export function getSnapPointForToken(x, y, token) {
return {x, y};
}
if (canvas.grid.isHex) {
if (isCanvasHex()) {
const size = getHexTokenSize(token);
if (size % 2 === 0) {
return findVertexSnapPoint(x, y, getAltOrientationFlagForToken(token, size));
@@ -89,27 +89,27 @@ export function getSnapPointForToken(x, y, token) {
}
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
let cellX, cellY;
if (token.document.width % 2 === 0) cellX = x - canvas.grid.h / 2;
else cellX = x;
if (token.document.height % 2 === 0) cellY = y - canvas.grid.h / 2;
else cellY = y;
const [centerX, centerY] = canvas.grid.getCenter(cellX, cellY);
let cell = {};
if (token.document.width % 2 === 0) cell.x = x - canvas.grid.sizeY / 2;
else cell.x = x;
if (token.document.height % 2 === 0) cell.y = y - canvas.grid.sizeY / 2;
else cell.y = y;
const center = canvas.grid.getCenterPoint(cell);
let snapX, snapY;
// Tiny tokens can snap to the cells corners
if (token.document.width <= 0.5) {
const offsetX = x - topLeftX;
const subGridWidth = Math.floor(canvas.grid.w / 2);
const subGridWidth = Math.floor(canvas.grid.sizeX / 2);
const subGridPosX = Math.floor(offsetX / subGridWidth);
snapX = topLeftX + (subGridPosX + 0.5) * subGridWidth;
}
// Tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
else if (Math.round(token.document.width) % 2 === 1 || token.document.width < 1) {
snapX = centerX;
snapX = center.x;
}
// All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
else {
snapX = centerX + canvas.grid.w / 2;
snapX = center.x + canvas.grid.sizeX / 2;
}
if (token.document.height <= 0.5) {
const offsetY = y - topLeftY;
@@ -117,9 +117,9 @@ export function getSnapPointForToken(x, y, token) {
const subGridPosY = Math.floor(offsetY / subGridHeight);
snapY = topLeftY + (subGridPosY + 0.5) * subGridHeight;
} else if (Math.round(token.document.height) % 2 === 1 || token.document.height < 1) {
snapY = centerY;
snapY = center.y;
} else {
snapY = centerY + canvas.grid.h / 2;
snapY = center.y + canvas.grid.sizeY / 2;
}
return {x: snapX, y: snapY};
}
@@ -147,7 +147,7 @@ export function highlightTokenShape(position, shape, color, alpha) {
const area = getAreaFromPositionAndShape(position, shape);
for (const space of area) {
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
canvas.grid.grid.highlightGridPosition(layer, {x, y, color, alpha: 0.25 * alpha});
canvas.grid.highlightGridPosition(layer, {x, y, color, alpha: 0.25 * alpha});
}
}
@@ -155,9 +155,9 @@ export function getAreaFromPositionAndShape(position, shape) {
return shape.map(space => {
let x = position.x + space.x;
let y = position.y + space.y;
if (canvas.grid.isHex) {
if (isCanvasHex()) {
let shiftedRow;
if (canvas.grid.grid.options.even) shiftedRow = 1;
if (canvas.grid?.even) shiftedRow = 1;
else shiftedRow = 0;
if (canvas.grid.grid.columnar) {
if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
@@ -212,6 +212,16 @@ export function getTokenShape(token) {
{x: 0, y: -2},
{x: 1, y: -2},
]);
if (size >= 5)
shape = shape.concat([
{x: -2, y: 0},
{x: 1, y: 1},
{x: -1, y: 2},
{x: 0, y: 2},
{x: 1, y: 2},
{x: -2, y: 1},
{x: 2, y: 0},
]);
if (getAltOrientationFlagForToken(token, size)) {
shape.forEach(space => (space.y *= -1));
@@ -226,7 +236,7 @@ export function getTokenShape(token) {
export function getTokenSize(token) {
let w, h;
if (canvas.grid.isHex) {
if (isCanvasHex()) {
w = h = getHexTokenSize(token);
} else {
w = token.document.width;
@@ -244,26 +254,26 @@ export function applyTokenSizeOffset(waypoints, token) {
const tokenSize = getTokenSize(token);
const waypointOffset = {x: 0, y: 0};
if (canvas.grid.isHex) {
if (isCanvasHex()) {
const isAltOrientation = getAltOrientationFlagForToken(token, getHexTokenSize(token));
if (canvas.grid.grid.columnar) {
if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2;
waypointOffset.x = canvas.grid.sizeX / 2;
if (isAltOrientation) waypointOffset.x *= -1;
}
} else {
if (tokenSize.h % 2 === 0) {
waypointOffset.y = canvas.grid.h / 2;
waypointOffset.y = canvas.grid.sizeY / 2;
if (isAltOrientation) waypointOffset.y *= -1;
}
}
// If hex size support isn't active leave the waypoints like they are
} else {
if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2;
waypointOffset.x = canvas.grid.sizeX / 2;
}
if (tokenSize.h % 2 === 0) {
waypointOffset.y = canvas.grid.h / 2;
waypointOffset.y = canvas.grid.sizeY / 2;
}
}
@@ -286,13 +296,12 @@ export function isClose(a, b, delta) {
}
export function getPointer() {
return canvas.app.renderer.plugins.interaction?.mouse ?? canvas.app.renderer.events.pointer;
return canvas.app.renderer.events.pointer;
}
export function getMeasurePosition() {
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const rulerOffset = canvas.controls.ruler.rulerOffset;
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
const measurePosition = {x: mousePosition.x, y: mousePosition.y};
return measurePosition;
}
@@ -314,3 +323,7 @@ export function isPathfindingEnabled() {
if (moveWithoutAnimation) return false;
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
}
function isCanvasHex() {
return canvas.grid.isHexagonal;
}