Compare commits

...

19 Commits

Author SHA1 Message Date
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
13 changed files with 145 additions and 135 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 ## 1.13.8
### Bugfixes ### Bugfixes
- `getMovedDistanceFromToken` no longer returns incorrect values on gridless maps - `getMovedDistanceFromToken` no longer returns incorrect values on gridless maps
+6 -1
View File
@@ -1,13 +1,17 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/staebchenfisch) [![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 # 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. 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 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 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) ![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 ## 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. 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.
@@ -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)) - 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) - Shadow of the Demon Lord (starting with version 1.7.15)
- Wasteland Ventures (starting with version 0.1.0) - 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) - WWII:OWB (starting with version 1.0.4)
+5 -4
View File
@@ -2,10 +2,11 @@
"id": "drag-ruler", "id": "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.13.8", "version": "1.14.2",
"compatibility": { "compatibility": {
"minimum": "11", "minimum": "12",
"verified": "11" "verified": "12",
"maximum": "12"
}, },
"authors": [ "authors": [
{ {
@@ -80,7 +81,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.13.8.zip", "download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.14.2.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",
+2 -3
View File
@@ -133,10 +133,9 @@ export function getColorForDistanceAndToken(distance, token, ranges = null) {
export function getMovedDistanceFromToken(token) { export function getMovedDistanceFromToken(token) {
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active; const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
const history = getMovementHistory(token); 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) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
tokenPos.x += token.w / 2; tokenPos = token.center;
tokenPos.y += token.h / 2;
} }
const segments = CONFIG.Canvas.rulerClass const segments = CONFIG.Canvas.rulerClass
.dragRulerGetRaysFromWaypoints(history, tokenPos) .dragRulerGetRaysFromWaypoints(history, tokenPos)
+2 -2
View File
@@ -15,7 +15,7 @@ export function highlightMeasurementTerrainRuler(
} }
export function measureDistances(segments, entity, shape, options = {}) { export function measureDistances(segments, entity, shape, options = {}) {
const opts = duplicate(options); const opts = foundry.utils.duplicate(options);
if (canvas.grid.diagonalRule === "EUCL") { if (canvas.grid.diagonalRule === "EUCL") {
opts.ignoreGrid = true; opts.ignoreGrid = true;
opts.gridSpaces = false; opts.gridSpaces = false;
@@ -31,7 +31,7 @@ export function measureDistances(segments, entity, shape, options = {}) {
); );
previousSegments.forEach( previousSegments.forEach(
segment => segment =>
(segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces)), (segment.ray.terrainRulerVisitedSpaces = foundry.utils.duplicate(segment.ray.dragRulerVisitedSpaces)),
); );
opts.costFunction = buildCostFunction(entity, shape); opts.costFunction = buildCostFunction(entity, shape);
if (previousSegments.length > 0) 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 // 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) { export function getPixelsFromGridPosition(xGrid, yGrid) {
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) { let coord = getPixelsFromGridPositionObj({x: xGrid, y: yGrid});
return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid); return [coord.x, coord.y];
}
return canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid);
} }
// Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently // 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
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
export function getGridPositionFromPixels(xPixel, yPixel) { export function getGridPositionFromPixels(xPixel, yPixel) {
const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel); let coord = getGridPositionFromPixelsObj({x: xPixel, y: yPixel});
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return [y, x]; return [coord.x, coord.y];
return [x, 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) { export function getGridPositionFromPixelsObj(o) {
const r = {}; const coord = canvas.grid.getOffset(o);
[r.x, r.y] = getGridPositionFromPixels(o.x, o.y); return {x: coord.j, y: coord.i};
return r;
} }
// 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) { export function getPixelsFromGridPositionObj(o) {
const r = {}; return canvas.grid.getTopLeftPoint({j: o.x, i: o.y});
[r.x, r.y] = getPixelsFromGridPosition(o.x, o.y);
return r;
} }
export function getCenterFromGridPositionObj(o) { export function getCenterFromGridPositionObj(o) {
+13 -46
View File
@@ -35,7 +35,8 @@ export async function moveEntities(draggedEntity, selectedEntities) {
); );
}); });
if (hasCollision) { 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(); this._endMeasurement();
return true; return true;
} }
@@ -47,7 +48,10 @@ export async function moveEntities(draggedEntity, selectedEntities) {
await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused); await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused);
// Once all animations are complete we can clear the ruler // 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 // 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 // Extract event data
const destination = { const destination = {
x: event.interactionData.destination.x + this.rulerOffset.x, x: event.interactionData.destination.x,
y: event.interactionData.destination.y + this.rulerOffset.y, y: event.interactionData.destination.y,
}; };
// Hide any existing Token HUD // Hide any existing Token HUD
@@ -185,51 +189,14 @@ export function highlightMeasurementNative(
tokenShape = [{x: 0, y: 0}], tokenShape = [{x: 0, y: 0}],
alpha = 1, alpha = 1,
) { ) {
const spacer = canvas.scene.grid.type === CONST.GRID_TYPES.SQUARE ? 1.41 : 1; for (const offset of canvas.grid.getDirectPath([ray.A, ray.B]).reverse()) {
const nMax = Math.max( const point = canvas.grid.getTopLeftPoint(offset);
Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), const center = canvas.grid.getCenterPoint(offset);
1, const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, center)}]);
);
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})}]);
const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true})); const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true}));
const color = this.dragRulerGetColorForDistance(distance); 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); 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);
} }
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 * - Instead of taking a grid parameter, get the grid value from the globas canvas
*/ */
export function findVertexSnapPoint(x, y, altOrientationFlag) { export function findVertexSnapPoint(x, y, altOrientationFlag) {
const grid = canvas.grid.grid; const grid = canvas.grid;
if (grid.columnar) { if (grid.columnar) {
return findSnapPointCols(x, y, grid.h, grid.w, altOrientationFlag); return findSnapPointCols(x, y, grid.sizeY, grid.sizeX, altOrientationFlag);
} else { } 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; const ruler = canvas.controls.ruler;
ruler.draggedEntity = this; ruler.draggedEntity = this;
const entityCenter = getEntityCenter(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")) { if (game.settings.get(settingsKey, "autoStartMeasurement")) {
let options = {}; let options = {};
setSnapParameterOnOptions(ruler, options); setSnapParameterOnOptions(ruler, options);
@@ -149,7 +145,7 @@ function onEntityLeftDragStart(wrapped, event) {
} }
function onEntityLeftDragMoveSnap(wrapped, event) { function onEntityLeftDragMoveSnap(wrapped, event) {
applyGridlessSnapping.call(this, event); applyGridlessSnapping.call(canvas.controls.ruler, event);
onEntityLeftDragMove.call(this, wrapped, event); onEntityLeftDragMove.call(this, wrapped, event);
} }
@@ -217,7 +213,7 @@ function applyGridlessSnapping(event) {
const rasterWidth = 35 / canvas.stage.scale.x; const rasterWidth = 35 / canvas.stage.scale.x;
const tokenX = event.interactionData.destination.x; const tokenX = event.interactionData.destination.x;
const tokenY = event.interactionData.destination.y; 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 ranges = getRangesFromSpeedProvider(ruler.draggedEntity);
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active; const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
@@ -241,13 +237,13 @@ function applyGridlessSnapping(event) {
const deltaY = destination.y - rasterLocation.y; const deltaY = destination.y - rasterLocation.y;
const rasterDistance = Math.hypot(deltaX, deltaY); const rasterDistance = Math.hypot(deltaX, deltaY);
if (rasterDistance < rasterWidth) { if (rasterDistance < rasterWidth) {
event.interactionData.destination.x = rasterLocation.x - ruler.rulerOffset.x; event.interactionData.destination.x = rasterLocation.x;
event.interactionData.destination.y = rasterLocation.y - ruler.rulerOffset.y; event.interactionData.destination.y = rasterLocation.y;
} }
} }
} else { } else {
let waypointDistance = 0; let waypointDistance = 0;
let origin = event.interactionData.origin; let origin = this.draggedEntity.getCenterPoint();
if (ruler.waypoints.length > 1) { if (ruler.waypoints.length > 1) {
const segments = ruler.constructor const segments = ruler.constructor
.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination) .dragRulerGetRaysFromWaypoints(ruler.waypoints, destination)
@@ -256,7 +252,7 @@ function applyGridlessSnapping(event) {
}); });
origin = segments.pop().ray.A; origin = segments.pop().ray.A;
waypointDistance = canvas.grid.measureDistances(segments).reduce((a, b) => a + b); 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; const deltaX = tokenX - origin.x;
+34 -19
View File
@@ -41,14 +41,17 @@ export function extendRuler() {
if (!this.isDragRuler) return await super.moveToken(event); if (!this.isDragRuler) return await super.moveToken(event);
} }
toJSON() { _getMeasurementData() {
const json = super.toJSON(); const json =
typeof super._getMeasurementData === "function"
? super._getMeasurementData()
: 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 => { json.waypoints = json.waypoints.map(old => {
let w = duplicate(old); let w = foundry.utils.duplicate(old);
w.isPathfinding = undefined; w.isPathfinding = undefined;
return w; return w;
}); });
@@ -56,7 +59,13 @@ export function extendRuler() {
return json; return json;
} }
/** @deprecated since V12 */
toJSON() {
return this._getMeasurementData();
}
update(data) { update(data) {
if (!data || data.state === Ruler.STATES.INACTIVE) return this.clear();
// Don't show a GMs drag ruler to non GM players // Don't show a GMs drag ruler to non GM players
if ( if (
data.draggedEntity && data.draggedEntity &&
@@ -105,7 +114,10 @@ export function extendRuler() {
// Compute the measurement destination, segments, and distance // Compute the measurement destination, segments, and distance
const d = this._getMeasurementDestination(destination); 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; this.destination = d;
// TODO Check if we can reuse the old path // TODO Check if we can reuse the old path
@@ -206,6 +218,7 @@ export function extendRuler() {
} }
} }
} }
this.dragRulerSendState();
return this.segments; return this.segments;
} }
@@ -227,7 +240,7 @@ export function extendRuler() {
const waypoints = const waypoints =
this.draggedEntity instanceof Token this.draggedEntity instanceof Token
? applyTokenSizeOffset(unsnappedWaypoints, this.draggedEntity) ? applyTokenSizeOffset(unsnappedWaypoints, this.draggedEntity)
: duplicate(unsnappedWaypoints); : foundry.utils.duplicate(unsnappedWaypoints);
const unsnappedSegments = []; const unsnappedSegments = [];
const segments = []; const segments = [];
for (const [i, p1] of waypoints.entries()) { for (const [i, p1] of waypoints.entries()) {
@@ -252,6 +265,9 @@ export function extendRuler() {
unsnappedSegments.push({ray: unsnappedRay, label}); unsnappedSegments.push({ray: unsnappedRay, label});
} }
this.dragRulerUnsnappedSegments = unsnappedSegments; this.dragRulerUnsnappedSegments = unsnappedSegments;
if (this.labels.children.length > segments.length) {
this.labels.removeChildren(segments.length).forEach(c => c.destroy());
}
return segments; return segments;
} else { } else {
return super._getMeasurementSegments(); return super._getMeasurementSegments();
@@ -269,14 +285,14 @@ export function extendRuler() {
enableTerrainRuler: this.dragRulerEnableTerrainRuler, enableTerrainRuler: this.dragRulerEnableTerrainRuler,
}; };
const distances = measureDistances(this.segments, this.draggedEntity, shape, options); const distances = measureDistances(this.segments, this.draggedEntity, shape, options);
let totalDistance = 0; this.totalDistance = 0;
for (const [i, d] of distances.entries()) { for (const [i, d] of distances.entries()) {
let s = this.segments[i]; let s = this.segments[i];
s.startDistance = totalDistance; s.startDistance = this.totalDistance;
totalDistance += d; this.totalDistance += d;
s.last = i === this.segments.length - 1; s.last = i === this.segments.length - 1;
s.distance = d; s.distance = d;
s.text = this._getSegmentLabel(s, totalDistance); s.text = this._getSegmentLabel(s);
} }
for (const [i, segment] of this.segments.entries()) { for (const [i, segment] of this.segments.entries()) {
@@ -375,15 +391,11 @@ export function extendRuler() {
if (this.waypoints.filter(w => !w.isPrevious).length > 1) { if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
event.preventDefault(); event.preventDefault();
const mousePosition = getPointer().getLocalPosition(canvas.tokens); const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const rulerOffset = this.rulerOffset;
// Options are not passed to _removeWaypoint in vanilla Foundry. // 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) // Send them in case other modules have overriden that behavior and accept an options parameter (Toggle Snap to Grid)
this._removeWaypoint( this._removeWaypoint({x: mousePosition.x, y: mousePosition.y}, options);
{x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}, this.performPostPathfindingActions(options);
options,
);
game.user.broadcastActivity({ruler: this});
} else { } else {
this.dragRulerAbortDrag(event); this.dragRulerAbortDrag(event);
} }
@@ -428,7 +440,7 @@ export function extendRuler() {
this.dragRulerAddWaypoint(waypoint, {snap: false}); this.dragRulerAddWaypoint(waypoint, {snap: false});
} }
this.measure(this.destination); this.measure(this.destination);
game.user.broadcastActivity({ruler: this}); this.dragRulerSendState();
} }
static dragRulerGetRaysFromWaypoints(waypoints, destination) { static dragRulerGetRaysFromWaypoints(waypoints, destination) {
@@ -478,15 +490,18 @@ export function extendRuler() {
ruler.dragRulerAddWaypoint(entityCenter, {snap: false}); ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
const mousePosition = getPointer().getLocalPosition(canvas.tokens); const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const destination = { const destination = {
x: mousePosition.x + ruler.rulerOffset.x, x: mousePosition.x,
y: mousePosition.y + ruler.rulerOffset.y, y: mousePosition.y,
}; };
if (measureImmediately) ruler.measure(destination, options); if (measureImmediately) ruler.measure(destination, options);
} }
dragRulerSendState() { dragRulerSendState() {
if (this.user !== game.user) {
return;
}
game.user.broadcastActivity({ 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) { for (const setting of provider.settings) {
try { try {
if (setting.scope === "world" && !game.user.isGM) continue; 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.id = `${provider.id}.setting.${s.id}`;
s.name = game.i18n.localize(s.name); s.name = game.i18n.localize(s.name);
s.hint = game.i18n.localize(s.hint); s.hint = game.i18n.localize(s.hint);
+1 -1
View File
@@ -115,7 +115,7 @@ export class GenericSpeedProvider extends SpeedProvider {
getRanges(token) { getRanges(token) {
const speedAttribute = this.getSetting("speedAttribute"); const speedAttribute = this.getSetting("speedAttribute");
if (!speedAttribute) return []; if (!speedAttribute) return [];
const tokenSpeed = parseFloat(getProperty(token, speedAttribute)); const tokenSpeed = parseFloat(foundry.utils.getProperty(token, speedAttribute));
if (tokenSpeed === undefined) { if (tokenSpeed === undefined) {
console.warn( 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).`, `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).`,
+42 -29
View File
@@ -37,20 +37,20 @@ export function getHexTokenSize(token) {
} }
export function getEntityCenter(token) { export function getEntityCenter(token) {
if (token instanceof Token && canvas.grid.isHex) { if (token instanceof Token && isCanvasHex()) {
const center = token.center; const center = token.center;
const size = getHexTokenSize(token); const size = getHexTokenSize(token);
if (size % 2 === 0) { if (size % 2 === 0) {
let offset; let offset;
if (canvas.grid.grid.columnar) { if (canvas.grid.columnar) {
offset = canvas.grid.grid.w - canvas.grid.grid.h; offset = canvas.grid.sizeX - canvas.grid.sizeY;
} else { } else {
offset = canvas.grid.grid.h - canvas.grid.grid.w; offset = canvas.grid.sizeY - canvas.grid.sizeX;
} }
if (getAltOrientationFlagForToken(token, size)) { if (getAltOrientationFlagForToken(token, size)) {
offset *= -1; offset *= -1;
} }
if (canvas.grid.grid.columnar) { if (canvas.grid.columnar) {
center.x -= offset; center.x -= offset;
return center; return center;
} else { } else {
@@ -79,7 +79,7 @@ export function getSnapPointForToken(x, y, token) {
return {x, y}; return {x, y};
} }
if (canvas.grid.isHex) { if (isCanvasHex()) {
const size = getHexTokenSize(token); const size = getHexTokenSize(token);
if (size % 2 === 0) { if (size % 2 === 0) {
return findVertexSnapPoint(x, y, getAltOrientationFlagForToken(token, size)); 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); const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
let cellX, cellY; let cell = {};
if (token.document.width % 2 === 0) cellX = x - canvas.grid.h / 2; if (token.document.width % 2 === 0) cell.x = x - canvas.grid.sizeY / 2;
else cellX = x; else cell.x = x;
if (token.document.height % 2 === 0) cellY = y - canvas.grid.h / 2; if (token.document.height % 2 === 0) cell.y = y - canvas.grid.sizeY / 2;
else cellY = y; else cell.y = y;
const [centerX, centerY] = canvas.grid.getCenter(cellX, cellY); const center = canvas.grid.getCenterPoint(cell);
let snapX, snapY; let snapX, snapY;
// Tiny tokens can snap to the cells corners // Tiny tokens can snap to the cells corners
if (token.document.width <= 0.5) { if (token.document.width <= 0.5) {
const offsetX = x - topLeftX; 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); const subGridPosX = Math.floor(offsetX / subGridWidth);
snapX = topLeftX + (subGridPosX + 0.5) * 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 // 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) { 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 // All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
else { else {
snapX = centerX + canvas.grid.w / 2; snapX = center.x + canvas.grid.sizeX / 2;
} }
if (token.document.height <= 0.5) { if (token.document.height <= 0.5) {
const offsetY = y - topLeftY; const offsetY = y - topLeftY;
@@ -117,9 +117,9 @@ export function getSnapPointForToken(x, y, token) {
const subGridPosY = Math.floor(offsetY / subGridHeight); const subGridPosY = Math.floor(offsetY / subGridHeight);
snapY = topLeftY + (subGridPosY + 0.5) * subGridHeight; snapY = topLeftY + (subGridPosY + 0.5) * subGridHeight;
} else if (Math.round(token.document.height) % 2 === 1 || token.document.height < 1) { } else if (Math.round(token.document.height) % 2 === 1 || token.document.height < 1) {
snapY = centerY; snapY = center.y;
} else { } else {
snapY = centerY + canvas.grid.h / 2; snapY = center.y + canvas.grid.sizeY / 2;
} }
return {x: snapX, y: snapY}; return {x: snapX, y: snapY};
} }
@@ -147,7 +147,7 @@ export function highlightTokenShape(position, shape, color, alpha) {
const area = getAreaFromPositionAndShape(position, shape); const area = getAreaFromPositionAndShape(position, shape);
for (const space of area) { for (const space of area) {
const [x, y] = getPixelsFromGridPosition(space.x, space.y); 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 => { return shape.map(space => {
let x = position.x + space.x; let x = position.x + space.x;
let y = position.y + space.y; let y = position.y + space.y;
if (canvas.grid.isHex) { if (isCanvasHex()) {
let shiftedRow; let shiftedRow;
if (canvas.grid.grid.options.even) shiftedRow = 1; if (canvas.grid?.even) shiftedRow = 1;
else shiftedRow = 0; else shiftedRow = 0;
if (canvas.grid.grid.columnar) { if (canvas.grid.grid.columnar) {
if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) { if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
@@ -212,6 +212,16 @@ export function getTokenShape(token) {
{x: 0, y: -2}, {x: 0, y: -2},
{x: 1, 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)) { if (getAltOrientationFlagForToken(token, size)) {
shape.forEach(space => (space.y *= -1)); shape.forEach(space => (space.y *= -1));
@@ -226,7 +236,7 @@ export function getTokenShape(token) {
export function getTokenSize(token) { export function getTokenSize(token) {
let w, h; let w, h;
if (canvas.grid.isHex) { if (isCanvasHex()) {
w = h = getHexTokenSize(token); w = h = getHexTokenSize(token);
} else { } else {
w = token.document.width; w = token.document.width;
@@ -244,26 +254,26 @@ export function applyTokenSizeOffset(waypoints, token) {
const tokenSize = getTokenSize(token); const tokenSize = getTokenSize(token);
const waypointOffset = {x: 0, y: 0}; const waypointOffset = {x: 0, y: 0};
if (canvas.grid.isHex) { if (isCanvasHex()) {
const isAltOrientation = getAltOrientationFlagForToken(token, getHexTokenSize(token)); const isAltOrientation = getAltOrientationFlagForToken(token, getHexTokenSize(token));
if (canvas.grid.grid.columnar) { if (canvas.grid.grid.columnar) {
if (tokenSize.w % 2 === 0) { if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2; waypointOffset.x = canvas.grid.sizeX / 2;
if (isAltOrientation) waypointOffset.x *= -1; if (isAltOrientation) waypointOffset.x *= -1;
} }
} else { } else {
if (tokenSize.h % 2 === 0) { if (tokenSize.h % 2 === 0) {
waypointOffset.y = canvas.grid.h / 2; waypointOffset.y = canvas.grid.sizeY / 2;
if (isAltOrientation) waypointOffset.y *= -1; if (isAltOrientation) waypointOffset.y *= -1;
} }
} }
// If hex size support isn't active leave the waypoints like they are // If hex size support isn't active leave the waypoints like they are
} else { } else {
if (tokenSize.w % 2 === 0) { if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2; waypointOffset.x = canvas.grid.sizeX / 2;
} }
if (tokenSize.h % 2 === 0) { 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() { export function getPointer() {
return canvas.app.renderer.plugins.interaction?.mouse ?? canvas.app.renderer.events.pointer; return canvas.app.renderer.events.pointer;
} }
export function getMeasurePosition() { export function getMeasurePosition() {
const mousePosition = getPointer().getLocalPosition(canvas.tokens); const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const rulerOffset = canvas.controls.ruler.rulerOffset; const measurePosition = {x: mousePosition.x, y: mousePosition.y};
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
return measurePosition; return measurePosition;
} }
@@ -314,3 +323,7 @@ export function isPathfindingEnabled() {
if (moveWithoutAnimation) return false; if (moveWithoutAnimation) return false;
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding; return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
} }
function isCanvasHex() {
return canvas.grid.isHexagonal;
}