Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d67ca01c3 | |||
| 80ff8832fd | |||
| c787b18fcb | |||
| c03dbc09ff | |||
| 27a2b46a8a | |||
| 94a8e6f147 | |||
| ab1f5b4c9b | |||
| 1974e6e4a3 | |||
| cb40c8de50 | |||
| 6da3e65301 | |||
| a7d06eaed3 | |||
| 18cf63895c | |||
| 0e2ab35db7 | |||
| 0808f17ee3 | |||
| b649dacdb5 | |||
| c490550ed9 | |||
| f12dbf0e61 | |||
| f1fcc52867 | |||
| af03341638 | |||
| 79db620fc1 | |||
| 8bf075c2ce | |||
| f0c6ce1bcc | |||
| 7b392e5c9d | |||
| efe6eac5aa | |||
| 52b64c3016 | |||
| 48d0d17628 | |||
| 8101381cc4 | |||
| 844df150a6 | |||
| 47715e95f6 | |||
| 1dccbcb081 | |||
| 4c3d7ab42a | |||
| 61fc795f7b | |||
| f10fa049b9 | |||
| c844318836 | |||
| 9af692566c | |||
| d08416777b |
@@ -0,0 +1,4 @@
|
||||
*.md
|
||||
*.html
|
||||
*.json
|
||||
foundry.js
|
||||
@@ -1,3 +1,47 @@
|
||||
## 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
|
||||
|
||||
|
||||
## 1.13.7
|
||||
### Compatibility
|
||||
- Drag Ruler's generic speed provider is now aware of good defaults for the Crucible game system
|
||||
|
||||
|
||||
## 1.13.6
|
||||
### Bugfixes
|
||||
- Fixed a bug that caused no measurements to be shown next to the ruler
|
||||
- Fixed a bug where diagonal paths would sometimes highlight squares that don't blong to the path on square maps
|
||||
|
||||
|
||||
## 1.13.5
|
||||
### Compatibility
|
||||
- Drag Ruler is now compatible with Foundry VTT v11 (thanks to pkonshik for doing much of the porting work!)
|
||||
- Drag Ruler's generic speed provider is now aware of good defaults for Warhammer Fantasy Roleplay 4th Edition
|
||||
|
||||
### Translations
|
||||
- Updated Portugese (Brazil) translation (thanks eunaumtenhoid!)
|
||||
|
||||
|
||||
## 1.13.4
|
||||
### Translations
|
||||
- New translation: Portuguese (Brazil) (thanks eunaumtenhoid!)
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
[](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.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -78,7 +78,7 @@
|
||||
},
|
||||
"showGMRulerToPlayers": {
|
||||
"name": "Mostrar régua do GM aos jogadores",
|
||||
"hint": "Se desativado, o governante dos GMs não será exibido para jogadores não-GM."
|
||||
"hint": "Se desativado, a régua dos GMs não será exibido para jogadores não-GM."
|
||||
},
|
||||
"speedProviderSettings": {
|
||||
"name": "Configurações do provedor de velocidade",
|
||||
@@ -114,7 +114,7 @@
|
||||
},
|
||||
"useGridlessRaster": {
|
||||
"name": "Use o ajuste baseado em velocidade",
|
||||
"hint": "Em cenas Gridless, isso faz com que os tokens se encaixem nas faixas de velocidade do token."
|
||||
"hint": "Em cenas sem grid, isso faz com que os tokens se encaixem nas faixas de velocidade do token."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -2,16 +2,17 @@
|
||||
"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.4",
|
||||
"version": "1.14.2",
|
||||
"compatibility": {
|
||||
"minimum": "10",
|
||||
"verified": "10"
|
||||
"minimum": "12",
|
||||
"verified": "12",
|
||||
"maximum": "12"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Manuel Vögele",
|
||||
"email": "develop@manuel-voegele.de",
|
||||
"discord": "Stäbchenfisch#5107"
|
||||
"discord": "stabchenfisch"
|
||||
}
|
||||
],
|
||||
"esmodules": [
|
||||
@@ -80,7 +81,7 @@
|
||||
},
|
||||
"socket": true,
|
||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.13.4.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",
|
||||
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
|
||||
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
||||
|
||||
+5
-1
@@ -133,8 +133,12 @@ export function getColorForDistanceAndToken(distance, token, ranges = null) {
|
||||
export function getMovedDistanceFromToken(token) {
|
||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
||||
const history = getMovementHistory(token);
|
||||
let tokenPos = {x: token.x, y: token.y};
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
tokenPos = token.center;
|
||||
}
|
||||
const segments = CONFIG.Canvas.rulerClass
|
||||
.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y})
|
||||
.dragRulerGetRaysFromWaypoints(history, tokenPos)
|
||||
.map(ray => {
|
||||
return {ray};
|
||||
});
|
||||
|
||||
@@ -15,10 +15,10 @@ 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.gridSpaes = false;
|
||||
opts.gridSpaces = false;
|
||||
}
|
||||
if (opts.enableTerrainRuler) {
|
||||
opts.gridSpaces = true;
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
+14
-47
@@ -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.data.destination.x + this.rulerOffset.x,
|
||||
y: event.data.destination.y + this.rulerOffset.y,
|
||||
x: event.interactionData.destination.x,
|
||||
y: event.interactionData.destination.y,
|
||||
};
|
||||
|
||||
// Hide any existing Token HUD
|
||||
@@ -148,7 +152,7 @@ export function onMouseMove(event) {
|
||||
function scheduleMeasurement(destination, event) {
|
||||
const measurementInterval = 50;
|
||||
const mt = event._measureTime || 0;
|
||||
const originalEvent = event.data.originalEvent;
|
||||
const originalEvent = event.interactionData.originalEvent;
|
||||
if (Date.now() - mt > measurementInterval) {
|
||||
this.measure(destination, {snap: !disableSnap});
|
||||
event._measureTime = Date.now();
|
||||
@@ -185,51 +189,14 @@ export function highlightMeasurementNative(
|
||||
tokenShape = [{x: 0, y: 0}],
|
||||
alpha = 1,
|
||||
) {
|
||||
const spacer = canvas.scene.gridType === 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 [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 snapPoint = getSnapPointForToken(point.x + 1, point.y + 1, 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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-14
@@ -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.data.origin.x,
|
||||
y: entityCenter.y - event.data.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);
|
||||
}
|
||||
|
||||
@@ -215,9 +211,9 @@ function applyGridlessSnapping(event) {
|
||||
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return;
|
||||
|
||||
const rasterWidth = 35 / canvas.stage.scale.x;
|
||||
const tokenX = event.data.destination.x;
|
||||
const tokenY = event.data.destination.y;
|
||||
const destination = {x: tokenX + ruler.rulerOffset.x, y: tokenY + ruler.rulerOffset.y};
|
||||
const tokenX = event.interactionData.destination.x;
|
||||
const tokenY = event.interactionData.destination.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.data.destination.x = rasterLocation.x - ruler.rulerOffset.x;
|
||||
event.data.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.data.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;
|
||||
@@ -271,8 +267,8 @@ function applyGridlessSnapping(event) {
|
||||
.reduce((a, b) => Math.max(a, b), 0);
|
||||
if (targetDistance) {
|
||||
if (distance < targetDistance + rasterWidth) {
|
||||
event.data.destination.x = origin.x + (deltaX * targetDistance) / distance;
|
||||
event.data.destination.y = origin.y + (deltaY * targetDistance) / distance;
|
||||
event.interactionData.destination.x = origin.x + (deltaX * targetDistance) / distance;
|
||||
event.interactionData.destination.y = origin.y + (deltaY * targetDistance) / distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-33
@@ -17,6 +17,7 @@ import {
|
||||
getTokenShape,
|
||||
isPathfindingEnabled,
|
||||
} from "./util.js";
|
||||
import {getPointer} from "./util.js";
|
||||
|
||||
export function extendRuler() {
|
||||
class DragRulerRuler extends CONFIG.Canvas.rulerClass {
|
||||
@@ -40,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;
|
||||
});
|
||||
@@ -55,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 &&
|
||||
@@ -104,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
|
||||
@@ -205,6 +218,7 @@ export function extendRuler() {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dragRulerSendState();
|
||||
return this.segments;
|
||||
}
|
||||
|
||||
@@ -226,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()) {
|
||||
@@ -251,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();
|
||||
@@ -261,12 +278,6 @@ export function extendRuler() {
|
||||
if (!this.isDragRuler) {
|
||||
return super._computeDistance(gridSpaces);
|
||||
}
|
||||
if (!this.dragRulerEnableTerrainRuler) {
|
||||
if (!this.dragRulerIgnoreGrid) {
|
||||
gridSpaces = true;
|
||||
}
|
||||
super._computeDistance(gridSpaces);
|
||||
} else {
|
||||
const shape = this.draggedEntity ? getTokenShape(this.draggedEntity) : null;
|
||||
const options = {
|
||||
ignoreGrid: this.dragRulerIgnoreGrid,
|
||||
@@ -274,16 +285,16 @@ 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()) {
|
||||
const unsnappedSegment = this.dragRulerUnsnappedSegments[i];
|
||||
unsnappedSegment.startDistance = segment.startDistance;
|
||||
@@ -379,18 +390,12 @@ export function extendRuler() {
|
||||
options.snap = options.snap ?? true;
|
||||
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
||||
event.preventDefault();
|
||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(
|
||||
canvas.tokens,
|
||||
);
|
||||
const rulerOffset = this.rulerOffset;
|
||||
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
|
||||
|
||||
// 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);
|
||||
}
|
||||
@@ -416,7 +421,7 @@ export function extendRuler() {
|
||||
this._endMeasurement();
|
||||
|
||||
// Deactivate the drag workflow in mouse
|
||||
token.mouseInteractionManager._deactivateDragEvents();
|
||||
token.mouseInteractionManager.cancel(event);
|
||||
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER;
|
||||
|
||||
// This will cancel the current drag operation
|
||||
@@ -435,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) {
|
||||
@@ -483,19 +488,20 @@ export function extendRuler() {
|
||||
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
||||
ruler.dragRulerAddWaypointHistory(getMovementHistory(entity));
|
||||
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
|
||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(
|
||||
canvas.tokens,
|
||||
);
|
||||
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
@@ -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);
|
||||
|
||||
@@ -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).`,
|
||||
|
||||
@@ -23,6 +23,10 @@ export function getDefaultSpeedAttribute() {
|
||||
return "actor.system.combatValues.movement.total";
|
||||
case "splittermond":
|
||||
return "actor.derivedValues.speed.value";
|
||||
case "wfrp4e":
|
||||
return "actor.system.details.move.walk";
|
||||
case "crucible":
|
||||
return "actor.system.movement.stride";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -45,6 +49,10 @@ export function getDefaultDashMultiplier() {
|
||||
return 5;
|
||||
case "splittermond":
|
||||
return 3;
|
||||
case "wfrp4e":
|
||||
return 2;
|
||||
case "crucible":
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+46
-31
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,12 +295,13 @@ export function isClose(a, b, delta) {
|
||||
return Math.abs(a - b) <= delta;
|
||||
}
|
||||
|
||||
export function getPointer() {
|
||||
return canvas.app.renderer.events.pointer;
|
||||
}
|
||||
|
||||
export function getMeasurePosition() {
|
||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(
|
||||
canvas.tokens,
|
||||
);
|
||||
const rulerOffset = canvas.controls.ruler.rulerOffset;
|
||||
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
|
||||
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
|
||||
const measurePosition = {x: mousePosition.x, y: mousePosition.y};
|
||||
return measurePosition;
|
||||
}
|
||||
|
||||
@@ -312,3 +323,7 @@ export function isPathfindingEnabled() {
|
||||
if (moveWithoutAnimation) return false;
|
||||
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
|
||||
}
|
||||
|
||||
function isCanvasHex() {
|
||||
return canvas.grid.isHexagonal;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user