Compare commits

...

26 Commits

Author SHA1 Message Date
Manuel Vögele a7811d6894 Release v1.7.0 2021-05-18 15:28:19 +02:00
Manuel Vögele e637f8d28f Fix typo in changelog 2021-05-18 15:17:04 +02:00
Manuel Vögele acd2e74064 Implement measurement template support (resolves #13) 2021-05-18 14:11:24 +02:00
Manuel Vögele fd81833583 Rename Ruler.draggedToken to Ruler.draggedEntity in preparation of measurement template suppport 2021-05-18 13:03:42 +02:00
Manuel Vögele a413d44795 Don't re-measure when the shift key is being pressed while a token is following the currently active ruler 2021-05-18 13:03:04 +02:00
Manuel Vögele e63ee1988d Deprecate the old (prior to 1.3.0) Drag Ruler API 2021-05-18 12:57:55 +02:00
Manuel Vögele 4aab6eec95 Improve the positioning of labels aroud the ruler graphic 2021-05-18 12:57:51 +02:00
Manuel Vögele 38b7df0248 Don't set Token._noAnimate. It has no effect and will be removed in future foundry versions
https://gitlab.com/foundrynet/foundryvtt/-/issues/5094
2021-05-18 07:23:56 +02:00
Manuel Vögele bbd30be80e Add Wasteland Ventures to the list of game systems with Drag Ruler integration 2021-05-16 18:47:59 +02:00
Manuel Vögele 78c96f9059 Merge branch 'master' into develop 2021-05-16 18:43:59 +02:00
Manuel Vögele d1367aa82e Release v1.6.5 2021-05-15 20:05:50 +02:00
Manuel Vögele 9f70e5bc27 If not token is selected, use the dragged token as selected token (fixes #70) 2021-05-15 20:04:22 +02:00
Manuel Vögele d2876671cf Add default values for Starfinder to the Generic Speed Provider 2021-05-12 16:20:05 +02:00
Manuel Vögele 19abc9eedc Add ironclaw2e to list of game systems with Drag Ruler integration 2021-05-11 19:20:04 +02:00
Manuel Vögele 286aaf7ada Properly store terrain ruler's state in the movement history 2021-05-11 19:01:51 +02:00
Manuel Vögele 85e167af45 Allow difficult terrain measurements on girdless maps 2021-05-11 18:21:39 +02:00
Manuel Vögele 5f0849f12d Mention ba0012e in changelog 2021-05-11 01:47:15 +02:00
CarnVanBeck ba0012e875 spelling correction (#68) 2021-05-11 01:45:28 +02:00
Manuel Vögele 334ccbc0f1 Release v1.6.4 2021-05-10 09:13:32 +02:00
Manuel Vögele 1242035744 Catch all errors thrown by a Speed Provider's getCostForStep function 2021-05-10 09:11:29 +02:00
Manuel Vögele 49542a68e5 Simplify foundry fixes 2021-05-06 09:00:50 +02:00
Chris Sharp 27a6235cc1 Mention WWII:OWB Operation WhiteBox system support in the readme 2021-05-05 23:40:40 +02:00
Manuel Vögele c26b39c984 Release v1.6.3 2021-05-05 16:01:52 +02:00
Manuel Vögele e8ab77a62e Update active rulers when the movement history changes 2021-05-05 16:01:13 +02:00
Manuel Vögele 7bed5abd0a Add default settings for shadowrun 5e 2021-05-03 19:39:22 +02:00
Manuel Vögele 4a96348659 Add "Tagmar RPG" and "Shadow of the Demon Lord" to the List of game systems with Drag Ruler integration 2021-05-03 16:00:34 +02:00
15 changed files with 286 additions and 119 deletions
+40
View File
@@ -1,3 +1,43 @@
## 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.6.3
### Bugfixes
- If the movement history for a token is being updated (for example by a history reset by the gm) while a player is currently measuring a distance for that token the history change is now being reflected in the active measurement.
### Compatibility
- Drag Ruler's Generic SpeedProvider is now aware of good default values for the Savage Worlds Adventure Edition game system
## 1.6.2 ## 1.6.2
### Bugfixes ### Bugfixes
- The reset movement history button now resets the movement history for all players, not just for the GM - The reset movement history button now resets the movement history for all players, not just for the GM
+5
View File
@@ -34,9 +34,14 @@ Drag Ruler will work with all Foundry VTT game systems. However some game system
The game systems that offer Drag Ruler integration are: The game systems that offer Drag Ruler integration are:
- Cypher System (starting with version 1.13.0) - Cypher System (starting with version 1.13.0)
- Ironclaw Second Edition (starting with version 0.2.2)
- Pathfinder 1 (starting with version 0.77.3) - Pathfinder 1 (starting with version 0.77.3)
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/)) - Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
- Tagmar RPG (starting with version 1.1.4)
- Tormenta20 (starting with version 1.1.37) - 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)
## Translations ## Translations
+5 -5
View File
@@ -4,7 +4,7 @@
"ok": "OK", "ok": "OK",
"terrain-ruler": { "terrain-ruler": {
"title": "Wie man schwieriges Gelände mit Drag Ruler misst", "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" "neverShowAgain": "Zeige dies nie wieder an"
}, },
"socketlib": { "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." "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": { "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." "hint": "Wenn diese Option deaktiviert ist wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt."
}, },
"speedProviderSettings": { "speedProviderSettings": {
@@ -53,7 +53,7 @@
}, },
"activeProvider": { "activeProvider": {
"name": "Verwendete Spielsystemintegration", "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", "noSettings": "Diese Spielsystemintegration hat keine Einstellmöglichkeiten",
"color": { "color": {
@@ -66,7 +66,7 @@
}, },
"speedProvider": { "speedProvider": {
"name": "Verwendete Spielsystemintegration", "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": { "choices": {
"module": "Modul {name}", "module": "Modul {name}",
"native": "Generisch", "native": "Generisch",
@@ -76,7 +76,7 @@
}, },
"swapSpacebarRightClick": { "swapSpacebarRightClick": {
"name": "Leertaste und Rechtsklick tauschen", "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."
} }
} }
} }
+2 -2
View File
@@ -2,7 +2,7 @@
"name": "drag-ruler", "name": "drag-ruler",
"title": "Drag Ruler", "title": "Drag Ruler",
"description": "When dragging a token displays a ruler showing how far you've moved that token.", "description": "When dragging a token displays a ruler showing how far you've moved that token.",
"version": "1.6.2", "version": "1.7.0",
"minimumCoreVersion" : "0.7.9", "minimumCoreVersion" : "0.7.9",
"compatibleCoreVersion" : "0.7.9", "compatibleCoreVersion" : "0.7.9",
"authors": [ "authors": [
@@ -49,7 +49,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.6.2.zip", "download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.7.0.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",
+12 -3
View File
@@ -14,6 +14,9 @@ function register(module, type, speedProvider) {
providerInstance = new speedProvider(id) providerInstance = new speedProvider(id)
} }
else { 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.id = id
speedProvider.usesRuler = () => true speedProvider.usesRuler = () => true
providerInstance = speedProvider providerInstance = speedProvider
@@ -101,10 +104,16 @@ export function getUnreachableColorFromSpeedProvider() {
} }
export function getCostFromSpeedProvider(token, area) { export function getCostFromSpeedProvider(token, area) {
if (currentSpeedProvider instanceof Function) { try {
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area); if (currentSpeedProvider instanceof Function) {
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area);
}
return currentSpeedProvider.getCostForStep(token, area);
}
catch (e) {
console.error(e);
return 1;
} }
return currentSpeedProvider.getCostForStep(token, area);
} }
export function getMovedDistanceFromToken(token) { export function getMovedDistanceFromToken(token) {
+8 -6
View File
@@ -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) const opts = duplicate(options)
opts.gridSpaces = gridSpaces; if (!opts.gridSpaces)
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS; 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) { if (terrainRulerAvailable) {
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces); const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
const previousSegments = segments.slice(0, firstNewSegmentIndex); const previousSegments = segments.slice(0, firstNewSegmentIndex);
const newSegments = segments.slice(firstNewSegmentIndex); const newSegments = segments.slice(firstNewSegmentIndex);
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance); const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces)); 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) 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)); return distances.concat(terrainRuler.measureDistances(newSegments, opts));
} }
else { else {
@@ -83,4 +86,3 @@ export function checkDependencies() {
} }
} }
} }
+2 -5
View File
@@ -2,13 +2,10 @@
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705 // https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
export function getPixelsFromGridPosition(xGrid, yGrid) { export function getPixelsFromGridPosition(xGrid, yGrid) {
if (canvas.grid.isHex) { if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid) return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid)
} }
const [x, y] = canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid) return canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid)
if (canvas.grid.type === CONST.GRID_TYPES.SQUARE)
return [y, x]
return [x, y]
} }
// 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
+72 -51
View File
@@ -1,25 +1,27 @@
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js"; import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
import {getGridPositionFromPixels} from "./foundry_fixes.js"; import {getGridPositionFromPixels} from "./foundry_fixes.js";
import {Line} from "./geometry.js";
import {getColorForDistance} from "./main.js" import {getColorForDistance} from "./main.js"
import {trackRays} from "./movement_tracking.js" import {trackRays} from "./movement_tracking.js"
import {applyTokenSizeOffset, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js"; import {recalculate} from "./socket.js";
import {applyTokenSizeOffset, getSnapPointForMeasuredTemplate, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
// This is a modified version of Ruler.moveToken from foundry 0.7.9 // This is a modified version of Ruler.moveToken from foundry 0.7.9
export async function moveTokens(draggedToken, selectedTokens) { export async function moveEntities(draggedEntity, selectedEntities) {
let wasPaused = game.paused; let wasPaused = game.paused;
if (wasPaused && !game.user.isGM) { if (wasPaused && !game.user.isGM) {
ui.notifications.warn(game.i18n.localize("GAME.PausedWarning")); ui.notifications.warn(game.i18n.localize("GAME.PausedWarning"));
return false; return false;
} }
if (!this.visible || !this.destination) return false; if (!this.visible || !this.destination) return false;
if (!draggedToken) return; if (!draggedEntity) return;
// Get the movement rays and check collision along each Ray // Get the movement rays and check collision along each Ray
// These rays are center-to-center for the purposes of collision checking // These rays are center-to-center for the purposes of collision checking
const rays = this.constructor.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination); const rays = this.constructor.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination);
if (!game.user.isGM) { if (!game.user.isGM && draggedEntity instanceof Token) {
const hasCollision = selectedTokens.some(token => { const hasCollision = selectedEntities.some(token => {
const offset = calculateTokenOffset(token, draggedToken) const offset = calculateTokenOffset(token, draggedEntity);
const offsetRays = rays.filter(ray => !ray.isPrevious).map(ray => applyOffsetToRay(ray, offset)) const offsetRays = rays.filter(ray => !ray.isPrevious).map(ray => applyOffsetToRay(ray, offset))
return offsetRays.some(r => canvas.walls.checkCollision(r)); return offsetRays.some(r => canvas.walls.checkCollision(r));
}) })
@@ -33,65 +35,61 @@ export async function moveTokens(draggedToken, selectedTokens) {
// Execute the movement path. // Execute the movement path.
// Transform each center-to-center ray into a top-left to top-left ray using the prior token offsets. // Transform each center-to-center ray into a top-left to top-left ray using the prior token offsets.
this._state = Ruler.STATES.MOVING; 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 // 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._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
async function animateTokens(tokens, draggedToken, draggedRays, wasPaused) { async function animateEntities(entities, draggedEntity, draggedRays, wasPaused) {
const newRays = draggedRays.filter(r => !r.isPrevious); const newRays = draggedRays.filter(r => !r.isPrevious);
const tokenAnimationData = tokens.map(token => { const entityAnimationData = entities.map(entity => {
const tokenOffset = calculateTokenOffset(token, draggedToken); const entityOffset = calculateEntityOffset(entity, draggedEntity);
const offsetRays = newRays.map(ray => applyOffsetToRay(ray, tokenOffset)); const offsetRays = newRays.map(ray => applyOffsetToRay(ray, entityOffset));
// Determine offset relative to the Token top-left. // 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. // 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 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; let dx, dy;
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
dx = token.data.x - origin[0]; dx = entity.data.x - origin[0];
dy = token.data.y - origin[1]; dy = entity.data.y - origin[1];
} }
else { else {
dx = token.data.x - origin[0]; dx = entity.data.x - origin[0];
dy = token.data.y - origin[1]; dy = entity.data.y - origin[1];
} }
return {token, rays: offsetRays, dx, dy}; return {entity, rays: offsetRays, dx, dy};
}); });
for (const {token, rays} of tokenAnimationData) { const isToken = draggedEntity instanceof Token;
token._noAnimate = true; const animate = isToken && !game.keyboard.isDown("Alt");
} const startWaypoint = animate ? 0 : entityAnimationData[0].rays.length - 1;
const animate = !game.keyboard.isDown("Alt"); for (let i = startWaypoint;i < entityAnimationData[0].rays.length; i++) {
const startWaypoint = animate ? 0 : tokenAnimationData[0].rays.length - 1;
for (let i = startWaypoint;i < tokenAnimationData[0].rays.length; i++) {
if (!wasPaused && game.paused) break; 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 ray = rays[i];
const dest = [ray.B.x, ray.B.y]; 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}); const path = new Ray({x: entity.x, y: entity.y}, {x: dest[0] + dx, y: dest[1] + dy});
return {token, path}; return {entity, path};
}); });
const updates = tokenPaths.map(({token, path}) => { const updates = entityPaths.map(({entity, path}) => {
return {x: path.B.x, y: path.B.y, _id: token.id}; return {x: path.B.x, y: path.B.y, _id: entity.id};
}); });
await draggedToken.scene.updateEmbeddedEntity(draggedToken.constructor.embeddedName, updates, {animate}); await draggedEntity.scene.updateEmbeddedEntity(draggedEntity.constructor.embeddedName, updates, {animate});
if (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) { if (isToken)
token._noAnimate = false; trackRays(entities, entityAnimationData.map(({rays}) => rays)).then(() => recalculate(entities));
}
trackRays(tokenAnimationData.map(({token}) => token), tokenAnimationData.map(({rays}) => rays));
} }
function calculateTokenOffset(tokenA, tokenB) { function calculateEntityOffset(entityA, entityB) {
return {x: tokenA.data.x - tokenB.data.x, y: tokenA.data.y - tokenB.data.y} return {x: entityA.data.x - entityB.data.x, y: entityA.data.y - entityB.data.y};
} }
function applyOffsetToRay(ray, offset) { function applyOffsetToRay(ray, offset) {
@@ -123,17 +121,22 @@ export function onMouseMove(event) {
// This is a modified version of Ruler.measure form foundry 0.7.9 // This is a modified version of Ruler.measure form foundry 0.7.9
export function measure(destination, {gridSpaces=true, snap=false} = {}) { 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 [] return []
if (snap) if (snap) {
destination = getSnapPointForToken(destination.x, destination.y, this.draggedToken) if (isToken)
destination = getSnapPointForToken(destination.x, destination.y, this.draggedEntity);
else
destination = getSnapPointForMeasuredTemplate(destination.x, destination.y);
}
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]); 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 // 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 // Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly
if (!terrainRulerAvailable) if (!terrainRulerAvailable)
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y)); centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y));
@@ -155,6 +158,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
centeredRay.isPrevious = ray.isPrevious; centeredRay.isPrevious = ray.isPrevious;
ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces; ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces;
centeredRay.dragRulerVisitedSpaces = ray.dragRulerVisitedSpaces; centeredRay.dragRulerVisitedSpaces = ray.dragRulerVisitedSpaces;
ray.dragRulerFinalState = origin.dragRulerFinalState;
centeredRay.dragRulerFinalState = ray.dragRulerFinalState;
if (ray.distance < 10) { if (ray.distance < 10) {
if (label) label.visible = false; if (label) label.visible = false;
continue; continue;
@@ -164,10 +169,10 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
} }
const shape = getTokenShape(this.draggedToken) const shape = isToken ? getTokenShape(this.draggedEntity) : null;
// Compute measured distance // Compute measured distance
const distances = measureDistances(centeredSegments, this.draggedToken, shape, gridSpaces); const distances = measureDistances(centeredSegments, this.draggedEntity, shape, {gridSpaces});
let totalDistance = 0; let totalDistance = 0;
for (let [i, d] of distances.entries()) { for (let [i, d] of distances.entries()) {
@@ -203,15 +208,31 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
label.text = text; label.text = text;
label.alpha = last ? 1.0 : 0.5; label.alpha = last ? 1.0 : 0.5;
label.visible = true; 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); label.position.set(labelPosition.x, labelPosition.y);
} }
// Highlight grid positions // Highlight grid positions
if (terrainRulerAvailable) if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier) if (terrainRulerAvailable)
else highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier); else
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
}
} }
// Draw endpoints // Draw endpoints
@@ -244,7 +265,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1); 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 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 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); const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
prior = [x1, y1]; prior = [x1, y1];
@@ -257,7 +278,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h); 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 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 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); 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);
} }
+21
View File
@@ -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;
}
}
+46 -35
View File
@@ -2,17 +2,19 @@
import {currentSpeedProvider, getMovedDistanceFromToken, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js" import {currentSpeedProvider, getMovedDistanceFromToken, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js"
import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.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 {performMigrations} from "./migration.js"
import {DragRulerRuler} from "./ruler.js"; import {DragRulerRuler} from "./ruler.js";
import {getMovementHistory, resetMovementHistory} from "./movement_tracking.js"; import {getMovementHistory, resetMovementHistory} from "./movement_tracking.js";
import {registerSettings, settingsKey} from "./settings.js" import {registerSettings, settingsKey} from "./settings.js"
import {recalculate} from "./socket.js";
import {SpeedProvider} from "./speed_provider.js" import {SpeedProvider} from "./speed_provider.js"
Hooks.once("init", () => { Hooks.once("init", () => {
registerSettings() registerSettings()
initApi() initApi()
hookTokenDragHandlers() hookDragHandlers(Token);
hookDragHandlers(MeasuredTemplate);
hookKeyboardManagerFunctions() hookKeyboardManagerFunctions()
Ruler = DragRulerRuler; Ruler = DragRulerRuler;
@@ -22,6 +24,7 @@ Hooks.once("init", () => {
getMovedDistanceFromToken, getMovedDistanceFromToken,
registerModule, registerModule,
registerSystem, registerSystem,
recalculate,
resetMovementHistory, resetMovementHistory,
} }
}) })
@@ -34,10 +37,10 @@ Hooks.once("ready", () => {
Hooks.on("canvasReady", () => { Hooks.on("canvasReady", () => {
canvas.controls.rulers.children.forEach(ruler => { canvas.controls.rulers.children.forEach(ruler => {
ruler.draggedToken = null ruler.draggedEntity = null;
Object.defineProperty(ruler, "isDragRuler", { Object.defineProperty(ruler, "isDragRuler", {
get: function 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
} }
}) })
}) })
@@ -52,29 +55,29 @@ Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
menu.splice(1, 0, entry); menu.splice(1, 0, entry);
}); });
function hookTokenDragHandlers() { function hookDragHandlers(entityType) {
const originalDragLeftStartHandler = Token.prototype._onDragLeftStart const originalDragLeftStartHandler = entityType.prototype._onDragLeftStart
Token.prototype._onDragLeftStart = function(event) { entityType.prototype._onDragLeftStart = function(event) {
originalDragLeftStartHandler.call(this, event) originalDragLeftStartHandler.call(this, event)
onTokenLeftDragStart.call(this, event) onEntityLeftDragStart.call(this, event)
} }
const originalDragLeftMoveHandler = Token.prototype._onDragLeftMove const originalDragLeftMoveHandler = entityType.prototype._onDragLeftMove
Token.prototype._onDragLeftMove = function (event) { entityType.prototype._onDragLeftMove = function (event) {
originalDragLeftMoveHandler.call(this, event) originalDragLeftMoveHandler.call(this, event)
onTokenLeftDragMove.call(this, event) onEntityLeftDragMove.call(this, event)
} }
const originalDragLeftDropHandler = Token.prototype._onDragLeftDrop const originalDragLeftDropHandler = entityType.prototype._onDragLeftDrop
Token.prototype._onDragLeftDrop = function (event) { entityType.prototype._onDragLeftDrop = function (event) {
const eventHandled = onTokenDragLeftDrop.call(this, event) const eventHandled = onEntityDragLeftDrop.call(this, event)
if (!eventHandled) if (!eventHandled)
originalDragLeftDropHandler.call(this, event) originalDragLeftDropHandler.call(this, event)
} }
const originalDragLeftCancelHandler = Token.prototype._onDragLeftCancel const originalDragLeftCancelHandler = entityType.prototype._onDragLeftCancel
Token.prototype._onDragLeftCancel = function (event) { entityType.prototype._onDragLeftCancel = function (event) {
const eventHandled = onTokenDragLeftCancel.call(this, event) const eventHandled = onEntityDragLeftCancel.call(this, event)
if (!eventHandled) if (!eventHandled)
originalDragLeftCancelHandler.call(this, event) originalDragLeftCancelHandler.call(this, event)
} }
@@ -113,6 +116,8 @@ function onKeyShift(up) {
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (!ruler.isDragRuler) if (!ruler.isDragRuler)
return false return false
if (ruler._state !== Ruler.STATES.MEASURING)
return false;
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens) const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens)
const rulerOffset = ruler.rulerOffset const rulerOffset = ruler.rulerOffset
@@ -120,42 +125,48 @@ function onKeyShift(up) {
ruler.measure(measurePosition, {snap: up}) ruler.measure(measurePosition, {snap: up})
} }
function onTokenLeftDragStart(event) { function onEntityLeftDragStart(event) {
if (!currentSpeedProvider.usesRuler(this)) const isToken = this instanceof Token;
if (isToken && !currentSpeedProvider.usesRuler(this))
return return
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
ruler.draggedToken = this ruler.draggedEntity = this;
let tokenCenter let entityCenter;
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this)) if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
tokenCenter = getHexSizeSupportTokenGridCenter(this) entityCenter = getHexSizeSupportTokenGridCenter(this);
else else
tokenCenter = this.center entityCenter = this.center;
ruler.clear(); ruler.clear();
ruler._state = Ruler.STATES.STARTING; ruler._state = Ruler.STATES.STARTING;
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y} ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
if (game.settings.get(settingsKey, "enableMovementHistory")) if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
ruler.dragRulerAddWaypointHistory(getMovementHistory(this)); ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
ruler.dragRulerAddWaypoint(tokenCenter, false); ruler.dragRulerAddWaypoint(entityCenter, false);
} }
function onTokenLeftDragMove(event) { function onEntityLeftDragMove(event) {
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (ruler.isDragRuler) if (ruler.isDragRuler)
onMouseMove.call(ruler, event) onMouseMove.call(ruler, event)
} }
function onTokenDragLeftDrop(event) { function onEntityDragLeftDrop(event) {
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (!ruler.isDragRuler) if (!ruler.isDragRuler)
return false return false
onMouseMove.call(ruler, event); 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 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 ruler._state = Ruler.STATES.MOVING
moveTokens.call(ruler, ruler.draggedToken, selectedTokens) moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
return true return true
} }
function onTokenDragLeftCancel(event) { function onEntityDragLeftCancel(event) {
// This function is invoked by right clicking // This function is invoked by right clicking
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING) if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING)
@@ -176,18 +187,18 @@ function onTokenDragLeftCancel(event) {
export function getColorForDistance(startDistance, subDistance=0) { export function getColorForDistance(startDistance, subDistance=0) {
if (!this.isDragRuler) if (!this.isDragRuler)
return this.color return this.color
if (!this.draggedToken.actor) { if (!this.draggedEntity.actor) {
return this.color; return this.color;
} }
// Don't apply colors if the current user doesn't have at least observer permissions // 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 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 return this.color
} }
const distance = startDistance + subDistance const distance = startDistance + subDistance
if (!this.dragRulerRanges) if (!this.dragRulerRanges)
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedToken) this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedEntity);
const ranges = this.dragRulerRanges; const ranges = this.dragRulerRanges;
if (ranges.length === 0) if (ranges.length === 0)
return this.color return this.color
+5 -4
View File
@@ -1,5 +1,5 @@
import {measureDistances} from "./compatibility.js"; import {measureDistances} from "./compatibility.js";
import {updateCombatantDragRulerFlags} from "./socket.js"; import {recalculate, updateCombatantDragRulerFlags} from "./socket.js";
import {getTokenShape, zip} from "./util.js"; import {getTokenShape, zip} from "./util.js";
function initTrackingFlag(combatant) { function initTrackingFlag(combatant) {
@@ -49,16 +49,16 @@ function calculateUpdate(combat, token, rays) {
} }
// Add the passed waypoints to the combatant // Add the passed waypoints to the combatant
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS; const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
const dragRulerFlags = combatant.flags.dragRuler; const dragRulerFlags = combatant.flags.dragRuler;
const waypoints = dragRulerFlags.passedWaypoints; const waypoints = dragRulerFlags.passedWaypoints;
for (const ray of rays) { for (const ray of rays) {
// Ignore rays that have the same start and end coordinates // Ignore rays that have the same start and end coordinates
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) { if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
if (terrainRulerAvailable) { 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; ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
dragRulerFlags.rulerState = ray.terrainRulerFinalState; ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
} }
waypoints.push(ray.A); waypoints.push(ray.A);
} }
@@ -90,4 +90,5 @@ export async function resetMovementHistory(combat, combatantId) {
dragRulerFlags.trackedRound = null; dragRulerFlags.trackedRound = null;
dragRulerFlags.rulerState = null; dragRulerFlags.rulerState = null;
await updateCombatantDragRulerFlags(combat, [{_id: combatantId, dragRulerFlags}]); await updateCombatantDragRulerFlags(combat, [{_id: combatantId, dragRulerFlags}]);
recalculate();
} }
+30 -8
View File
@@ -1,4 +1,5 @@
import {measure} from "./foundry_imports.js" import {measure} from "./foundry_imports.js"
import {getMovementHistory} from "./movement_tracking.js";
import {settingsKey} from "./settings.js"; import {settingsKey} from "./settings.js";
import {getSnapPointForToken} from "./util.js"; import {getSnapPointForToken} from "./util.js";
@@ -32,18 +33,18 @@ export class DragRulerRuler extends Ruler {
toJSON() { toJSON() {
const json = super.toJSON(); const json = super.toJSON();
if (this.draggedToken) if (this.draggedEntity)
json["draggedToken"] = this.draggedToken.data._id; json["draggedEntity"] = this.draggedEntity.data._id;
return json; return json;
} }
update(data) { update(data) {
// Don't show a GMs drag ruler to non GM players // 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; return;
if (data.draggedToken) { if (data.draggedEntity) {
this.draggedToken = canvas.tokens.get(data.draggedToken); this.draggedEntity = canvas.tokens.get(data.draggedEntity);
} }
super.update(data); super.update(data);
} }
@@ -59,13 +60,13 @@ export class DragRulerRuler extends Ruler {
_endMeasurement() { _endMeasurement() {
super._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 // The functions below aren't present in the orignal Ruler class and are added by Drag Ruler
dragRulerAddWaypoint(point, snap=true) { dragRulerAddWaypoint(point, snap=true) {
if (snap) if (snap)
point = getSnapPointForToken(point.x, point.y, this.draggedToken); point = getSnapPointForToken(point.x, point.y, this.draggedEntity);
this.waypoints.push(new PIXI.Point(point.x, point.y)); this.waypoints.push(new PIXI.Point(point.x, point.y));
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
} }
@@ -78,6 +79,11 @@ export class DragRulerRuler extends Ruler {
} }
} }
dragRulerClearWaypoints() {
this.waypoints = [];
this.labels.removeChildren().forEach(c => c.destroy());
}
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}) { dragRulerDeleteWaypoint(event={preventDefault: () => {return}}) {
if (this.waypoints.filter(w => !w.isPrevious).length > 1) { if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
event.preventDefault(); event.preventDefault();
@@ -87,7 +93,7 @@ export class DragRulerRuler extends Ruler {
game.user.broadcastActivity({ruler: this}); game.user.broadcastActivity({ruler: this});
} }
else { else {
const token = this.draggedToken; const token = this.draggedEntity;
this._endMeasurement(); this._endMeasurement();
// Deactivate the drag workflow in mouse // Deactivate the drag workflow in mouse
@@ -100,6 +106,22 @@ export class DragRulerRuler extends Ruler {
} }
} }
async dragRulerRecalculate(tokenIds) {
if (this._state !== Ruler.STATES.MEASURING)
return;
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.draggedEntity));
for (const waypoint of waypoints) {
this.dragRulerAddWaypoint(waypoint, false);
}
this.measure(this.destination);
game.user.broadcastActivity({ruler: this});
}
static dragRulerGetRaysFromWaypoints(waypoints, destination) { static dragRulerGetRaysFromWaypoints(waypoints, destination) {
if ( destination ) if ( destination )
waypoints = waypoints.concat([destination]); waypoints = waypoints.concat([destination]);
+9
View File
@@ -5,6 +5,7 @@ let socket;
Hooks.once("socketlib.ready", () => { Hooks.once("socketlib.ready", () => {
socket = socketlib.registerModule("drag-ruler"); socket = socketlib.registerModule("drag-ruler");
socket.register("updateCombatantDragRulerFlags", _socketUpdateCombatantDragRulerFlags); socket.register("updateCombatantDragRulerFlags", _socketUpdateCombatantDragRulerFlags);
socket.register("recalculate", _socketRecalculate);
}); });
export function updateCombatantDragRulerFlags(combat, updates) { export function updateCombatantDragRulerFlags(combat, updates) {
@@ -32,3 +33,11 @@ async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
}); });
await combat.updateEmbeddedEntity("Combatant", updates, {diff: false}); await combat.updateEmbeddedEntity("Combatant", updates, {diff: false});
} }
export function recalculate(tokens) {
socket.executeForEveryone(_socketRecalculate, tokens ? tokens.map(token => token.id) : undefined);
}
function _socketRecalculate(tokenIds) {
return canvas.controls.ruler.dragRulerRecalculate(tokenIds);
}
+6
View File
@@ -9,6 +9,10 @@ export function getDefaultSpeedAttribute() {
return "actor.data.data.mech.speed" return "actor.data.data.mech.speed"
case "pf1": case "pf1":
return "actor.data.data.attributes.speed.land.total" 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": case "swade":
return "actor.data.data.stats.speed.value" return "actor.data.data.stats.speed.value"
} }
@@ -23,6 +27,8 @@ export function getDefaultDashMultiplier() {
case "dnd5e": case "dnd5e":
case "lancer": case "lancer":
case "pf1": case "pf1":
case "sfrpg":
case "shadowrun5e":
return 2 return 2
} }
return 0 return 0
+23
View File
@@ -39,6 +39,29 @@ export function getSnapPointForToken(x, y, token) {
return new PIXI.Point(snappedX + canvas.grid.w / 2, snappedY + canvas.grid.h / 2) 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 highlightTokenShape(position, shape, color, alpha) { export function highlightTokenShape(position, shape, color, alpha) {
const layer = canvas.grid.highlightLayers[this.name]; const layer = canvas.grid.highlightLayers[this.name];
if ( !layer ) if ( !layer )