Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eef05553c0 | |||
| 7ba89e4229 | |||
| d11d6385e2 | |||
| 4d2e4b7715 | |||
| 3cbe41e2be | |||
| 732802c579 | |||
| 8bbb11ddaa | |||
| e6cee3477a | |||
| 7cee1e7af0 | |||
| a7811d6894 | |||
| e637f8d28f | |||
| acd2e74064 | |||
| fd81833583 | |||
| a413d44795 | |||
| e63ee1988d | |||
| 4aab6eec95 | |||
| 38b7df0248 | |||
| bbd30be80e | |||
| 78c96f9059 | |||
| d1367aa82e | |||
| 9f70e5bc27 | |||
| d2876671cf | |||
| 19abc9eedc | |||
| 286aaf7ada | |||
| 85e167af45 | |||
| 5f0849f12d | |||
| ba0012e875 |
@@ -1,3 +1,46 @@
|
||||
## 1.7.3
|
||||
### Compatibility
|
||||
- Drag Ruler is now compatible with Foundry 0.8.5
|
||||
|
||||
|
||||
## 1.7.2
|
||||
### Bugfixes
|
||||
- Fixed a bug that prevented waypoints for measurement templates from snapping to any other point than a grid cell corner (or grid cell center on hex)
|
||||
- Fixed a bug that could cause the ruler to not end up at the token's center (especially if the token is being moved very quickly and then stopped abruptly)
|
||||
|
||||
|
||||
## 1.7.1
|
||||
### Bugfixes
|
||||
- Fixed a bug that prevented players from moving their tokens ([#74](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/74))
|
||||
|
||||
|
||||
## 1.7.0
|
||||
**BREAKING** This update is incompatible with previous Terrain Ruler versions. If you're using Terrain Ruler, make sure you update Terrain Ruler to at least version 1.3.0.
|
||||
|
||||
### New features
|
||||
- A ruler will now be shown when dragging measurement templates over the map ([#13](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/13))
|
||||
- Drag Ruler can now measure difficult terrain on gridless maps (if the Terrain Ruler module is installed and enabled)
|
||||
- Improved the positioning of the labels around the ruler. The labels should now never overlap with the waypoint.
|
||||
|
||||
### Bugfixes
|
||||
- Fixed a bug that sometimes measured diagonals incorrectly with the 5/10/5 grid rule
|
||||
- Fixed a bug that would cause the ruler to re-measure when the shift key is being pressed or released while a token is moving
|
||||
|
||||
### Compatibility
|
||||
- Drag Ruler's Generic Speed Provider is now aware of good default values for the Starfinder game system
|
||||
|
||||
### Translation
|
||||
- Corrected typos in the german translation (thanks to CarnVanBeck!)
|
||||
|
||||
### API
|
||||
- The old API that Drag Ruler offered prior to version 1.3.0 is now deprecated. Speed Providers that still use this API will continue to work for now, but will generate a warning in the console about the deprecation. All modules and game systems offered on the FoundryVTT website have already updated to the new API. If you see the deprecation warning, please consider updating to the current version of the respective system/module you're using.
|
||||
|
||||
|
||||
## 1.6.5
|
||||
### Bugfixes
|
||||
- Drag Ruler no longer gets stuck if the user presses ESC during drag ([#70](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/70))
|
||||
|
||||
|
||||
## 1.6.4
|
||||
### Bugfixes
|
||||
- Fixed a bug where a bug in a Speed Provider could lead to the ruler getting stuck, leaving the token immovable
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
[](https://ko-fi.com/staebchenfisch)
|
||||
|
||||
# Drag Ruler
|
||||
This module shows a ruler when you drag a token to infrom you how far you've dragged the token from it's start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. 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 infrom you how far you've dragged it from it's 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 red 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.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Path color
|
||||

|
||||
## 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. If the [Hex Token Size Support](https://foundryvtt.com/packages/hex-size-support) is installed this is also true for large tokens on hex scenes.
|
||||
|
||||
The token has a speed of 30ft. All squares on the path within that range are colored in the players color. If the token is running it can cover double that range (this can be changed in the settings). All Squares on the path that can only be reached while running are colored in yellow. Squares on the path that the token cannot possibly reached at regular speeds are colored red. This coloring behavior can be tweaked in the settings and can be overridden by modules and game systems to provide more granular, game system specific control. For more information on how to do this see the [API](#api) section of this document.
|
||||

|
||||

|
||||
|
||||
|
||||
## Waypoints
|
||||
You can add waypoints to the path by pressing spacebar while you drag the token. To remove a placed waypoint press the right mouse button.
|
||||
## Difficult Terrain support
|
||||
**To use support for difficult terrain you must install the [Terrain Ruler](https://foundryvtt.com/packages/terrain-ruler) module**
|
||||
|
||||

|
||||
With the Terrain Ruler module installed, Drag Ruler is able to take difficult terrain that was placed down using the [Enhanced Terrain Layer](https://foundryvtt.com/packages/enhanced-terrain-layer) or [TerrainLayer](https://foundryvtt.com/packages/TerrainLayer) module into account.
|
||||
|
||||
## Difficult Terrain
|
||||
**To use support for difficult terrain you must install the [Terrain Ruler module](https://foundryvtt.com/packages/terrain-ruler)**
|
||||
|
||||
With the Terrain Ruler module installed, Drag Ruler is able to take difficult terrain that was placed with the [TerrainLayer module](https://foundryvtt.com/packages/TerrainLayer/) into account when measuring distances.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Why would I want to use this instead of ShowDragDistance?
|
||||
ShowDragDistance isn't maintained anymore. This means that it is at risk to stop working with every foundry update. In fact this process has already begun. As of Foundry Version 0.7.9 ShowDragDistance doesn't work anymore on gridless maps. Drag Ruler on the other hand is fully compatible with the current Foundry release and I'll continue updating it for future foundry releases for the forseeable future.
|
||||
## Movement history (optional)
|
||||
*This feature can be disabled in the settings if you don't like it*
|
||||
|
||||
In addition Drag Ruler provides more flexibility for game systems and modules via it's api to provide an experience that fits the rules of the game system that you are playing best.
|
||||
During combat, Drag Ruler will remember the path a token has taken during it's turn. When the token is being picked up again, Drag Ruler will continue measuring where it has left off. The path of the previous movement will be dipslayed in a faded color.
|
||||
|
||||

|
||||
|
||||
|
||||
## Game systems with Drag Ruler integration
|
||||
@@ -34,11 +37,13 @@ Drag Ruler will work with all Foundry VTT game systems. However some game system
|
||||
|
||||
The game systems that offer Drag Ruler integration are:
|
||||
- Cypher System (starting with version 1.13.0)
|
||||
- Ironclaw Second Edition (starting with version 0.2.2)
|
||||
- Pathfinder 1 (starting with version 0.77.3)
|
||||
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
|
||||
- Tagmar RPG (starting with version 1.1.4)
|
||||
- Tormenta20 (starting with version 1.1.37)
|
||||
- Shadow of the Demon Lord (starting with version 1.7.15)
|
||||
- Wasteland Ventures (starting with version 0.1.0)
|
||||
- WWII:OWB (starting with version 1.0.4)
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@
|
||||
"ok": "OK",
|
||||
"terrain-ruler": {
|
||||
"title": "Wie man schwieriges Gelände mit Drag Ruler misst",
|
||||
"text": "Du hast das {moduleName} Modul installiert. Drag Ruler kann schwieriges Gelände, das mit diesem Modul platziert wurde in seinen Messungen berücksichtigen. Hierzu greift Drag Ruler auf die Funktionalität von Terrain Ruler zurück. Wenn Terrain Ruler installiert und aktiviert ist wird Drag Ruler automatisch anfangen schwiriges Gelände in den Messungen zu berücksichtigen.",
|
||||
"text": "Du hast das {moduleName} Modul installiert. Drag Ruler kann schwieriges Gelände, das mit diesem Modul platziert wurde in seinen Messungen berücksichtigen. Hierzu greift Drag Ruler auf die Funktionalität von Terrain Ruler zurück. Wenn Terrain Ruler installiert und aktiviert ist wird Drag Ruler automatisch anfangen schwieriges Gelände in den Messungen zu berücksichtigen.",
|
||||
"neverShowAgain": "Zeige dies nie wieder an"
|
||||
},
|
||||
"socketlib": {
|
||||
@@ -39,7 +39,7 @@
|
||||
"hint": "Wenn diesen Option aktiviert ist merkt sich Drag Ruler die Bewegungen, die während einer Runde eines Kampfes gemacht wurden und zeigt diese an, wenn das Token weiterbewegt wird."
|
||||
},
|
||||
"showGMRulerToPlayers": {
|
||||
"name": "Lineal des Sipelleiters bei Spielern anzeigen",
|
||||
"name": "Lineal des Spielleiters bei Spielern anzeigen",
|
||||
"hint": "Wenn diese Option deaktiviert ist wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt."
|
||||
},
|
||||
"speedProviderSettings": {
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
"activeProvider": {
|
||||
"name": "Verwendete Spielsystemintegration",
|
||||
"hint": "Die unten angezeigten Einstellungen hängen von der verwendeten Spielsystemintegration ab. Wenn der Spielleiter eine andere Spielsystemintegration auswählt können sich die zur Verfügung stehnden Einstellungen ändern."
|
||||
"hint": "Die unten angezeigten Einstellungen hängen von der verwendeten Spielsystemintegration ab. Wenn der Spielleiter eine andere Spielsystemintegration auswählt können sich die zur Verfügung stehenden Einstellungen ändern."
|
||||
},
|
||||
"noSettings": "Diese Spielsystemintegration hat keine Einstellmöglichkeiten",
|
||||
"color": {
|
||||
@@ -66,7 +66,7 @@
|
||||
},
|
||||
"speedProvider": {
|
||||
"name": "Verwendete Spielsystemintegration",
|
||||
"hint": "Wähle aus, welche Spielsystemintegration für die farbliche Hervorhebung der Felder zuständig sein soll. Drag Ruler stellt eine generische Spielsystemintegration zur Verfügung, die grundlegende Funktionalität bietet und bei korrekter konfiguration mit allen Spielsystemen funktionieren sollte. Weitere Spielsystemintegrationen können durch Module und Spielsysteme angeboten werden. Eine andere Spielsystemintegration als die generische auszuwählen kann eine bessere Inegration in das verwendete Spielsystem bieten. Die Optionen unterhalb sind davon abhäng, welche Spielsystemintegration hier ausgewählt wurde.",
|
||||
"hint": "Wähle aus, welche Spielsystemintegration für die farbliche Hervorhebung der Felder zuständig sein soll. Drag Ruler stellt eine generische Spielsystemintegration zur Verfügung, die grundlegende Funktionalität bietet und bei korrekter konfiguration mit allen Spielsystemen funktionieren sollte. Weitere Spielsystemintegrationen können durch Module und Spielsysteme angeboten werden. Eine andere Spielsystemintegration als die generische auszuwählen kann eine bessere Inegration in das verwendete Spielsystem bieten. Die Optionen unterhalb sind davon abhängig, welche Spielsystemintegration hier ausgewählt wurde.",
|
||||
"choices": {
|
||||
"module": "Modul {name}",
|
||||
"native": "Generisch",
|
||||
@@ -76,7 +76,7 @@
|
||||
},
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "Leertaste und Rechtsklick tauschen",
|
||||
"hint": "Die Funktionen der Leertaste und des Rechtsklicks sind während eine Spielfigur bewegt wird vertauscht. Wenn diese Option aktiviert wrid können mit Rechtsklick Wegpunkte gesetzt werden und mit der Leertaste werden sie wieder gelöscht."
|
||||
"hint": "Die Funktionen der Leertaste und des Rechtsklicks sind, während eine Spielfigur bewegt wird, vertauscht. Wenn diese Option aktiviert wird können mit Rechtsklick Wegpunkte gesetzt werden und mit der Leertaste werden sie wieder gelöscht."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -2,9 +2,9 @@
|
||||
"name": "drag-ruler",
|
||||
"title": "Drag Ruler",
|
||||
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
||||
"version": "1.6.4",
|
||||
"minimumCoreVersion" : "0.7.9",
|
||||
"compatibleCoreVersion" : "0.7.9",
|
||||
"version": "1.7.3",
|
||||
"minimumCoreVersion" : "0.8.5",
|
||||
"compatibleCoreVersion" : "0.8.5",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Manuel Vögele",
|
||||
@@ -49,7 +49,7 @@
|
||||
],
|
||||
"socket": true,
|
||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.6.4.zip",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.7.3.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",
|
||||
|
||||
@@ -14,6 +14,9 @@ function register(module, type, speedProvider) {
|
||||
providerInstance = new speedProvider(id)
|
||||
}
|
||||
else {
|
||||
console.warn(`Drag Ruler | The ${type} '${module.id}' uses the old, deprecated version of the Drag Ruler API. ` +
|
||||
"That old API will be removed in a future Drag Ruler version. " +
|
||||
`Please update the ${type} ${module.id} to stay compatible with future Drag Ruler versions.`);
|
||||
speedProvider.id = id
|
||||
speedProvider.usesRuler = () => true
|
||||
providerInstance = speedProvider
|
||||
|
||||
@@ -15,19 +15,22 @@ export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=
|
||||
}
|
||||
}
|
||||
|
||||
export function measureDistances(segments, token, shape, gridSpaces=true, options={}) {
|
||||
export function measureDistances(segments, entity, shape, options={}) {
|
||||
const isToken = entity instanceof Token;
|
||||
const opts = duplicate(options)
|
||||
opts.gridSpaces = gridSpaces;
|
||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
||||
if (!opts.gridSpaces)
|
||||
opts.gridSpaces = true;
|
||||
const terrainRulerAvailable = isToken && game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
|
||||
|
||||
if (terrainRulerAvailable) {
|
||||
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
|
||||
const previousSegments = segments.slice(0, firstNewSegmentIndex);
|
||||
const newSegments = segments.slice(firstNewSegmentIndex);
|
||||
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
|
||||
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
|
||||
opts.costFunction = (x, y) => getCostFromSpeedProvider(token, getAreaFromPositionAndShape({x, y}, shape), {x, y});
|
||||
opts.costFunction = (x, y) => getCostFromSpeedProvider(entity, getAreaFromPositionAndShape({x, y}, shape), {x, y});
|
||||
if (previousSegments.length > 0)
|
||||
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1];
|
||||
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
||||
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
|
||||
}
|
||||
else {
|
||||
@@ -83,4 +86,3 @@ export function checkDependencies() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+85
-50
@@ -1,26 +1,30 @@
|
||||
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
||||
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
||||
import {Line} from "./geometry.js";
|
||||
import {getColorForDistance} from "./main.js"
|
||||
import {trackRays} from "./movement_tracking.js"
|
||||
import {recalculate} from "./socket.js";
|
||||
import {applyTokenSizeOffset, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
||||
import {applyTokenSizeOffset, getSnapPointForEntity, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
||||
|
||||
// This is a modified version of Ruler.moveToken from foundry 0.7.9
|
||||
export async function moveTokens(draggedToken, selectedTokens) {
|
||||
export async function moveEntities(draggedEntity, selectedEntities) {
|
||||
let wasPaused = game.paused;
|
||||
if (wasPaused && !game.user.isGM) {
|
||||
ui.notifications.warn(game.i18n.localize("GAME.PausedWarning"));
|
||||
return false;
|
||||
}
|
||||
if (!this.visible || !this.destination) return false;
|
||||
if (!draggedToken) return;
|
||||
if (!draggedEntity) return;
|
||||
|
||||
// Wait until all scheduled measurements are done
|
||||
await this.deferredMeasurementPromise;
|
||||
|
||||
// Get the movement rays and check collision along each Ray
|
||||
// These rays are center-to-center for the purposes of collision checking
|
||||
const rays = this.constructor.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination);
|
||||
if (!game.user.isGM) {
|
||||
const hasCollision = selectedTokens.some(token => {
|
||||
const offset = calculateTokenOffset(token, draggedToken)
|
||||
if (!game.user.isGM && draggedEntity instanceof Token) {
|
||||
const hasCollision = selectedEntities.some(token => {
|
||||
const offset = calculateEntityOffset(token, draggedEntity);
|
||||
const offsetRays = rays.filter(ray => !ray.isPrevious).map(ray => applyOffsetToRay(ray, offset))
|
||||
return offsetRays.some(r => canvas.walls.checkCollision(r));
|
||||
})
|
||||
@@ -34,65 +38,61 @@ export async function moveTokens(draggedToken, selectedTokens) {
|
||||
// Execute the movement path.
|
||||
// Transform each center-to-center ray into a top-left to top-left ray using the prior token offsets.
|
||||
this._state = Ruler.STATES.MOVING;
|
||||
await animateTokens.call(this, selectedTokens, draggedToken, rays, wasPaused);
|
||||
await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused);
|
||||
|
||||
// Once all animations are complete we can clear the ruler
|
||||
if (this.draggedToken?.id === draggedToken.id)
|
||||
if (this.draggedEntity?.id === draggedEntity.id)
|
||||
this._endMeasurement();
|
||||
}
|
||||
|
||||
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
|
||||
async function animateTokens(tokens, draggedToken, draggedRays, wasPaused) {
|
||||
async function animateEntities(entities, draggedEntity, draggedRays, wasPaused) {
|
||||
const newRays = draggedRays.filter(r => !r.isPrevious);
|
||||
const tokenAnimationData = tokens.map(token => {
|
||||
const tokenOffset = calculateTokenOffset(token, draggedToken);
|
||||
const offsetRays = newRays.map(ray => applyOffsetToRay(ray, tokenOffset));
|
||||
const entityAnimationData = entities.map(entity => {
|
||||
const entityOffset = calculateEntityOffset(entity, draggedEntity);
|
||||
const offsetRays = newRays.map(ray => applyOffsetToRay(ray, entityOffset));
|
||||
|
||||
// Determine offset relative to the Token top-left.
|
||||
// This is important so we can position the token relative to the ruler origin for non-1x1 tokens.
|
||||
const firstWaypoint = this.waypoints.find(w => !w.isPrevious);
|
||||
const origin = [firstWaypoint.x + tokenOffset.x, firstWaypoint.y + tokenOffset.y];
|
||||
const origin = [firstWaypoint.x + entityOffset.x, firstWaypoint.y + entityOffset.y];
|
||||
let dx, dy;
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
dx = token.data.x - origin[0];
|
||||
dy = token.data.y - origin[1];
|
||||
dx = entity.data.x - origin[0];
|
||||
dy = entity.data.y - origin[1];
|
||||
}
|
||||
else {
|
||||
dx = token.data.x - origin[0];
|
||||
dy = token.data.y - origin[1];
|
||||
dx = entity.data.x - origin[0];
|
||||
dy = entity.data.y - origin[1];
|
||||
}
|
||||
|
||||
return {token, rays: offsetRays, dx, dy};
|
||||
return {entity, rays: offsetRays, dx, dy};
|
||||
});
|
||||
|
||||
for (const {token, rays} of tokenAnimationData) {
|
||||
token._noAnimate = true;
|
||||
}
|
||||
const animate = !game.keyboard.isDown("Alt");
|
||||
const startWaypoint = animate ? 0 : tokenAnimationData[0].rays.length - 1;
|
||||
for (let i = startWaypoint;i < tokenAnimationData[0].rays.length; i++) {
|
||||
const isToken = draggedEntity instanceof Token;
|
||||
const animate = isToken && !game.keyboard.isDown("Alt");
|
||||
const startWaypoint = animate ? 0 : entityAnimationData[0].rays.length - 1;
|
||||
for (let i = startWaypoint;i < entityAnimationData[0].rays.length; i++) {
|
||||
if (!wasPaused && game.paused) break;
|
||||
const tokenPaths = tokenAnimationData.map(({token, rays, dx, dy}) => {
|
||||
const entityPaths = entityAnimationData.map(({entity, rays, dx, dy}) => {
|
||||
const ray = rays[i];
|
||||
const dest = [ray.B.x, ray.B.y];
|
||||
const path = new Ray({x: token.x, y: token.y}, {x: dest[0] + dx, y: dest[1] + dy});
|
||||
return {token, path};
|
||||
const path = new Ray({x: entity.x, y: entity.y}, {x: dest[0] + dx, y: dest[1] + dy});
|
||||
return {entity, path};
|
||||
});
|
||||
const updates = tokenPaths.map(({token, path}) => {
|
||||
return {x: path.B.x, y: path.B.y, _id: token.id};
|
||||
const updates = entityPaths.map(({entity, path}) => {
|
||||
return {x: path.B.x, y: path.B.y, _id: entity.id};
|
||||
});
|
||||
await draggedToken.scene.updateEmbeddedEntity(draggedToken.constructor.embeddedName, updates, {animate});
|
||||
await draggedEntity.scene.updateEmbeddedDocuments(draggedEntity.constructor.embeddedName, updates, {animate});
|
||||
if (animate)
|
||||
await Promise.all(tokenPaths.map(({token, path}) => token.animateMovement(path)));
|
||||
await Promise.all(entityPaths.map(({entity, path}) => entity.animateMovement(path)));
|
||||
}
|
||||
for (const {token} of tokenAnimationData) {
|
||||
token._noAnimate = false;
|
||||
}
|
||||
trackRays(tokens, tokenAnimationData.map(({rays}) => rays)).then(() => recalculate(tokens));
|
||||
if (isToken)
|
||||
trackRays(entities, entityAnimationData.map(({rays}) => rays)).then(() => recalculate(entities));
|
||||
}
|
||||
|
||||
function calculateTokenOffset(tokenA, tokenB) {
|
||||
return {x: tokenA.data.x - tokenB.data.x, y: tokenA.data.y - tokenB.data.y}
|
||||
function calculateEntityOffset(entityA, entityB) {
|
||||
return {x: entityA.data.x - entityB.data.x, y: entityA.data.y - entityB.data.y};
|
||||
}
|
||||
|
||||
function applyOffsetToRay(ray, offset) {
|
||||
@@ -106,8 +106,6 @@ export function onMouseMove(event) {
|
||||
if (this._state === Ruler.STATES.MOVING) return;
|
||||
|
||||
// Extract event data
|
||||
const mt = event._measureTime || 0;
|
||||
const originalEvent = event.data.originalEvent;
|
||||
const destination = {x: event.data.destination.x + this.rulerOffset.x, y: event.data.destination.y + this.rulerOffset.y}
|
||||
|
||||
// Hide any existing Token HUD
|
||||
@@ -115,26 +113,45 @@ export function onMouseMove(event) {
|
||||
delete event.data.hudState;
|
||||
|
||||
// Draw measurement updates
|
||||
if (Date.now() - mt > 50) {
|
||||
scheduleMeasurement.call(this, destination, event);
|
||||
}
|
||||
|
||||
function scheduleMeasurement(destination, event) {
|
||||
const measurementInterval = 50;
|
||||
const mt = event._measureTime || 0;
|
||||
const originalEvent = event.data.originalEvent;
|
||||
if (Date.now() - mt > measurementInterval) {
|
||||
this.measure(destination, {snap: !originalEvent.shiftKey});
|
||||
event._measureTime = Date.now();
|
||||
this._state = Ruler.STATES.MEASURING;
|
||||
window.clearTimeout(this.deferredMeasurementTimeout);
|
||||
this.deferredMeasurementTimeout = undefined;
|
||||
this.deferredMeasurementResolve?.();
|
||||
}
|
||||
else {
|
||||
this.deferredMeasurementData = {destination, event};
|
||||
if (!this.deferredMeasurementTimeout) {
|
||||
this.deferredMeasurementPromise = new Promise((resolve, reject) => this.deferredMeasurementResolve = resolve);
|
||||
this.deferredMeasurementTimeout = window.setTimeout(() => scheduleMeasurement.call(this, this.deferredMeasurementData.destination, this.deferredMeasurementData.event), measurementInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a modified version of Ruler.measure form foundry 0.7.9
|
||||
export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
if (this.isDragRuler && !this.draggedToken.isVisible)
|
||||
const isToken = this.draggedEntity instanceof Token;
|
||||
if (isToken && !this.draggedEntity.isVisible)
|
||||
return []
|
||||
|
||||
if (snap)
|
||||
destination = getSnapPointForToken(destination.x, destination.y, this.draggedToken)
|
||||
if (snap) {
|
||||
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
||||
}
|
||||
|
||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
||||
const terrainRulerAvailable = isToken && game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
|
||||
|
||||
const waypoints = this.waypoints.concat([destination]);
|
||||
// Move the waypoints to the center of the grid if a size is used that measures from edge to edge
|
||||
const centeredWaypoints = applyTokenSizeOffset(waypoints, this.draggedToken)
|
||||
const centeredWaypoints = isToken ? applyTokenSizeOffset(waypoints, this.draggedEntity) : duplicate(waypoints);
|
||||
// Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly
|
||||
if (!terrainRulerAvailable)
|
||||
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y));
|
||||
@@ -156,6 +173,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
centeredRay.isPrevious = ray.isPrevious;
|
||||
ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces;
|
||||
centeredRay.dragRulerVisitedSpaces = ray.dragRulerVisitedSpaces;
|
||||
ray.dragRulerFinalState = origin.dragRulerFinalState;
|
||||
centeredRay.dragRulerFinalState = ray.dragRulerFinalState;
|
||||
if (ray.distance < 10) {
|
||||
if (label) label.visible = false;
|
||||
continue;
|
||||
@@ -165,10 +184,10 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
}
|
||||
|
||||
|
||||
const shape = getTokenShape(this.draggedToken)
|
||||
const shape = isToken ? getTokenShape(this.draggedEntity) : null;
|
||||
|
||||
// Compute measured distance
|
||||
const distances = measureDistances(centeredSegments, this.draggedToken, shape, gridSpaces);
|
||||
const distances = measureDistances(centeredSegments, this.draggedEntity, shape, {gridSpaces});
|
||||
|
||||
let totalDistance = 0;
|
||||
for (let [i, d] of distances.entries()) {
|
||||
@@ -204,16 +223,32 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
label.text = text;
|
||||
label.alpha = last ? 1.0 : 0.5;
|
||||
label.visible = true;
|
||||
let labelPosition = cs.ray.project((cs.ray.distance + 50) / cs.ray.distance);
|
||||
let labelPosition = {x: s.ray.x0, y: s.ray.y0};
|
||||
labelPosition.x -= label.width / 2;
|
||||
labelPosition.y -= label.height / 2;
|
||||
const rayLine = Line.fromPoints(s.ray.A, s.ray.B);
|
||||
const rayLabelXHitY = rayLine.calcY(labelPosition.x);
|
||||
let innerDistance;
|
||||
// If ray hits top or bottom side of label
|
||||
if (rayLine.isVertical || rayLabelXHitY < labelPosition.y || rayLabelXHitY > labelPosition.y + label.height)
|
||||
innerDistance = Math.abs((label.height / 2) / Math.sin(s.ray.angle));
|
||||
// If ray hits left or right side of label
|
||||
else
|
||||
innerDistance = Math.abs((label.width / 2) / Math.cos(s.ray.angle));
|
||||
labelPosition = s.ray.project((s.ray.distance + 50 + innerDistance) / s.ray.distance);
|
||||
labelPosition.x -= label.width / 2;
|
||||
labelPosition.y -= label.height / 2;
|
||||
label.position.set(labelPosition.x, labelPosition.y);
|
||||
}
|
||||
|
||||
// Highlight grid positions
|
||||
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
|
||||
if (terrainRulerAvailable)
|
||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
||||
else
|
||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw endpoints
|
||||
for (let p of waypoints) {
|
||||
@@ -245,7 +280,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
|
||||
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
|
||||
prior = [x1, y1];
|
||||
@@ -258,7 +293,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
|
||||
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export class Line {
|
||||
constructor(m, b) {
|
||||
this.m = m;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
static fromPoints(p1, p2) {
|
||||
// Bring line into y=mx+b form
|
||||
const m = (p1.y - p2.y) / (p1.x - p2.x);
|
||||
const b = p1.y - m * p1.x;
|
||||
return new Line(m, b);
|
||||
}
|
||||
|
||||
get isVertical() {
|
||||
return !isFinite(this.m);
|
||||
}
|
||||
|
||||
calcY(x) {
|
||||
return this.m * x + this.b;
|
||||
}
|
||||
}
|
||||
+45
-37
@@ -2,7 +2,7 @@
|
||||
|
||||
import {currentSpeedProvider, getMovedDistanceFromToken, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js"
|
||||
import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
||||
import {moveTokens, onMouseMove} from "./foundry_imports.js"
|
||||
import {moveEntities, onMouseMove} from "./foundry_imports.js"
|
||||
import {performMigrations} from "./migration.js"
|
||||
import {DragRulerRuler} from "./ruler.js";
|
||||
import {getMovementHistory, resetMovementHistory} from "./movement_tracking.js";
|
||||
@@ -13,7 +13,8 @@ import {SpeedProvider} from "./speed_provider.js"
|
||||
Hooks.once("init", () => {
|
||||
registerSettings()
|
||||
initApi()
|
||||
hookTokenDragHandlers()
|
||||
hookDragHandlers(Token);
|
||||
hookDragHandlers(MeasuredTemplate);
|
||||
hookKeyboardManagerFunctions()
|
||||
|
||||
Ruler = DragRulerRuler;
|
||||
@@ -36,10 +37,10 @@ Hooks.once("ready", () => {
|
||||
|
||||
Hooks.on("canvasReady", () => {
|
||||
canvas.controls.rulers.children.forEach(ruler => {
|
||||
ruler.draggedToken = null
|
||||
ruler.draggedEntity = null;
|
||||
Object.defineProperty(ruler, "isDragRuler", {
|
||||
get: function isDragRuler() {
|
||||
return Boolean(this.draggedToken) // If draggedToken is set this is a drag ruler
|
||||
return Boolean(this.draggedEntity); // If draggedEntity is set this is a drag ruler
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -49,34 +50,34 @@ Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
|
||||
const entry = {
|
||||
name: "drag-ruler.resetMovementHistory",
|
||||
icon: '<i class="fas fa-undo-alt"></i>',
|
||||
callback: li => resetMovementHistory(ui.combat.combat, li.data('combatant-id')),
|
||||
callback: li => resetMovementHistory(ui.combat.viewed, li.data('combatant-id')),
|
||||
};
|
||||
menu.splice(1, 0, entry);
|
||||
});
|
||||
|
||||
function hookTokenDragHandlers() {
|
||||
const originalDragLeftStartHandler = Token.prototype._onDragLeftStart
|
||||
Token.prototype._onDragLeftStart = function(event) {
|
||||
function hookDragHandlers(entityType) {
|
||||
const originalDragLeftStartHandler = entityType.prototype._onDragLeftStart
|
||||
entityType.prototype._onDragLeftStart = function(event) {
|
||||
originalDragLeftStartHandler.call(this, event)
|
||||
onTokenLeftDragStart.call(this, event)
|
||||
onEntityLeftDragStart.call(this, event)
|
||||
}
|
||||
|
||||
const originalDragLeftMoveHandler = Token.prototype._onDragLeftMove
|
||||
Token.prototype._onDragLeftMove = function (event) {
|
||||
const originalDragLeftMoveHandler = entityType.prototype._onDragLeftMove
|
||||
entityType.prototype._onDragLeftMove = function (event) {
|
||||
originalDragLeftMoveHandler.call(this, event)
|
||||
onTokenLeftDragMove.call(this, event)
|
||||
onEntityLeftDragMove.call(this, event)
|
||||
}
|
||||
|
||||
const originalDragLeftDropHandler = Token.prototype._onDragLeftDrop
|
||||
Token.prototype._onDragLeftDrop = function (event) {
|
||||
const eventHandled = onTokenDragLeftDrop.call(this, event)
|
||||
const originalDragLeftDropHandler = entityType.prototype._onDragLeftDrop
|
||||
entityType.prototype._onDragLeftDrop = function (event) {
|
||||
const eventHandled = onEntityDragLeftDrop.call(this, event)
|
||||
if (!eventHandled)
|
||||
originalDragLeftDropHandler.call(this, event)
|
||||
}
|
||||
|
||||
const originalDragLeftCancelHandler = Token.prototype._onDragLeftCancel
|
||||
Token.prototype._onDragLeftCancel = function (event) {
|
||||
const eventHandled = onTokenDragLeftCancel.call(this, event)
|
||||
const originalDragLeftCancelHandler = entityType.prototype._onDragLeftCancel
|
||||
entityType.prototype._onDragLeftCancel = function (event) {
|
||||
const eventHandled = onEntityDragLeftCancel.call(this, event)
|
||||
if (!eventHandled)
|
||||
originalDragLeftCancelHandler.call(this, event)
|
||||
}
|
||||
@@ -115,6 +116,8 @@ function onKeyShift(up) {
|
||||
const ruler = canvas.controls.ruler
|
||||
if (!ruler.isDragRuler)
|
||||
return false
|
||||
if (ruler._state !== Ruler.STATES.MEASURING)
|
||||
return false;
|
||||
|
||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens)
|
||||
const rulerOffset = ruler.rulerOffset
|
||||
@@ -122,42 +125,47 @@ function onKeyShift(up) {
|
||||
ruler.measure(measurePosition, {snap: up})
|
||||
}
|
||||
|
||||
function onTokenLeftDragStart(event) {
|
||||
if (!currentSpeedProvider.usesRuler(this))
|
||||
function onEntityLeftDragStart(event) {
|
||||
const isToken = this instanceof Token;
|
||||
if (isToken && !currentSpeedProvider.usesRuler(this))
|
||||
return
|
||||
const ruler = canvas.controls.ruler
|
||||
ruler.draggedToken = this
|
||||
let tokenCenter
|
||||
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
|
||||
tokenCenter = getHexSizeSupportTokenGridCenter(this)
|
||||
ruler.draggedEntity = this;
|
||||
let entityCenter;
|
||||
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
|
||||
entityCenter = getHexSizeSupportTokenGridCenter(this);
|
||||
else
|
||||
tokenCenter = this.center
|
||||
entityCenter = this.center;
|
||||
ruler.clear();
|
||||
ruler._state = Ruler.STATES.STARTING;
|
||||
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y}
|
||||
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
||||
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
|
||||
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
||||
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
|
||||
ruler.dragRulerAddWaypoint(tokenCenter, false);
|
||||
ruler.dragRulerAddWaypoint(entityCenter, false);
|
||||
}
|
||||
|
||||
function onTokenLeftDragMove(event) {
|
||||
function onEntityLeftDragMove(event) {
|
||||
const ruler = canvas.controls.ruler
|
||||
if (ruler.isDragRuler)
|
||||
onMouseMove.call(ruler, event)
|
||||
}
|
||||
|
||||
function onTokenDragLeftDrop(event) {
|
||||
function onEntityDragLeftDrop(event) {
|
||||
const ruler = canvas.controls.ruler
|
||||
if (!ruler.isDragRuler)
|
||||
return false
|
||||
onMouseMove.call(ruler, event);
|
||||
// When we're dragging a measured template no token will ever be selected,
|
||||
// resulting in only the dragged template to be moved as would be expected
|
||||
const selectedTokens = canvas.tokens.controlled
|
||||
// This can happen if the user presses ESC during drag (maybe there are other ways too)
|
||||
if (selectedTokens.length === 0)
|
||||
selectedTokens.push(ruler.draggedEntity);
|
||||
ruler._state = Ruler.STATES.MOVING
|
||||
moveTokens.call(ruler, ruler.draggedToken, selectedTokens)
|
||||
moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
|
||||
return true
|
||||
}
|
||||
|
||||
function onTokenDragLeftCancel(event) {
|
||||
function onEntityDragLeftCancel(event) {
|
||||
// This function is invoked by right clicking
|
||||
const ruler = canvas.controls.ruler
|
||||
if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING)
|
||||
@@ -178,18 +186,18 @@ function onTokenDragLeftCancel(event) {
|
||||
export function getColorForDistance(startDistance, subDistance=0) {
|
||||
if (!this.isDragRuler)
|
||||
return this.color
|
||||
if (!this.draggedToken.actor) {
|
||||
if (!this.draggedEntity.actor) {
|
||||
return this.color;
|
||||
}
|
||||
// Don't apply colors if the current user doesn't have at least observer permissions
|
||||
if (this.draggedToken.actor.permission < 2) {
|
||||
if (this.draggedEntity.actor.permission < 2) {
|
||||
// If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway
|
||||
if (!(this.draggedToken.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs")))
|
||||
if (!(this.draggedEntity.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs")))
|
||||
return this.color
|
||||
}
|
||||
const distance = startDistance + subDistance
|
||||
if (!this.dragRulerRanges)
|
||||
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedToken)
|
||||
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedEntity);
|
||||
const ranges = this.dragRulerRanges;
|
||||
if (ranges.length === 0)
|
||||
return this.color
|
||||
|
||||
+15
-15
@@ -4,19 +4,19 @@ import {getTokenShape, zip} from "./util.js";
|
||||
|
||||
function initTrackingFlag(combatant) {
|
||||
const initialFlag = {passedWaypoints: [], trackedRound: 0};
|
||||
let dragRulerFlag = combatant.flags?.dragRuler;
|
||||
let dragRulerFlag = combatant.data.flags.dragRuler;
|
||||
if (dragRulerFlag) {
|
||||
if (isNaN(dragRulerFlag.trackedRound)) {
|
||||
mergeObject(dragRulerFlag, initialFlag);
|
||||
}
|
||||
}
|
||||
else {
|
||||
combatant.flags.dragRuler = initialFlag;
|
||||
combatant.data.flags.dragRuler = initialFlag;
|
||||
}
|
||||
}
|
||||
|
||||
function getInitializedCombatant(token, combat) {
|
||||
const combatant = combat.getCombatantByToken(token.data._id);
|
||||
const combatant = combat.getCombatantByToken(token.id);
|
||||
if (!combatant)
|
||||
return undefined;
|
||||
initTrackingFlag(combatant);
|
||||
@@ -43,37 +43,37 @@ function calculateUpdate(combat, token, rays) {
|
||||
return;
|
||||
|
||||
// Check if we have entered a new round. If so, remove the currently stored path
|
||||
if (combat.data.round > combatant.flags.dragRuler.trackedRound) {
|
||||
combatant.flags.dragRuler.passedWaypoints = [];
|
||||
combatant.flags.dragRuler.trackedRound = combat.data.round;
|
||||
if (combat.data.round > combatant.data.flags.dragRuler.trackedRound) {
|
||||
combatant.data.flags.dragRuler.passedWaypoints = [];
|
||||
combatant.data.flags.dragRuler.trackedRound = combat.data.round;
|
||||
}
|
||||
|
||||
// Add the passed waypoints to the combatant
|
||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
||||
const dragRulerFlags = combatant.flags.dragRuler;
|
||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
|
||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
||||
const waypoints = dragRulerFlags.passedWaypoints;
|
||||
for (const ray of rays) {
|
||||
// Ignore rays that have the same start and end coordinates
|
||||
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
|
||||
if (terrainRulerAvailable) {
|
||||
measureDistances([{ray}], token, getTokenShape(token), true, {terrainRulerInitialState: dragRulerFlags.rulerState});
|
||||
measureDistances([{ray}], token, getTokenShape(token), {terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState});
|
||||
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
|
||||
dragRulerFlags.rulerState = ray.terrainRulerFinalState;
|
||||
ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
|
||||
}
|
||||
waypoints.push(ray.A);
|
||||
}
|
||||
}
|
||||
return {_id: combatant._id, dragRulerFlags};
|
||||
return {_id: combatant.id, dragRulerFlags};
|
||||
}
|
||||
|
||||
export function getMovementHistory(token) {
|
||||
const combat = game.combat;
|
||||
if (!combat)
|
||||
return [];
|
||||
const combatant = combat.getCombatantByToken(token.data._id);
|
||||
const combatant = combat.getCombatantByToken(token.id);
|
||||
if (!combatant)
|
||||
return [];
|
||||
const dragRulerFlags = combatant.flags.dragRuler;
|
||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
||||
if (!dragRulerFlags)
|
||||
return [];
|
||||
if (combat.data.round > dragRulerFlags.trackedRound)
|
||||
@@ -82,8 +82,8 @@ export function getMovementHistory(token) {
|
||||
}
|
||||
|
||||
export async function resetMovementHistory(combat, combatantId) {
|
||||
const combatant = combat.getCombatant(combatantId);
|
||||
const dragRulerFlags = combatant.flags.dragRuler;
|
||||
const combatant = combat.combatants.get(combatantId);
|
||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
||||
if (!dragRulerFlags)
|
||||
return;
|
||||
dragRulerFlags.passedWaypoints = null;
|
||||
|
||||
+13
-12
@@ -1,7 +1,7 @@
|
||||
import {measure} from "./foundry_imports.js"
|
||||
import {getMovementHistory} from "./movement_tracking.js";
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {getSnapPointForToken} from "./util.js";
|
||||
import {getSnapPointForEntity} from "./util.js";
|
||||
|
||||
export class DragRulerRuler extends Ruler {
|
||||
// Functions below are overridden versions of functions in Ruler
|
||||
@@ -33,18 +33,18 @@ export class DragRulerRuler extends Ruler {
|
||||
|
||||
toJSON() {
|
||||
const json = super.toJSON();
|
||||
if (this.draggedToken)
|
||||
json["draggedToken"] = this.draggedToken.data._id;
|
||||
if (this.draggedEntity)
|
||||
json["draggedEntity"] = this.draggedEntity.id;
|
||||
return json;
|
||||
}
|
||||
|
||||
update(data) {
|
||||
// Don't show a GMs drag ruler to non GM players
|
||||
if (data.draggedToken && this.user.isGM && !game.user.isGM && !game.settings.get(settingsKey, "showGMRulerToPlayers"))
|
||||
if (data.draggedEntity && this.user.isGM && !game.user.isGM && !game.settings.get(settingsKey, "showGMRulerToPlayers"))
|
||||
return;
|
||||
|
||||
if (data.draggedToken) {
|
||||
this.draggedToken = canvas.tokens.get(data.draggedToken);
|
||||
if (data.draggedEntity) {
|
||||
this.draggedEntity = canvas.tokens.get(data.draggedEntity);
|
||||
}
|
||||
super.update(data);
|
||||
}
|
||||
@@ -60,13 +60,14 @@ export class DragRulerRuler extends Ruler {
|
||||
|
||||
_endMeasurement() {
|
||||
super._endMeasurement();
|
||||
this.draggedToken = null;
|
||||
this.draggedEntity = null;
|
||||
}
|
||||
|
||||
// The functions below aren't present in the orignal Ruler class and are added by Drag Ruler
|
||||
dragRulerAddWaypoint(point, snap=true) {
|
||||
if (snap)
|
||||
point = getSnapPointForToken(point.x, point.y, this.draggedToken);
|
||||
if (snap) {
|
||||
point = getSnapPointForEntity(point.x, point.y, this.draggedEntity);
|
||||
}
|
||||
this.waypoints.push(new PIXI.Point(point.x, point.y));
|
||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||
}
|
||||
@@ -93,7 +94,7 @@ export class DragRulerRuler extends Ruler {
|
||||
game.user.broadcastActivity({ruler: this});
|
||||
}
|
||||
else {
|
||||
const token = this.draggedToken;
|
||||
const token = this.draggedEntity;
|
||||
this._endMeasurement();
|
||||
|
||||
// Deactivate the drag workflow in mouse
|
||||
@@ -109,12 +110,12 @@ export class DragRulerRuler extends Ruler {
|
||||
async dragRulerRecalculate(tokenIds) {
|
||||
if (this._state !== Ruler.STATES.MEASURING)
|
||||
return;
|
||||
if (tokenIds && !tokenIds.includes(this.draggedToken.id))
|
||||
if (tokenIds && !tokenIds.includes(this.draggedEntity.id))
|
||||
return;
|
||||
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
|
||||
this.dragRulerClearWaypoints();
|
||||
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
||||
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedToken));
|
||||
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedEntity));
|
||||
for (const waypoint of waypoints) {
|
||||
this.dragRulerAddWaypoint(waypoint, false);
|
||||
}
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ export function updateCombatantDragRulerFlags(combat, updates) {
|
||||
const combatId = combat.id;
|
||||
// TODO Check if canvas.tokens.get is still neccessary in future foundry versions
|
||||
return socket.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates)
|
||||
.then(() => currentSpeedProvider.onMovementHistoryUpdate(updates.map(update => canvas.tokens.get(combat.getCombatant(update._id).token._id))));
|
||||
.then(() => currentSpeedProvider.onMovementHistoryUpdate(updates.map(update => canvas.tokens.get(combat.combatants.get(update._id).token.id))));
|
||||
}
|
||||
|
||||
async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
||||
@@ -20,10 +20,10 @@ async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
||||
const combat = game.combats.get(combatId);
|
||||
const requestedUpdates = updates.length;
|
||||
updates = updates.filter(update => {
|
||||
const actor = combat.getCombatant(update._id).actor;
|
||||
const actor = combat.combatants.get(update._id).actor;
|
||||
if (!actor)
|
||||
return false;
|
||||
return actor.hasPerm(user, "OWNER");
|
||||
return actor.testUserPermission(user, "OWNER");
|
||||
});
|
||||
if (updates.length !== requestedUpdates) {
|
||||
console.warn(`Some of the movement history updates requested by user '${game.users.get(this.socketdata.userId).name}' were not performed because the user lacks owner permissions for those tokens`);
|
||||
@@ -31,7 +31,7 @@ async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
||||
updates = updates.map(update => {
|
||||
return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}};
|
||||
});
|
||||
await combat.updateEmbeddedEntity("Combatant", updates, {diff: false});
|
||||
await combat.updateEmbeddedDocuments("Combatant", updates, {diff: false});
|
||||
}
|
||||
|
||||
export function recalculate(tokens) {
|
||||
|
||||
@@ -9,6 +9,8 @@ export function getDefaultSpeedAttribute() {
|
||||
return "actor.data.data.mech.speed"
|
||||
case "pf1":
|
||||
return "actor.data.data.attributes.speed.land.total"
|
||||
case "sfrpg":
|
||||
return "actor.data.data.attributes.speed.value";
|
||||
case "shadowrun5e":
|
||||
return "actor.data.data.movement.walk.value";
|
||||
case "swade":
|
||||
@@ -25,6 +27,7 @@ export function getDefaultDashMultiplier() {
|
||||
case "dnd5e":
|
||||
case "lancer":
|
||||
case "pf1":
|
||||
case "sfrpg":
|
||||
case "shadowrun5e":
|
||||
return 2
|
||||
}
|
||||
|
||||
+31
@@ -39,6 +39,37 @@ export function getSnapPointForToken(x, y, token) {
|
||||
return new PIXI.Point(snappedX + canvas.grid.w / 2, snappedY + canvas.grid.h / 2)
|
||||
}
|
||||
|
||||
export function getSnapPointForMeasuredTemplate(x, y) {
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
return new PIXI.Point(x, y);
|
||||
}
|
||||
let subgridWidth, subgridHeight;
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.SQUARE) {
|
||||
subgridWidth = subgridHeight = canvas.dimensions.size / 2;
|
||||
}
|
||||
else {
|
||||
if (canvas.grid.grid.columns) {
|
||||
subgridWidth = canvas.grid.w / 4;
|
||||
subgridHeight = canvas.grid.h / 2;
|
||||
}
|
||||
else {
|
||||
subgridWidth = canvas.grid.w / 2;
|
||||
subgridHeight = canvas.grid.h / 4;
|
||||
}
|
||||
}
|
||||
const snappedX = Math.round(x / subgridWidth) * subgridWidth;
|
||||
const snappedY = Math.round(y / subgridHeight) * subgridHeight;
|
||||
return new PIXI.Point(snappedX, snappedY);
|
||||
}
|
||||
|
||||
export function getSnapPointForEntity(x, y, entity) {
|
||||
const isToken = entity instanceof Token;
|
||||
if (isToken)
|
||||
return getSnapPointForToken(x, y, entity);
|
||||
else
|
||||
return getSnapPointForMeasuredTemplate(x, y);
|
||||
}
|
||||
|
||||
export function highlightTokenShape(position, shape, color, alpha) {
|
||||
const layer = canvas.grid.highlightLayers[this.name];
|
||||
if ( !layer )
|
||||
|
||||
Reference in New Issue
Block a user