Add support for the Toggle Snap To Grid module (#97)
This commit is contained in:
+3
-3
@@ -103,12 +103,12 @@ export function getUnreachableColorFromSpeedProvider() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCostFromSpeedProvider(token, area) {
|
export function getCostFromSpeedProvider(token, area, options) {
|
||||||
try {
|
try {
|
||||||
if (currentSpeedProvider instanceof Function) {
|
if (currentSpeedProvider instanceof Function) {
|
||||||
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area);
|
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area, options);
|
||||||
}
|
}
|
||||||
return currentSpeedProvider.getCostForStep(token, area);
|
return currentSpeedProvider.getCostForStep(token, area, options);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@@ -16,24 +16,26 @@ export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function measureDistances(segments, entity, shape, options={}) {
|
export function measureDistances(segments, entity, shape, options={}) {
|
||||||
const isToken = entity instanceof Token;
|
|
||||||
const opts = duplicate(options)
|
const opts = duplicate(options)
|
||||||
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) {
|
if (opts.enableTerrainRuler) {
|
||||||
|
opts.gridSpaces = true;
|
||||||
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(entity, getAreaFromPositionAndShape({x, y}, shape), {x, y});
|
opts.costFunction = (x, y, costOptions={}) => { return getCostFromSpeedProvider(entity, getAreaFromPositionAndShape({x, y}, shape), costOptions); }
|
||||||
if (previousSegments.length > 0)
|
if (previousSegments.length > 0)
|
||||||
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
||||||
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
|
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// If another module wants to enable grid measurements but disable grid highlighting,
|
||||||
|
// manually set the *duplicate* option's gridSpaces value to true for the Foundry logic to work properly
|
||||||
|
if(!opts.ignoreGrid) {
|
||||||
|
opts.gridSpaces = true;
|
||||||
|
}
|
||||||
return canvas.grid.measureDistances(segments, opts);
|
return canvas.grid.measureDistances(segments, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-8
@@ -148,22 +148,30 @@ function scheduleMeasurement(destination, 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, options={}) {
|
||||||
const isToken = this.draggedEntity instanceof Token;
|
const isToken = this.draggedEntity instanceof Token;
|
||||||
if (isToken && !this.draggedEntity.isVisible)
|
if (isToken && !this.draggedEntity.isVisible)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if (snap) {
|
if (options.snap) {
|
||||||
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
const terrainRulerAvailable = isToken && game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
|
if(options.gridSpaces === undefined) {
|
||||||
|
options.gridSpaces = canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.ignoreGrid === undefined) {
|
||||||
|
options.ignoreGrid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.enableTerrainRuler = 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 = isToken ? applyTokenSizeOffset(waypoints, this.draggedEntity) : duplicate(waypoints);
|
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 (!options.enableTerrainRuler && !options.ignoreGrid)
|
||||||
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));
|
||||||
|
|
||||||
const r = this.ruler;
|
const r = this.ruler;
|
||||||
@@ -197,7 +205,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
const shape = isToken ? getTokenShape(this.draggedEntity) : null;
|
const shape = isToken ? getTokenShape(this.draggedEntity) : null;
|
||||||
|
|
||||||
// Compute measured distance
|
// Compute measured distance
|
||||||
const distances = measureDistances(centeredSegments, this.draggedEntity, shape, {gridSpaces});
|
const distances = measureDistances(centeredSegments, this.draggedEntity, shape, options);
|
||||||
|
|
||||||
let totalDistance = 0;
|
let totalDistance = 0;
|
||||||
for (let [i, d] of distances.entries()) {
|
for (let [i, d] of distances.entries()) {
|
||||||
@@ -216,7 +224,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
// Draw measured path
|
// Draw measured path
|
||||||
r.clear();
|
r.clear();
|
||||||
let rulerColor
|
let rulerColor
|
||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
if (!options.gridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
||||||
rulerColor = getColorForDistance.call(this, totalDistance)
|
rulerColor = getColorForDistance.call(this, totalDistance)
|
||||||
else
|
else
|
||||||
rulerColor = this.color
|
rulerColor = this.color
|
||||||
@@ -252,8 +260,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Highlight grid positions
|
// Highlight grid positions
|
||||||
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
|
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) {
|
||||||
if (terrainRulerAvailable)
|
if (options.enableTerrainRuler)
|
||||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
||||||
else
|
else
|
||||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
||||||
|
|||||||
+8
-5
@@ -9,6 +9,7 @@ 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 {recalculate} from "./socket.js";
|
||||||
import {SpeedProvider} from "./speed_provider.js"
|
import {SpeedProvider} from "./speed_provider.js"
|
||||||
|
import {setSnapParameterOnOptions} from "./util.js";
|
||||||
|
|
||||||
Hooks.once("init", () => {
|
Hooks.once("init", () => {
|
||||||
registerSettings()
|
registerSettings()
|
||||||
@@ -141,7 +142,7 @@ function onEntityLeftDragStart(event) {
|
|||||||
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
|
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
|
||||||
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
||||||
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
|
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
|
||||||
ruler.dragRulerAddWaypoint(entityCenter, false);
|
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEntityLeftDragMove(event) {
|
function onEntityLeftDragMove(event) {
|
||||||
@@ -171,13 +172,15 @@ function onEntityDragLeftCancel(event) {
|
|||||||
if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING)
|
if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING)
|
||||||
return false
|
return false
|
||||||
if (ruler._state === Ruler.STATES.MEASURING) {
|
if (ruler._state === Ruler.STATES.MEASURING) {
|
||||||
|
let options = {};
|
||||||
|
setSnapParameterOnOptions(this, event, options);
|
||||||
|
|
||||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
||||||
ruler.dragRulerDeleteWaypoint(event);
|
ruler.dragRulerDeleteWaypoint(event, options);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
const snap = !event.shiftKey
|
ruler.dragRulerAddWaypoint(ruler.destination, options);
|
||||||
ruler.dragRulerAddWaypoint(ruler.destination, snap);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
+18
-9
@@ -1,7 +1,7 @@
|
|||||||
import {measure} from "./foundry_imports.js"
|
import {measure} from "./foundry_imports.js"
|
||||||
import {getMovementHistory} from "./movement_tracking.js";
|
import {getMovementHistory} from "./movement_tracking.js";
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {getSnapPointForEntity} from "./util.js";
|
import {getSnapPointForEntity, setSnapParameterOnOptions} from "./util.js";
|
||||||
|
|
||||||
export class DragRulerRuler extends Ruler {
|
export class DragRulerRuler extends Ruler {
|
||||||
// Functions below are overridden versions of functions in Ruler
|
// Functions below are overridden versions of functions in Ruler
|
||||||
@@ -22,12 +22,15 @@ export class DragRulerRuler extends Ruler {
|
|||||||
// This function is invoked by left clicking
|
// This function is invoked by left clicking
|
||||||
if (!this.isDragRuler)
|
if (!this.isDragRuler)
|
||||||
return await super.moveToken(event);
|
return await super.moveToken(event);
|
||||||
|
|
||||||
|
let options = {};
|
||||||
|
setSnapParameterOnOptions(this, event, options);
|
||||||
|
|
||||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
||||||
const snap = !event.shiftKey;
|
this.dragRulerAddWaypoint(this.destination, options);
|
||||||
this.dragRulerAddWaypoint(this.destination, snap);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.dragRulerDeleteWaypoint();
|
this.dragRulerDeleteWaypoint(event, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ export class DragRulerRuler extends Ruler {
|
|||||||
else
|
else
|
||||||
this.draggedEntity = canvas.templates.get(data.draggedEntity);
|
this.draggedEntity = canvas.templates.get(data.draggedEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.update(data);
|
super.update(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +74,9 @@ export class DragRulerRuler extends Ruler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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, options={}) {
|
||||||
if (snap) {
|
options.snap = options.snap ?? true;
|
||||||
|
if (options.snap) {
|
||||||
point = getSnapPointForEntity(point.x, point.y, this.draggedEntity);
|
point = getSnapPointForEntity(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));
|
||||||
@@ -91,12 +96,16 @@ export class DragRulerRuler extends Ruler {
|
|||||||
this.labels.removeChildren().forEach(c => c.destroy());
|
this.labels.removeChildren().forEach(c => c.destroy());
|
||||||
}
|
}
|
||||||
|
|
||||||
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}) {
|
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) {
|
||||||
|
options.snap = options.snap ?? true;
|
||||||
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
||||||
const rulerOffset = this.rulerOffset;
|
const rulerOffset = this.rulerOffset;
|
||||||
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y});
|
|
||||||
|
// Options are not passed to _removeWaypoint in vanilla Foundry.
|
||||||
|
// Send them in case other modules have overriden that behavior and accept an options parameter (Toggle Snap to Grid)
|
||||||
|
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}, options);
|
||||||
game.user.broadcastActivity({ruler: this});
|
game.user.broadcastActivity({ruler: this});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -123,7 +132,7 @@ export class DragRulerRuler extends Ruler {
|
|||||||
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
||||||
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedEntity));
|
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedEntity));
|
||||||
for (const waypoint of waypoints) {
|
for (const waypoint of waypoints) {
|
||||||
this.dragRulerAddWaypoint(waypoint, false);
|
this.dragRulerAddWaypoint(waypoint, {snap: false});
|
||||||
}
|
}
|
||||||
this.measure(this.destination);
|
this.measure(this.destination);
|
||||||
game.user.broadcastActivity({ruler: this});
|
game.user.broadcastActivity({ruler: this});
|
||||||
|
|||||||
@@ -61,13 +61,18 @@ export class SpeedProvider {
|
|||||||
* The area indicates the whole area that the token will occupy (for tokens larger than 1x1) the array will more than one entry.
|
* The area indicates the whole area that the token will occupy (for tokens larger than 1x1) the array will more than one entry.
|
||||||
* The return value should be an integer indicating a multiplicator by that the cost of that step should be increased.
|
* The return value should be an integer indicating a multiplicator by that the cost of that step should be increased.
|
||||||
* (1 is regular cost, 2 costs double, 3 costs triple, ...)
|
* (1 is regular cost, 2 costs double, 3 costs triple, ...)
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* - options: An object used to configure TerrainLayer's cost calculation. Ex: If options.ignoreGrid is set to true, then Euclidean measurement can be forced on a gridded map.
|
||||||
|
*
|
||||||
* This function is only called if the TerrainLayer and TerrainRuler modules are enabled.
|
* This function is only called if the TerrainLayer and TerrainRuler modules are enabled.
|
||||||
*
|
*
|
||||||
* Implementing this method is optional and only needs to be done if you want to provide a custom cost function (for example to allow tokens to ignore difficult terrain)
|
* Implementing this method is optional and only needs to be done if you want to provide a custom cost function (for example to allow tokens to ignore difficult terrain)
|
||||||
*/
|
*/
|
||||||
getCostForStep(token, area) {
|
getCostForStep(token, area, options={}) {
|
||||||
// Lookup the cost for each square occupied by the token
|
// Lookup the cost for each square occupied by the token
|
||||||
const costs = area.map(space => terrainRuler.getCost(space.x, space.y, {token}));
|
options.token = token;
|
||||||
|
const costs = area.map(space => terrainRuler.getCost(space.x, space.y, options));
|
||||||
// Return the maximum of the costs
|
// Return the maximum of the costs
|
||||||
return costs.reduce((max, current) => Math.max(max, current))
|
return costs.reduce((max, current) => Math.max(max, current))
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -198,3 +198,15 @@ export function applyTokenSizeOffset(waypoints, token) {
|
|||||||
|
|
||||||
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y))
|
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSnapParameterOnOptions(sourceObject, event, options) {
|
||||||
|
// Allow outside modules to override snapping
|
||||||
|
if (sourceObject.snapOverride?.active) {
|
||||||
|
options.snapOverrideActive = true;
|
||||||
|
options.snap = sourceObject.snapOverride.snap;
|
||||||
|
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
options.snap = !event.shiftKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user