diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..65056ce --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +printWidth: 100 +trailingComma: "all" +bracketSpacing: false +arrowParens: "avoid" diff --git a/js/api.js b/js/api.js index 7a371c4..84d8e65 100644 --- a/js/api.js +++ b/js/api.js @@ -1,105 +1,116 @@ import {measureDistances} from "./compatibility.js"; import {getMovementHistory} from "./movement_tracking.js"; -import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js" -import {settingsKey} from "./settings.js" +import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js"; +import {settingsKey} from "./settings.js"; import {getAreaFromPositionAndShape, getTokenShape} from "./util.js"; -export const availableSpeedProviders = {} -export let currentSpeedProvider = undefined +export const availableSpeedProviders = {}; +export let currentSpeedProvider = undefined; function register(module, type, speedProvider) { - const id = `${type}.${module.id}` - let providerInstance + const id = `${type}.${module.id}`; + let providerInstance; if (speedProvider.prototype instanceof SpeedProvider) { - providerInstance = new speedProvider(id) + 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; } - 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 - } - setupProvider(providerInstance) + setupProvider(providerInstance); } function setupProvider(speedProvider) { if (speedProvider instanceof SpeedProvider) { - const unreachableColor = {id: "unreachable", default: speedProvider.defaultUnreachableColor, name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name"} + const unreachableColor = { + id: "unreachable", + default: speedProvider.defaultUnreachableColor, + name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name", + }; for (const color of speedProvider.colors.concat([unreachableColor])) { game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.color.${color.id}`, { config: false, scope: "client", type: Number, default: color.default, - }) + }); } for (const setting of speedProvider.settings) { - setting.config = false - game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.setting.${setting.id}`, setting) + setting.config = false; + game.settings.register( + settingsKey, + `speedProviders.${speedProvider.id}.setting.${setting.id}`, + setting, + ); } } - availableSpeedProviders[speedProvider.id] = speedProvider - game.settings.settings.get("drag-ruler.speedProvider").default = getDefaultSpeedProvider() - updateSpeedProvider() + availableSpeedProviders[speedProvider.id] = speedProvider; + game.settings.settings.get("drag-ruler.speedProvider").default = getDefaultSpeedProvider(); + updateSpeedProvider(); } export function getDefaultSpeedProvider() { - const providerIds = Object.keys(availableSpeedProviders) + const providerIds = Object.keys(availableSpeedProviders); // Game systems take the highest precedence for the being the default - const gameSystem = providerIds.find(key => key.startsWith("system.")) - if (gameSystem) - return gameSystem + const gameSystem = providerIds.find(key => key.startsWith("system.")); + if (gameSystem) return gameSystem; // If no game system is registered modules are next up. // For lack of a method to select the best module we're just falling back to taking the next best module // settingKeys should always be sorted the same way so this should achive a stable default - const module = providerIds.find(key => key.startsWith("module.")) - if (module) - return module + const module = providerIds.find(key => key.startsWith("module.")); + if (module) return module; // If neither a game system or a module is found fall back to the native implementation - return providerIds[0] + return providerIds[0]; } export function updateSpeedProvider() { // If the configured provider is registered use that one. If not use the default provider - const configuredProvider = game.settings.get("drag-ruler", "speedProvider") - currentSpeedProvider = availableSpeedProviders[configuredProvider] ?? availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default] + const configuredProvider = game.settings.get("drag-ruler", "speedProvider"); + currentSpeedProvider = + availableSpeedProviders[configuredProvider] ?? + availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default]; } export function initApi() { - const genericSpeedProviderInstance = new GenericSpeedProvider("native") - setupProvider(genericSpeedProviderInstance) + const genericSpeedProviderInstance = new GenericSpeedProvider("native"); + setupProvider(genericSpeedProviderInstance); } export function getRangesFromSpeedProvider(token) { try { - if (currentSpeedProvider instanceof Function) - return currentSpeedProvider(token, 0x00FF00) - const ranges = currentSpeedProvider.getRanges(token) + if (currentSpeedProvider instanceof Function) return currentSpeedProvider(token, 0x00ff00); + const ranges = currentSpeedProvider.getRanges(token); for (const range of ranges) { - range.color = game.settings.get(settingsKey, `speedProviders.${currentSpeedProvider.id}.color.${range.color}`) + range.color = game.settings.get( + settingsKey, + `speedProviders.${currentSpeedProvider.id}.color.${range.color}`, + ); } - return ranges - } - catch (e) { - console.error(e) - return [] + return ranges; + } catch (e) { + console.error(e); + return []; } } export function getUnreachableColorFromSpeedProvider() { - if (currentSpeedProvider instanceof Function) - return 0xFF0000 + if (currentSpeedProvider instanceof Function) return 0xff0000; try { - return game.settings.get(settingsKey, `speedProviders.${currentSpeedProvider.id}.color.unreachable`) - } - catch (e) { - console.error(e) - return 0xFF0000 + return game.settings.get( + settingsKey, + `speedProviders.${currentSpeedProvider.id}.color.unreachable`, + ); + } catch (e) { + console.error(e); + return 0xff0000; } } @@ -109,77 +120,87 @@ export function getCostFromSpeedProvider(token, area, options) { return SpeedProvider.prototype.getCostForStep.call(undefined, token, area, options); } return currentSpeedProvider.getCostForStep(token, area, options); - } - catch (e) { + } catch (e) { console.error(e); return 1; } } -export function getColorForDistanceAndToken(distance, token, ranges=null) { +export function getColorForDistanceAndToken(distance, token, ranges = null) { if (!ranges) { ranges = getRangesFromSpeedProvider(token); } - if (ranges.length === 0) - return null; - const currentRange = ranges.reduce((minRange, currentRange) => { - if (distance <= currentRange.range && currentRange.range < minRange.range) - return currentRange; - return minRange; - }, {range: Infinity, color: getUnreachableColorFromSpeedProvider()}); + if (ranges.length === 0) return null; + const currentRange = ranges.reduce( + (minRange, currentRange) => { + if (distance <= currentRange.range && currentRange.range < minRange.range) + return currentRange; + return minRange; + }, + {range: Infinity, color: getUnreachableColorFromSpeedProvider()}, + ); return currentRange.color; } export function getMovedDistanceFromToken(token) { const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active; const history = getMovementHistory(token); - const segments = Ruler.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y}).map(ray => {return {ray}}); + const segments = Ruler.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y}).map( + ray => { + return {ray}; + }, + ); const shape = getTokenShape(token); - const distances = measureDistances(segments, token, shape, {enableTerrainRuler: terrainRulerAvailable}); + const distances = measureDistances(segments, token, shape, { + enableTerrainRuler: terrainRulerAvailable, + }); // Sum up the distances return distances.reduce((acc, val) => acc + val, 0); } export function buildCostFunction(token, shape) { - return (x, y, costOptions={}) => getCostFromSpeedProvider(token, getAreaFromPositionAndShape({x, y}, shape), costOptions); + return (x, y, costOptions = {}) => + getCostFromSpeedProvider(token, getAreaFromPositionAndShape({x, y}, shape), costOptions); } export function registerModule(moduleId, speedProvider) { // Check if a module with the given id exists and is currently enabled - const module = game.modules.get(moduleId) + const module = game.modules.get(moduleId); // If it doesn't the calling module did something wrong. Log a warning and ignore this module if (!module) { console.warn( `Drag Ruler | A module tried to register with the id "${moduleId}". However no active module with this id was found.` + - "This api registration call was ignored. " + - "If you are the author of that module please check that the id passed to `registerModule` matches the id in your manifest exactly." + - "If this call was made form a game system instead of a module please use `registerSystem` instead.") - return + "This api registration call was ignored. " + + "If you are the author of that module please check that the id passed to `registerModule` matches the id in your manifest exactly." + + "If this call was made form a game system instead of a module please use `registerSystem` instead.", + ); + return; } // Using Drag Ruler's id is not allowed if (moduleId === "drag-ruler") { console.warn( `Drag Ruler | A module tried to register with the id "${moduleId}", which is not allowed. This api registration call was ignored. ` + - "If you're the author of the module please use the id of your own module as it's specified in your manifest to register to this api. " + - "If this call was made form a game system instead of a module please use `registerSystem` instead." - ) - return + "If you're the author of the module please use the id of your own module as it's specified in your manifest to register to this api. " + + "If this call was made form a game system instead of a module please use `registerSystem` instead.", + ); + return; } - register(module, "module", speedProvider) + register(module, "module", speedProvider); } export function registerSystem(systemId, speedProvider) { - const system = game.system + const system = game.system; // If the current system id doesn't match the provided id something went wrong. Log a warning and ignore this module if (system.id != systemId) { console.warn( `Drag Ruler | A system tried to register with the id "${systemId}". However the active system has a different id.` + - "This api registration call was ignored. " + - "If you are the author of that system please check that the id passed to `registerSystem` matches the id in your manifest exactly." + - "If this call was made form a module instead of a game system please use `registerModule` instead.") - return + "This api registration call was ignored. " + + "If you are the author of that system please check that the id passed to `registerSystem` matches the id in your manifest exactly." + + "If this call was made form a module instead of a game system please use `registerModule` instead.", + ); + return; } - register(system, "system", speedProvider) + register(system, "system", speedProvider); } diff --git a/js/compatibility.js b/js/compatibility.js index b97bcf0..64b1174 100644 --- a/js/compatibility.js +++ b/js/compatibility.js @@ -3,19 +3,24 @@ import {settingsKey} from "./settings.js"; import {highlightTokenShape} from "./util.js"; export function getHexSizeSupportTokenGridCenter(token) { - const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token) - return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y} + const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token); + return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y}; } -export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) { +export function highlightMeasurementTerrainRuler( + ray, + startDistance, + tokenShape = [{x: 0, y: 0}], + alpha = 1, +) { for (const space of ray.terrainRulerVisitedSpaces.reverse()) { const color = this.dragRulerGetColorForDistance(startDistance + space.distance); - highlightTokenShape.call(this, space, tokenShape, color, alpha) + highlightTokenShape.call(this, space, tokenShape, color, alpha); } } -export function measureDistances(segments, entity, shape, options={}) { - const opts = duplicate(options) +export function measureDistances(segments, entity, shape, options = {}) { + const opts = duplicate(options); if (canvas.grid.diagonalRule === "EUCL") { opts.ignoreGrid = true; opts.gridSpaes = false; @@ -25,17 +30,23 @@ export function measureDistances(segments, entity, shape, options={}) { 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)); + 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 = buildCostFunction(entity, shape); 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)); - } - 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) { + if (!opts.ignoreGrid) { opts.gridSpaces = true; } return canvas.grid.measureDistances(segments, opts); @@ -44,23 +55,31 @@ export function measureDistances(segments, entity, shape, options={}) { export function checkDependencies() { if (!game.modules.get("socketlib")?.active) { - console.error("Drag Ruler | The `socketlib` module isn't enabled, but it's required for Drag Ruler to operate properly."); + console.error( + "Drag Ruler | The `socketlib` module isn't enabled, but it's required for Drag Ruler to operate properly.", + ); if (game.user.isGM) { new Dialog({ title: game.i18n.localize("drag-ruler.dependencies.socketlib.title"), - content: `

${game.i18n.localize("drag-ruler.dependencies.socketlib.title")}

${game.i18n.localize("drag-ruler.dependencies.socketlib.text")}

`, + content: `

${game.i18n.localize( + "drag-ruler.dependencies.socketlib.title", + )}

${game.i18n.localize("drag-ruler.dependencies.socketlib.text")}

`, buttons: { ok: { icon: '', - label: game.i18n.localize("drag-ruler.dependencies.ok") - } + label: game.i18n.localize("drag-ruler.dependencies.ok"), + }, }, }).render(true); } - } - else if (!game.modules.get("terrain-ruler")?.active && game.user.isGM && !game.settings.get(settingsKey, "neverShowTerrainRulerHint")) { + } else if ( + !game.modules.get("terrain-ruler")?.active && + game.user.isGM && + !game.settings.get(settingsKey, "neverShowTerrainRulerHint") + ) { const lastHint = game.settings.get(settingsKey, "lastTerrainRulerHintTime"); - if (Date.now() - lastHint > 604800000) { // One week + if (Date.now() - lastHint > 604800000) { + // One week let enabledTerrainModule; if (game.modules.get("enhanced-terrain-layer")?.active) { enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title; @@ -68,20 +87,25 @@ export function checkDependencies() { if (enabledTerrainModule) { new Dialog({ title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"), - content: `

${game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title")}

${game.i18n.format("drag-ruler.dependencies.terrain-ruler.text", {moduleName: enabledTerrainModule})}

`, + content: `

${game.i18n.localize( + "drag-ruler.dependencies.terrain-ruler.title", + )}

${game.i18n.format("drag-ruler.dependencies.terrain-ruler.text", { + moduleName: enabledTerrainModule, + })}

`, buttons: { ok: { icon: '', label: game.i18n.localize("drag-ruler.dependencies.ok"), - callback: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()), + callback: () => + game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()), }, neverShowAgain: { icon: '', label: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.neverShowAgain"), callback: () => game.settings.set(settingsKey, "neverShowTerrainRulerHint", true), - } + }, }, - close: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()) + close: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()), }).render(true); } } diff --git a/js/data_structures.js b/js/data_structures.js index 8450913..0b9f94e 100644 --- a/js/data_structures.js +++ b/js/data_structures.js @@ -110,8 +110,8 @@ export class ProcessOnceQueue { const newNode = { value: element, next: null, - previous: null - } + previous: null, + }; if (!this.first) { this.first = newNode; diff --git a/js/foundry_fixes.js b/js/foundry_fixes.js index 9cafa59..876d7dc 100644 --- a/js/foundry_fixes.js +++ b/js/foundry_fixes.js @@ -3,18 +3,17 @@ // https://gitlab.com/foundrynet/foundryvtt/-/issues/4705 export function getPixelsFromGridPosition(xGrid, yGrid) { if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) { - return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid) + return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid); } - return canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid) + return canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid); } // Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently // https://gitlab.com/foundrynet/foundryvtt/-/issues/4705 export function getGridPositionFromPixels(xPixel, yPixel) { - const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel) - if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) - return [y, x] - return [x, y] + const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel); + if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return [y, x]; + return [x, y]; } export function getGridPositionFromPixelsObj(o) { diff --git a/js/foundry_imports.js b/js/foundry_imports.js index 2e96cab..869c707 100644 --- a/js/foundry_imports.js +++ b/js/foundry_imports.js @@ -1,12 +1,25 @@ import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js"; -import {getGridPositionFromPixels, getGridPositionFromPixelsObj, getPixelsFromGridPositionObj} from "./foundry_fixes.js"; +import { + getGridPositionFromPixels, + getGridPositionFromPixelsObj, + getPixelsFromGridPositionObj, +} from "./foundry_fixes.js"; import {Line} from "./geometry.js"; import {disableSnap, moveWithoutAnimation} from "./keybindings.js"; -import {trackRays} from "./movement_tracking.js" +import {trackRays} from "./movement_tracking.js"; import {findPath, isPathfindingEnabled} from "./pathfinding.js"; import {settingsKey} from "./settings.js"; import {recalculate} from "./socket.js"; -import {applyTokenSizeOffset, enumeratedZip, getSnapPointForEntity, getSnapPointForToken, getSnapPointForTokenObj, getTokenShape, highlightTokenShape, sum} from "./util.js"; +import { + applyTokenSizeOffset, + enumeratedZip, + getSnapPointForEntity, + getSnapPointForToken, + getSnapPointForTokenObj, + getTokenShape, + highlightTokenShape, + sum, +} from "./util.js"; // This is a modified version of Ruler.moveToken from foundry 0.7.9 export async function moveEntities(draggedEntity, selectedEntities) { @@ -27,12 +40,14 @@ export async function moveEntities(draggedEntity, selectedEntities) { 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)) + const offsetRays = rays + .filter(ray => !ray.isPrevious) + .map(ray => applyOffsetToRay(ray, offset)); if (window.WallHeight) { window.WallHeight.addBoundsToRays(offsetRays, draggedEntity); } return offsetRays.some(r => canvas.walls.checkCollision(r)); - }) + }); if (hasCollision) { ui.notifications.error(game.i18n.localize("ERROR.TokenCollide")); this._endMeasurement(); @@ -46,8 +61,7 @@ export async function moveEntities(draggedEntity, selectedEntities) { await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused); // Once all animations are complete we can clear the ruler - if (this.draggedEntity?.id === draggedEntity.id) - this._endMeasurement(); + if (this.draggedEntity?.id === draggedEntity.id) this._endMeasurement(); } // This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9 @@ -65,8 +79,7 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused) if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { dx = entity.data.x - origin[0]; dy = entity.data.y - origin[1]; - } - else { + } else { dx = entity.data.x - origin[0]; dy = entity.data.y - origin[1]; } @@ -81,7 +94,7 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused) // This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early this.cancelMovement = false; - for (let i = startWaypoint;i < entityAnimationData[0].rays.length; i++) { + for (let i = startWaypoint; i < entityAnimationData[0].rays.length; i++) { if (!wasPaused && game.paused) break; const entityPaths = entityAnimationData.map(({entity, rays, dx, dy}) => { const ray = rays[i]; @@ -92,18 +105,29 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused) const updates = entityPaths.map(({entity, path}) => { return {x: path.B.x, y: path.B.y, _id: entity.id}; }); - await draggedEntity.scene.updateEmbeddedDocuments(draggedEntity.constructor.embeddedName, updates, {animate}); + await draggedEntity.scene.updateEmbeddedDocuments( + draggedEntity.constructor.embeddedName, + updates, + {animate}, + ); if (animate) - await Promise.all(entityPaths.map(({entity}) => CanvasAnimation.getAnimation(entity.movementAnimationName)?.promise)); + await Promise.all( + entityPaths.map( + ({entity}) => CanvasAnimation.getAnimation(entity.movementAnimationName)?.promise, + ), + ); // This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early if (this.cancelMovement) { - entityAnimationData.forEach(ead => ead.rays = ead.rays.slice(0, i + 1)); + entityAnimationData.forEach(ead => (ead.rays = ead.rays.slice(0, i + 1))); break; } } if (isToken) - trackRays(entities, entityAnimationData.map(({rays}) => rays)).then(() => recalculate(entities)); + trackRays( + entities, + entityAnimationData.map(({rays}) => rays), + ).then(() => recalculate(entities)); } function calculateEntityOffset(entityA, entityB) { @@ -111,7 +135,10 @@ function calculateEntityOffset(entityA, entityB) { } function applyOffsetToRay(ray, offset) { - const newRay = new Ray({x: ray.A.x + offset.x, y: ray.A.y + offset.y}, {x: ray.B.x + offset.x, y: ray.B.y + offset.y}); + const newRay = new Ray( + {x: ray.A.x + offset.x, y: ray.A.y + offset.y}, + {x: ray.B.x + offset.x, y: ray.B.y + offset.y}, + ); newRay.isPrevious = ray.isPrevious; return newRay; } @@ -121,7 +148,10 @@ export function onMouseMove(event) { if (this._state === Ruler.STATES.MOVING) return; // Extract event data - const destination = {x: event.data.destination.x + this.rulerOffset.x, y: event.data.destination.y + this.rulerOffset.y} + const destination = { + x: event.data.destination.x + this.rulerOffset.x, + y: event.data.destination.y + this.rulerOffset.y, + }; // Hide any existing Token HUD canvas.hud.token.clear(); @@ -140,12 +170,21 @@ function scheduleMeasurement(destination, event) { event._measureTime = Date.now(); this._state = Ruler.STATES.MEASURING; cancelScheduledMeasurement.call(this); - } - else { + } 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.deferredMeasurementPromise = new Promise( + (resolve, reject) => (this.deferredMeasurementResolve = resolve), + ); + this.deferredMeasurementTimeout = window.setTimeout( + () => + scheduleMeasurement.call( + this, + this.deferredMeasurementData.destination, + this.deferredMeasurementData.event, + ), + measurementInterval, + ); } } } @@ -157,11 +196,9 @@ export function cancelScheduledMeasurement() { } // This is a modified version of Ruler.measure form foundry 0.7.9 -export function measure(destination, options={}) { - +export function measure(destination, options = {}) { const isToken = this.draggedEntity instanceof Token; - if (isToken && !this.draggedEntity.isVisible) - return [] + if (isToken && !this.draggedEntity.isVisible) return []; options.snap = options.snap ?? !disableSnap; @@ -175,32 +212,34 @@ export function measure(destination, options={}) { const from = getGridPositionFromPixelsObj(this.waypoints[this.waypoints.length - 1]); const to = getGridPositionFromPixelsObj(destination); let path = findPath(from, to, this.draggedEntity, this.waypoints); - if (path) - path.shift(); + if (path) path.shift(); if (path && path.length > 0) { - path = path.map(point => getSnapPointForTokenObj(getPixelsFromGridPositionObj(point), this.draggedEntity)); + path = path.map(point => + getSnapPointForTokenObj(getPixelsFromGridPositionObj(point), this.draggedEntity), + ); // If the token is snapped to the grid, the first point of the path is already handled by the ruler - if (path[0].x === this.waypoints[this.waypoints.length - 1].x && path[0].y === this.waypoints[this.waypoints.length - 1].y) + if ( + path[0].x === this.waypoints[this.waypoints.length - 1].x && + path[0].y === this.waypoints[this.waypoints.length - 1].y + ) path = path.slice(1); // If snapping is enabled, the last point of the path is already handled by the ruler - if (options.snap) - path = path.slice(0, path.length - 1); + if (options.snap) path = path.slice(0, path.length - 1); for (const point of path) { point.isPathfinding = true; this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); } this.waypoints = this.waypoints.concat(path); - } - else { + } else { // Don't show a path if the pathfinding yields no result to show the user that the destination is unreachable destination = this.waypoints[this.waypoints.length - 1]; } } - if(options.gridSpaces === undefined) { + if (options.gridSpaces === undefined) { options.gridSpaces = canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS; } @@ -209,7 +248,7 @@ export function measure(destination, options={}) { options.ignoreGrid = true; } - if(options.ignoreGrid === undefined) { + if (options.ignoreGrid === undefined) { options.ignoreGrid = false; } @@ -217,24 +256,26 @@ export function measure(destination, options={}) { 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 = 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 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; this.destination = destination; // Iterate over waypoints and construct segment rays const segments = []; - const centeredSegments = [] + const centeredSegments = []; for (let [i, dest] of waypoints.slice(1).entries()) { - const centeredDest = centeredWaypoints[i + 1] + const centeredDest = centeredWaypoints[i + 1]; const origin = waypoints[i]; - const centeredOrigin = centeredWaypoints[i] + const centeredOrigin = centeredWaypoints[i]; const label = this.labels.children[i]; const ray = new Ray(origin, dest); - const centeredRay = new Ray(centeredOrigin, centeredDest) + const centeredRay = new Ray(centeredOrigin, centeredDest); ray.isPrevious = Boolean(origin.isPrevious); centeredRay.isPrevious = ray.isPrevious; ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces; @@ -245,11 +286,10 @@ export function measure(destination, options={}) { if (label) label.visible = false; continue; } - segments.push({ ray, label }); - centeredSegments.push({ray: centeredRay, label}) + segments.push({ray, label}); + centeredSegments.push({ray: centeredRay, label}); } - const shape = isToken ? getTokenShape(this.draggedEntity) : null; // Compute measured distance @@ -258,9 +298,9 @@ export function measure(destination, options={}) { let totalDistance = 0; for (let [i, d] of distances.entries()) { let s = centeredSegments[i]; - s.startDistance = totalDistance + s.startDistance = totalDistance; totalDistance += d; - s.last = i === (centeredSegments.length - 1); + s.last = i === centeredSegments.length - 1; s.distance = d; s.text = this._getSegmentLabel(d, totalDistance, s.last); } @@ -271,18 +311,24 @@ export function measure(destination, options={}) { // Draw measured path r.clear(); - let rulerColor + let rulerColor; if (!options.gridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) rulerColor = this.dragRulerGetColorForDistance(totalDistance); - else - rulerColor = this.color - for (const [i, s, cs] of enumeratedZip([...segments].reverse(), [...centeredSegments].reverse())) { - const { label, text, last } = cs; + else rulerColor = this.color; + for (const [i, s, cs] of enumeratedZip( + [...segments].reverse(), + [...centeredSegments].reverse(), + )) { + const {label, text, last} = cs; // Draw line segment const opacityMultiplier = s.ray.isPrevious ? 0.33 : 1; - r.lineStyle(6, 0x000000, 0.5 * opacityMultiplier).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y) - .lineStyle(4, rulerColor, 0.25 * opacityMultiplier).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y); + r.lineStyle(6, 0x000000, 0.5 * opacityMultiplier) + .moveTo(s.ray.A.x, s.ray.A.y) + .lineTo(s.ray.B.x, s.ray.B.y) + .lineStyle(4, rulerColor, 0.25 * opacityMultiplier) + .moveTo(s.ray.A.x, s.ray.A.y) + .lineTo(s.ray.B.x, s.ray.B.y); // Draw the distance label just after the endpoint of the segment if (label) { @@ -296,11 +342,14 @@ export function measure(destination, options={}) { 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 ( + 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)); + 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; @@ -310,9 +359,14 @@ export function measure(destination, options={}) { // Highlight grid positions if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) { if (options.enableTerrainRuler) { - highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier) - } - else { + highlightMeasurementTerrainRuler.call( + this, + cs.ray, + cs.startDistance, + shape, + opacityMultiplier, + ); + } else { const previousSegments = centeredSegments.slice(0, segments.length - 1 - i); highlightMeasurementNative.call(this, cs.ray, previousSegments, shape, opacityMultiplier); } @@ -328,22 +382,30 @@ export function measure(destination, options={}) { return segments; } -export function highlightMeasurementNative(ray, previousSegments, tokenShape=[{x: 0, y: 0}], alpha=1) { +export function highlightMeasurementNative( + ray, + previousSegments, + tokenShape = [{x: 0, y: 0}], + alpha = 1, +) { const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1; - const nMax = Math.max(Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), 1); - const tMax = Array.fromRange(nMax+1).map(t => t / nMax); + const nMax = Math.max( + Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), + 1, + ); + const tMax = Array.fromRange(nMax + 1).map(t => t / nMax); // Track prior position let prior = null; // Iterate over ray portions - for ( let [i, t] of tMax.reverse().entries() ) { + for (let [i, t] of tMax.reverse().entries()) { let {x, y} = ray.project(t); // Get grid position - let [x0, y0] = (i === 0) ? [null, null] : prior; + let [x0, y0] = i === 0 ? [null, null] : prior; let [x1, y1] = canvas.grid.grid.getGridPositionFromPixels(x, y); - if ( x0 === x1 && y0 === y1 ) continue; + if (x0 === x1 && y0 === y1) continue; // Highlight the grid position let [xgtl, ygtl] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1); @@ -358,7 +420,7 @@ export function highlightMeasurementNative(ray, previousSegments, tokenShape=[{x // If the positions are not neighbors, also highlight their halfway point if (i > 0 && !canvas.grid.isNeighbor(x0, y0, x1, y1)) { - let th = tMax[i - 1] - (0.5 / nMax); + let th = tMax[i - 1] - 0.5 / nMax; let {x, y} = ray.project(th); let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y); let [xghtl, yghtl] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h); diff --git a/js/hex_support.js b/js/hex_support.js index 8d9f349..011e39c 100644 --- a/js/hex_support.js +++ b/js/hex_support.js @@ -18,76 +18,73 @@ export function findVertexSnapPoint(x, y, altOrientationFlag) { } function findSnapPointRows(x, y, h, w, alt) { - let xOffset = 0.0 + let xOffset = 0.0; if (canvas.grid.grid.even) { - xOffset = -0.5 + xOffset = -0.5; } - let yOffset1 = 0.75 - let yOffset2 = 0.00 + let yOffset1 = 0.75; + let yOffset2 = 0.0; if (alt) { - yOffset1 = 0.25 - yOffset2 = 1.00 + yOffset1 = 0.25; + yOffset2 = 1.0; } let row1 = calculateSnapPointsRows(x, y, h, w, 0.5 + xOffset, yOffset1); let row2 = calculateSnapPointsRows(x, y, h, w, 1.0 + xOffset, yOffset2); - let dist1 = Math.pow((row1.x - x), 2) + Math.pow((row1.y - y), 2) - let dist2 = Math.pow((row2.x - x), 2) + Math.pow((row2.y - y), 2) + let dist1 = Math.pow(row1.x - x, 2) + Math.pow(row1.y - y, 2); + let dist2 = Math.pow(row2.x - x, 2) + Math.pow(row2.y - y, 2); if (dist1 < dist2) { - return row1 - } - else { - return row2 + return row1; + } else { + return row2; } } function calculateSnapPointsRows(x, y, h, w, xOff, yOff) { - let c = Math.floor(((x + ((0.5 - xOff) * w)) / w) + 1) - let r = Math.floor(((y + ((0.75 - yOff) * h)) / (1.5 * h)) + 1) + let c = Math.floor((x + (0.5 - xOff) * w) / w + 1); + let r = Math.floor((y + (0.75 - yOff) * h) / (1.5 * h) + 1); - let snapX = (c * w) - ((1 - xOff) * w) - let snapY = (r * h * 1.5) - ((1.5 - yOff) * h) + let snapX = c * w - (1 - xOff) * w; + let snapY = r * h * 1.5 - (1.5 - yOff) * h; - return {x: snapX, y: snapY} + return {x: snapX, y: snapY}; } function findSnapPointCols(x, y, h, w, alt) { - let yOffset = 0.0 + let yOffset = 0.0; if (canvas.grid.grid.even) { - yOffset = -0.5 + yOffset = -0.5; } - let xOffset1 = 0.25 - let xOffset2 = 1.00 + let xOffset1 = 0.25; + let xOffset2 = 1.0; if (alt) { - xOffset1 = 0.75 - xOffset2 = 0.00 + xOffset1 = 0.75; + xOffset2 = 0.0; } let row1 = calculateSnapPointsCols(x, y, h, w, xOffset1, 0.5 + yOffset); let row2 = calculateSnapPointsCols(x, y, h, w, xOffset2, 1.0 + yOffset); - let dist1 = Math.pow((row1.x - x), 2) + Math.pow((row1.y - y), 2) - let dist2 = Math.pow((row2.x - x), 2) + Math.pow((row2.y - y), 2) + let dist1 = Math.pow(row1.x - x, 2) + Math.pow(row1.y - y, 2); + let dist2 = Math.pow(row2.x - x, 2) + Math.pow(row2.y - y, 2); if (dist1 < dist2) { - return row1 + return row1; + } else { + return row2; } - else { - return row2 - } - } function calculateSnapPointsCols(x, y, h, w, xOff, yOff) { - let c = Math.floor(((x + ((0.75 - xOff) * w)) / (1.5 * w)) + 1) - let r = Math.floor(((y + ((0.5 - yOff) * h)) / h) + 1) + let c = Math.floor((x + (0.75 - xOff) * w) / (1.5 * w) + 1); + let r = Math.floor((y + (0.5 - yOff) * h) / h + 1); - let snapX = (c * w * 1.5) - ((1.5 - xOff) * w) - let snapY = (r * h) - ((1 - yOff) * h) + let snapX = c * w * 1.5 - (1.5 - xOff) * w; + let snapY = r * h - (1 - yOff) * h; - return {x: snapX, y: snapY} + return {x: snapX, y: snapY}; } diff --git a/js/keybindings.js b/js/keybindings.js index 095ea09..785c285 100644 --- a/js/keybindings.js +++ b/js/keybindings.js @@ -9,18 +9,22 @@ export function registerKeybindings() { game.keybindings.register(settingsKey, "cancelDrag", { name: "drag-ruler.keybindings.cancelDrag", onDown: cancelDrag, - uneditable: [{ - key: "Escape", - }], + uneditable: [ + { + key: "Escape", + }, + ], precedence: -1, }); game.keybindings.register(settingsKey, "createWaypoint", { name: "drag-ruler.keybindings.createWaypoint", onDown: handleCreateWaypoint, - editable: [{ - key: "Space" - }], + editable: [ + { + key: "Space", + }, + ], precedence: -1, }); @@ -35,9 +39,11 @@ export function registerKeybindings() { hint: "drag-ruler.keybindings.disableSnap.hint", onDown: handleDisableSnap, onUp: handleDisableSnap, - editable: [{ - key: "ShiftLeft", - }], + editable: [ + { + key: "ShiftLeft", + }, + ], precedence: -1, }); @@ -46,9 +52,11 @@ export function registerKeybindings() { hint: "drag-ruler.keybindings.moveWithoutAnimation.hint", onDown: handleMoveWithoutAnimation, onUp: handleMoveWithoutAnimation, - editable: [{ - key: "AltLeft", - }], + editable: [ + { + key: "AltLeft", + }, + ], precedence: -1, }); @@ -64,8 +72,7 @@ export function registerKeybindings() { function handleDeleteWaypoint() { const ruler = canvas.controls.ruler; - if (!ruler?.draggedEntity) - return false; + if (!ruler?.draggedEntity) return false; ruler.dragRulerDeleteWaypoint(); return true; } @@ -74,16 +81,14 @@ function handleCreateWaypoint() { const ruler = canvas.controls.ruler; // .draggedEntity is used here because .isDragRuler only returns true once the ruler started measuring // Ruler can end up being undefined here if no canvas is active - if (!ruler?.draggedEntity) - return false; + if (!ruler?.draggedEntity) return false; let options = {}; setSnapParameterOnOptions(ruler, options); if (ruler._state === Ruler.STATES.INACTIVE) { ruler.dragRulerStart(options); - } - else { + } else { ruler.dragRulerAddWaypoint(getMeasurePosition(), options); } return true; @@ -91,8 +96,7 @@ function handleCreateWaypoint() { function cancelDrag() { const ruler = canvas.controls.ruler; - if (!ruler?.draggedEntity) - return false; + if (!ruler?.draggedEntity) return false; ruler.dragRulerAbortDrag(); return true; } @@ -101,10 +105,8 @@ function handleDisableSnap(event) { disableSnap = !event.up; const ruler = canvas.controls.ruler; - if (!ruler?.isDragRuler) - return false; - if (ruler._state !== Ruler.STATES.MEASURING) - return false; + if (!ruler?.isDragRuler) return false; + if (ruler._state !== Ruler.STATES.MEASURING) return false; ruler.measure(getMeasurePosition(), {snap: !disableSnap}); ruler.dragRulerSendState(); @@ -115,10 +117,8 @@ function handleMoveWithoutAnimation(event) { moveWithoutAnimation = !event.up; const ruler = canvas.controls.ruler; - if (!ruler?.isDragRuler) - return false; - if (ruler._state !== Ruler.STATES.MEASURING) - return false; + if (!ruler?.isDragRuler) return false; + if (ruler._state !== Ruler.STATES.MEASURING) return false; ruler.measure(getMeasurePosition(), {snap: !disableSnap}); ruler.dragRulerSendState(); @@ -129,10 +129,8 @@ function handleTogglePathfinding(event) { togglePathfinding = !event.up; const ruler = canvas.controls.ruler; - if (!ruler?.isDragRuler) - return false; - if (ruler._state !== Ruler.STATES.MEASURING) - return false; + if (!ruler?.isDragRuler) return false; + if (ruler._state !== Ruler.STATES.MEASURING) return false; ruler.measure(getMeasurePosition(), {snap: !disableSnap}); ruler.dragRulerSendState(); diff --git a/js/libwrapper_shim.js b/js/libwrapper_shim.js index b74a73b..5e4ef34 100644 --- a/js/libwrapper_shim.js +++ b/js/libwrapper_shim.js @@ -1,73 +1,90 @@ // SPDX-License-Identifier: MIT // Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro - -'use strict'; +"use strict"; // A shim for the libWrapper library export let libWrapper = undefined; -export const VERSIONS = [1,11,0]; -export const TGT_SPLIT_RE = new RegExp("([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])", 'g'); -export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", 'g'); +export const VERSIONS = [1, 11, 0]; +export const TGT_SPLIT_RE = new RegExp( + "([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])", + "g", +); +export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", "g"); // Main shim code -Hooks.once('init', () => { +Hooks.once("init", () => { // Check if the real module is already loaded - if so, use it - if(globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) { + if (globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) { libWrapper = globalThis.libWrapper; return; } // Fallback implementation libWrapper = class { - static get is_fallback() { return true }; + static get is_fallback() { + return true; + } - static get WRAPPER() { return 'WRAPPER' }; - static get MIXED() { return 'MIXED' }; - static get OVERRIDE() { return 'OVERRIDE' }; + static get WRAPPER() { + return "WRAPPER"; + } + static get MIXED() { + return "MIXED"; + } + static get OVERRIDE() { + return "OVERRIDE"; + } - static register(package_id, target, fn, type="MIXED", {chain=undefined}={}) { - const is_setter = target.endsWith('#set'); + static register(package_id, target, fn, type = "MIXED", {chain = undefined} = {}) { + const is_setter = target.endsWith("#set"); target = !is_setter ? target : target.slice(0, -4); - const split = target.match(TGT_SPLIT_RE).map((x)=>x.replace(/\\(.)/g, '$1').replace(TGT_CLEANUP_RE,'')); - const root_nm = split.splice(0,1)[0]; + const split = target + .match(TGT_SPLIT_RE) + .map(x => x.replace(/\\(.)/g, "$1").replace(TGT_CLEANUP_RE, "")); + const root_nm = split.splice(0, 1)[0]; let obj, fn_name; - if(split.length == 0) { + if (split.length == 0) { obj = globalThis; fn_name = root_nm; - } - else { + } else { const _eval = eval; fn_name = split.pop(); - obj = split.reduce((x,y)=>x[y], globalThis[root_nm] ?? _eval(root_nm)); + obj = split.reduce((x, y) => x[y], globalThis[root_nm] ?? _eval(root_nm)); } let iObj = obj; let descriptor = null; - while(iObj) { + while (iObj) { descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name); - if(descriptor) break; + if (descriptor) break; iObj = Object.getPrototypeOf(iObj); } - if(!descriptor || descriptor?.configurable === false) throw `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`; + if (!descriptor || descriptor?.configurable === false) + throw `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`; let original = null; - const wrapper = (chain ?? (type.toUpperCase?.() != 'OVERRIDE' && type != 3)) ? function() { return fn.call(this, original.bind(this), ...arguments); } : function() { return fn.apply(this, arguments); }; + const wrapper = + chain ?? (type.toUpperCase?.() != "OVERRIDE" && type != 3) + ? function () { + return fn.call(this, original.bind(this), ...arguments); + } + : function () { + return fn.apply(this, arguments); + }; - if(!is_setter) { - if(descriptor.value) { + if (!is_setter) { + if (descriptor.value) { original = descriptor.value; descriptor.value = wrapper; - } - else { + } else { original = descriptor.get; descriptor.get = wrapper; } - } - else { - if(!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`; + } else { + if (!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`; original = descriptor.set; descriptor.set = wrapper; } @@ -75,5 +92,5 @@ Hooks.once('init', () => { descriptor.configurable = true; Object.defineProperty(obj, fn_name, descriptor); } - } + }; }); diff --git a/js/main.js b/js/main.js index 0e14968..6dea774 100644 --- a/js/main.js +++ b/js/main.js @@ -1,20 +1,31 @@ -"use strict" +"use strict"; -import {getColorForDistanceAndToken, getMovedDistanceFromToken, getRangesFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js"; +import { + getColorForDistanceAndToken, + getMovedDistanceFromToken, + getRangesFromSpeedProvider, + initApi, + registerModule, + registerSystem, +} from "./api.js"; import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js"; -import {moveEntities, onMouseMove} from "./foundry_imports.js" +import {moveEntities, onMouseMove} from "./foundry_imports.js"; import {disableSnap, registerKeybindings} from "./keybindings.js"; import {libWrapper} from "./libwrapper_shim.js"; -import {performMigrations} from "./migration.js" +import {performMigrations} from "./migration.js"; import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js"; -import {wipePathfindingCache, initializePathfinding, startBackgroundCaching} from "./pathfinding.js"; +import { + wipePathfindingCache, + initializePathfinding, + startBackgroundCaching, +} from "./pathfinding.js"; import {extendRuler} from "./ruler.js"; -import {registerSettings, RightClickAction, settingsKey} from "./settings.js" +import {registerSettings, RightClickAction, settingsKey} from "./settings.js"; import {recalculate} from "./socket.js"; -import {SpeedProvider} from "./speed_provider.js" +import {SpeedProvider} from "./speed_provider.js"; import {setSnapParameterOnOptions} from "./util.js"; -import initGridlessPathfinding, * as GridlessPathfinding from "../wasm/gridless_pathfinding.js" +import initGridlessPathfinding, * as GridlessPathfinding from "../wasm/gridless_pathfinding.js"; CONFIG.debug.dragRuler = false; export let debugGraphics = undefined; @@ -37,7 +48,7 @@ initGridlessPathfinding().then(() => { }); // Whenever a token the current user controls updates, start caching - Hooks.on("updateToken", (document) => { + Hooks.on("updateToken", document => { const token = document.object; if (token._controlled) { startBackgroundCaching(token); @@ -46,12 +57,17 @@ initGridlessPathfinding().then(() => { }); Hooks.once("init", () => { - registerSettings() - registerKeybindings() - initApi() + registerSettings(); + registerKeybindings(); + initApi(); hookDragHandlers(Token); hookDragHandlers(MeasuredTemplate); - libWrapper.register("drag-ruler", "TokenLayer.prototype.undoHistory", tokenLayerUndoHistory, "WRAPPER"); + libWrapper.register( + "drag-ruler", + "TokenLayer.prototype.undoHistory", + tokenLayerUndoHistory, + "WRAPPER", + ); extendRuler(); @@ -62,16 +78,15 @@ Hooks.once("init", () => { registerSystem, recalculate, resetMovementHistory, - } -}) + }; +}); Hooks.once("ready", () => { - performMigrations() + performMigrations(); checkDependencies(); - Hooks.callAll("dragRuler.ready", SpeedProvider) - if (CONFIG.debug.dragRuler) - debugGraphics = canvas.controls.addChild(new PIXI.Container()); -}) + Hooks.callAll("dragRuler.ready", SpeedProvider); + if (CONFIG.debug.dragRuler) debugGraphics = canvas.controls.addChild(new PIXI.Container()); +}); Hooks.on("canvasReady", () => { canvas.controls.rulers.children.forEach(ruler => { @@ -79,37 +94,61 @@ Hooks.on("canvasReady", () => { Object.defineProperty(ruler, "isDragRuler", { get: function isDragRuler() { return Boolean(this.draggedEntity) && this._state !== Ruler.STATES.INACTIVE; - } - }) - }) -}) + }, + }); + }); +}); Hooks.on("getCombatTrackerEntryContext", function (html, menu) { const entry = { name: "drag-ruler.resetMovementHistory", icon: '', - callback: li => resetMovementHistory(ui.combat.viewed, li.data('combatant-id')), + callback: li => resetMovementHistory(ui.combat.viewed, li.data("combatant-id")), }; menu.splice(1, 0, entry); }); function forwardIfUnahndled(newFn) { - return function(oldFn, ...args) { + return function (oldFn, ...args) { const eventHandled = newFn(...args); - if (!eventHandled) - oldFn(...args); + if (!eventHandled) oldFn(...args); }; } function hookDragHandlers(entityType) { - const entityName = entityType.name - libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftStart`, onEntityLeftDragStart, "WRAPPER"); + const entityName = entityType.name; + libWrapper.register( + "drag-ruler", + `${entityName}.prototype._onDragLeftStart`, + onEntityLeftDragStart, + "WRAPPER", + ); if (entityType === Token) - libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMoveSnap, "WRAPPER"); + libWrapper.register( + "drag-ruler", + `${entityName}.prototype._onDragLeftMove`, + onEntityLeftDragMoveSnap, + "WRAPPER", + ); else - libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMove, "WRAPPER"); - libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftDrop`, forwardIfUnahndled(onEntityDragLeftDrop), "MIXED"); - libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftCancel`, forwardIfUnahndled(onEntityDragLeftCancel), "MIXED"); + libWrapper.register( + "drag-ruler", + `${entityName}.prototype._onDragLeftMove`, + onEntityLeftDragMove, + "WRAPPER", + ); + libWrapper.register( + "drag-ruler", + `${entityName}.prototype._onDragLeftDrop`, + forwardIfUnahndled(onEntityDragLeftDrop), + "MIXED", + ); + libWrapper.register( + "drag-ruler", + `${entityName}.prototype._onDragLeftCancel`, + forwardIfUnahndled(onEntityDragLeftCancel), + "MIXED", + ); } async function tokenLayerUndoHistory(wrapped) { @@ -127,14 +166,21 @@ async function tokenLayerUndoHistory(wrapped) { function onEntityLeftDragStart(wrapped, event) { wrapped(event); const isToken = this instanceof Token; - const ruler = canvas.controls.ruler + const ruler = canvas.controls.ruler; ruler.draggedEntity = this; let entityCenter; - if (isToken && 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) + ) entityCenter = getHexSizeSupportTokenGridCenter(this); - else - entityCenter = this.center; - ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y}; + else entityCenter = this.center; + ruler.rulerOffset = { + x: entityCenter.x - event.data.origin.x, + y: entityCenter.y - event.data.origin.y, + }; if (game.settings.get(settingsKey, "autoStartMeasurement")) { let options = {}; setSnapParameterOnOptions(ruler, options); @@ -149,48 +195,42 @@ function onEntityLeftDragMoveSnap(wrapped, event) { function onEntityLeftDragMove(wrapped, event) { wrapped(event); - const ruler = canvas.controls.ruler - if (ruler.isDragRuler) - onMouseMove.call(ruler, event) + const ruler = canvas.controls.ruler; + if (ruler.isDragRuler) onMouseMove.call(ruler, event); } function onEntityDragLeftDrop(event) { - const ruler = canvas.controls.ruler + const ruler = canvas.controls.ruler; if (!ruler.isDragRuler) { ruler.draggedEntity = undefined; - return false + return false; } // 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); + if (selectedTokens.length === 0) selectedTokens.push(ruler.draggedEntity); // This can happen if the ruler is being dragged so rapidly that the drag move handler hasn't been called before dropping - if (ruler._state === Ruler.STATES.STARTING) - onMouseMove.call(ruler, event); - ruler._state = Ruler.STATES.MOVING + if (ruler._state === Ruler.STATES.STARTING) onMouseMove.call(ruler, event); + ruler._state = Ruler.STATES.MOVING; moveEntities.call(ruler, ruler.draggedEntity, selectedTokens); - return true + return true; } function onEntityDragLeftCancel(event) { // This function is invoked by right clicking - const ruler = canvas.controls.ruler - if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING) - return false + const ruler = canvas.controls.ruler; + if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING) return false; const rightClickAction = game.settings.get(settingsKey, "rightClickAction"); let options = {}; setSnapParameterOnOptions(ruler, options); if (ruler._state === Ruler.STATES.INACTIVE) { - if (rightClickAction !== RightClickAction.CREATE_WAYPOINT) - return false; + if (rightClickAction !== RightClickAction.CREATE_WAYPOINT) return false; ruler.dragRulerStart(options); event.preventDefault(); - } - else if (ruler._state === Ruler.STATES.MEASURING) { + } else if (ruler._state === Ruler.STATES.MEASURING) { switch (rightClickAction) { case RightClickAction.CREATE_WAYPOINT: event.preventDefault(); @@ -209,14 +249,10 @@ function onEntityDragLeftCancel(event) { function applyGridlessSnapping(event) { const ruler = canvas.controls.ruler; - if (!game.settings.get(settingsKey, "useGridlessRaster")) - return; - if (!ruler.isDragRuler) - return; - if (disableSnap) - return; - if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) - return; + if (!game.settings.get(settingsKey, "useGridlessRaster")) return; + if (!ruler.isDragRuler) return; + if (disableSnap) return; + if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return; const rasterWidth = 35 / canvas.stage.scale.x; const tokenX = event.data.destination.x; @@ -226,7 +262,9 @@ function applyGridlessSnapping(event) { const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active; if (terrainRulerAvailable) { - const segments = Ruler.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination).map(ray => {return {ray}}); + const segments = Ruler.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination).map(ray => { + return {ray}; + }); const pinpointDistances = new Map(); for (const range of ranges) { pinpointDistances.set(range.range, null); @@ -234,7 +272,7 @@ function applyGridlessSnapping(event) { terrainRuler.measureDistances(segments, {pinpointDistances}); const targetDistance = Array.from(pinpointDistances.entries()) .filter(([_key, val]) => val) - .reduce((value, current) => value[0] > current[0] ? value : current, [0, null]); + .reduce((value, current) => (value[0] > current[0] ? value : current), [0, null]); const rasterLocation = targetDistance[1]; if (rasterLocation) { const deltaX = destination.x - rasterLocation.x; @@ -245,12 +283,15 @@ function applyGridlessSnapping(event) { event.data.destination.y = rasterLocation.y - ruler.rulerOffset.y; } } - } - else { + } else { let waypointDistance = 0; let origin = event.data.origin; if (ruler.waypoints.length > 1) { - const segments = Ruler.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination).map(ray => {return {ray}}); + const segments = Ruler.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination).map( + ray => { + return {ray}; + }, + ); origin = segments.pop().ray.A; waypointDistance = canvas.grid.measureDistances(segments).reduce((a, b) => a + b); origin = {x: origin.x - ruler.rulerOffset.x, y: origin.y - ruler.rulerOffset.y}; @@ -263,13 +304,13 @@ function applyGridlessSnapping(event) { let targetDistance = ranges .map(range => range.range) .map(range => range - waypointDistance) - .map(range => range * canvas.dimensions.size / canvas.dimensions.distance) + .map(range => (range * canvas.dimensions.size) / canvas.dimensions.distance) .filter(range => range < distance) .reduce((a, b) => Math.max(a, b), 0); if (targetDistance) { if (distance < targetDistance + rasterWidth) { - event.data.destination.x = origin.x + deltaX * targetDistance / distance; - event.data.destination.y = origin.y + deltaY * targetDistance / distance; + event.data.destination.x = origin.x + (deltaX * targetDistance) / distance; + event.data.destination.y = origin.y + (deltaY * targetDistance) / distance; } } } diff --git a/js/migration.js b/js/migration.js index b92ac79..3f7db2a 100644 --- a/js/migration.js +++ b/js/migration.js @@ -1,26 +1,24 @@ -import {RightClickAction, settingsKey} from "./settings.js" +import {RightClickAction, settingsKey} from "./settings.js"; -const currentDataVersion = "1.10.0" +const currentDataVersion = "1.10.0"; export async function performMigrations() { - if (game.user.isGM) - await performWorldMigraionts() - await performClientMigrations() + if (game.user.isGM) await performWorldMigraionts(); + await performClientMigrations(); } async function performWorldMigraionts() { let dataVersion = game.settings.get(settingsKey, "dataVersion"); - if (dataVersion === currentDataVersion) - return; + if (dataVersion === currentDataVersion) return; if (dataVersion === "fresh install") { - game.settings.set(settingsKey, "dataVersion", currentDataVersion) - return + game.settings.set(settingsKey, "dataVersion", currentDataVersion); + return; } if (dataVersion === "1.3.0") { - dataVersion = "1.10.0" + dataVersion = "1.10.0"; } game.settings.set(settingsKey, "dataVersion", dataVersion); @@ -31,13 +29,15 @@ async function performClientMigrations() { if (dataVersion === "fresh install") { // Start of migration from unnamed version (< 1.10.0). TODO Remove in a future version - const swapSpacebarRightClick = game.settings.storage.get("client").getItem(`${settingsKey}.swapSpacebarRightClick`); + const swapSpacebarRightClick = game.settings.storage + .get("client") + .getItem(`${settingsKey}.swapSpacebarRightClick`); if (swapSpacebarRightClick) { game.settings.set(settingsKey, "rightClickAction", RightClickAction.CREATE_WAYPOINT); await game.keybindings.set(settingsKey, "createWaypoint", []); await game.keybindings.set(settingsKey, "deleteWaypoint", [{key: "Space"}]); } // End of migration from unnamed version - game.settings.set(settingsKey, "clientDataVersion", currentDataVersion) + game.settings.set(settingsKey, "clientDataVersion", currentDataVersion); } } diff --git a/js/movement_tracking.js b/js/movement_tracking.js index 541f3a5..77f4a44 100644 --- a/js/movement_tracking.js +++ b/js/movement_tracking.js @@ -9,38 +9,35 @@ function initTrackingFlag(combatant) { if (isNaN(dragRulerFlag.trackedRound)) { mergeObject(dragRulerFlag, initialFlag); } - } - else { + } else { combatant.data.flags.dragRuler = initialFlag; } } function getInitializedCombatant(token, combat) { const combatant = combat.getCombatantByToken(token.id); - if (!combatant) - return undefined; + if (!combatant) return undefined; initTrackingFlag(combatant); return combatant; } export async function trackRays(tokens, tokenRays) { const combat = game.combat; - if (!combat) - return; - if (!combat.started) - return; + if (!combat) return; + if (!combat.started) return; if (!(tokens instanceof Array)) { tokens = [tokens]; tokenRays = [tokenRays]; } - const updates = Array.from(zip(tokens, tokenRays)).map(([token, rays]) => calculateUpdate(combat, token, rays)).filter(Boolean); + const updates = Array.from(zip(tokens, tokenRays)) + .map(([token, rays]) => calculateUpdate(combat, token, rays)) + .filter(Boolean); await updateCombatantDragRulerFlags(combat, updates); } function calculateUpdate(combat, token, rays) { const combatant = getInitializedCombatant(token, combat); - if (!combatant) - return; + if (!combatant) return; // Check if we have entered a new round. If so, remove the currently stored path if (combat.data.round > combatant.data.flags.dragRuler.trackedRound) { @@ -56,7 +53,10 @@ function calculateUpdate(combat, token, 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), {terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState, enableTerrainRuler: terrainRulerAvailable}); + measureDistances([{ray}], token, getTokenShape(token), { + terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState, + enableTerrainRuler: terrainRulerAvailable, + }); ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces; ray.A.dragRulerFinalState = ray.terrainRulerFinalState; } @@ -68,23 +68,18 @@ function calculateUpdate(combat, token, rays) { export function getMovementHistory(token) { const combat = game.combat; - if (!combat) - return []; + if (!combat) return []; const combatant = combat.getCombatantByToken(token.id); - if (!combatant) - return []; + if (!combatant) return []; const dragRulerFlags = combatant.data.flags.dragRuler; - if (!dragRulerFlags) - return []; - if (combat.data.round > dragRulerFlags.trackedRound) - return []; + if (!dragRulerFlags) return []; + if (combat.data.round > dragRulerFlags.trackedRound) return []; return dragRulerFlags.passedWaypoints ?? []; } export async function removeLastHistoryEntryIfAt(token, x, y) { const history = getMovementHistory(token); - if (history.length === 0) - return; + if (history.length === 0) return; const entry = history[history.length - 1]; if (!isClose(x + token.w / 2, entry.x, 0.1) || !isClose(y + token.h / 2, entry.y, 0.1)) { return; @@ -92,14 +87,15 @@ export async function removeLastHistoryEntryIfAt(token, x, y) { history.pop(); const combat = game.combat; const combatant = combat.getCombatantByToken(token.id); - await updateCombatantDragRulerFlags(combat, [{_id: combatant.id, dragRulerFlags: combatant.data.flags.dragRuler}]); + await updateCombatantDragRulerFlags(combat, [ + {_id: combatant.id, dragRulerFlags: combatant.data.flags.dragRuler}, + ]); } export async function resetMovementHistory(combat, combatantId) { const combatant = combat.combatants.get(combatantId); const dragRulerFlags = combatant.data.flags.dragRuler; - if (!dragRulerFlags) - return; + if (!dragRulerFlags) return; dragRulerFlags.passedWaypoints = null; dragRulerFlags.trackedRound = null; dragRulerFlags.rulerState = null; diff --git a/js/pathfinding.js b/js/pathfinding.js index 46bf512..58d4d29 100644 --- a/js/pathfinding.js +++ b/js/pathfinding.js @@ -1,8 +1,19 @@ -import {getCenterFromGridPositionObj, getGridPositionFromPixelsObj, getPixelsFromGridPositionObj} from "./foundry_fixes.js"; +import { + getCenterFromGridPositionObj, + getGridPositionFromPixelsObj, + getPixelsFromGridPositionObj, +} from "./foundry_fixes.js"; import {moveWithoutAnimation, togglePathfinding} from "./keybindings.js"; import {debugGraphics} from "./main.js"; import {settingsKey} from "./settings.js"; -import {buildSnapPointTokenData, getSnapPointForTokenDataObj, getTokenShape, getTokenShapeForTokenData, isModuleActive, iterPairs} from "./util.js"; +import { + buildSnapPointTokenData, + getSnapPointForTokenDataObj, + getTokenShape, + getTokenShapeForTokenData, + isModuleActive, + iterPairs, +} from "./util.js"; import * as GridlessPathfinding from "../wasm/gridless_pathfinding.js"; import {PriorityQueueSet, ProcessOnceQueue} from "./data_structures.js"; @@ -60,8 +71,8 @@ class Cache { this.background = { nextJobId: null, nextTimeoutId: null, - nextAnimationFrameId: null - } + nextAnimationFrameId: null, + }; } clear() { @@ -98,8 +109,10 @@ class Cache { // Check if we already have the max number of layers. If we do, // get rid of the one that hasn't been used for the longest if (this.layers.size >= Cache.maxCacheLayers) { - const oldestCache = Array.from(this.layers.values()) - .reduce((layer1, layer2) => (layer1?.lastUsed < layer2.lastUsed) ? layer1 : layer2, null); + const oldestCache = Array.from(this.layers.values()).reduce( + (layer1, layer2) => (layer1?.lastUsed < layer2.lastUsed ? layer1 : layer2), + null, + ); this.layers.delete(oldestCache.cacheId); } @@ -119,7 +132,7 @@ class Cache { */ startBackgroundCaching(token) { const cacheLayer = this.getCacheLayer(token); - const tokenPosition = getGridPositionFromPixelsObj(token.position) + const tokenPosition = getGridPositionFromPixelsObj(token.position); cacheLayer.queue.push(cacheLayer.nodes[tokenPosition.y][tokenPosition.x]); @@ -137,8 +150,8 @@ class Cache { // Find the latest-used cache that has nodes left to cache const latestCache = this.getLatestCacheWithNonEmptyQueue(); if (latestCache) { - this.background.nextJobId = window.requestIdleCallback( - () => this.runBackgroundCache(latestCache) + this.background.nextJobId = window.requestIdleCallback(() => + this.runBackgroundCache(latestCache), ); this.resetAnimationFrameTimeout(); } @@ -152,13 +165,10 @@ class Cache { this.cancelTimeout(); this.cancelAnimationFrame(); - this.background.nextTimeoutId = window.setTimeout( - () => { - this.scheduleAnimationFrameCache(); - this.background.nextTimeoutId = null; - }, - Cache.backgroundCachingTimeoutMillis - ); + this.background.nextTimeoutId = window.setTimeout(() => { + this.scheduleAnimationFrameCache(); + this.background.nextTimeoutId = null; + }, Cache.backgroundCachingTimeoutMillis); } /** @@ -167,8 +177,8 @@ class Cache { scheduleAnimationFrameCache() { const latestCache = this.getLatestCacheWithNonEmptyQueue(); if (latestCache) { - this.background.nextAnimationFrameId = window.requestAnimationFrame( - () => this.runAnimationCache(latestCache) + this.background.nextAnimationFrameId = window.requestAnimationFrame(() => + this.runAnimationCache(latestCache), ); } } @@ -179,7 +189,7 @@ class Cache { getLatestCacheWithNonEmptyQueue() { return Array.from(this.layers.values()) .filter(layer => layer.queue.hasNext()) - .reduce((layer1, layer2) => (layer1?.lastUsed > layer2.lastUsed) ? layer1 : layer2, null); + .reduce((layer1, layer2) => (layer1?.lastUsed > layer2.lastUsed ? layer1 : layer2), null); } /** @@ -238,12 +248,9 @@ let gridlessPathfinders = new Map(); let gridWidth, gridHeight; export function isPathfindingEnabled() { - if (this.user !== game.user) - return false; - if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding")) - return false; - if (moveWithoutAnimation) - return false; + if (this.user !== game.user) return false; + if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding")) return false; + if (moveWithoutAnimation) return false; return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding; } @@ -253,7 +260,12 @@ export function findPath(from, to, token, previousWaypoints) { let pathfinder = gridlessPathfinders.get(tokenSize); if (!pathfinder) { let radiusMultiplier = game.settings.get(settingsKey, "pathfindingRadius"); - pathfinder = GridlessPathfinding.initialize(canvas.walls.placeables, tokenSize * radiusMultiplier, token.data.elevation, Boolean(game.modules.get("wall-height")?.active)); + pathfinder = GridlessPathfinding.initialize( + canvas.walls.placeables, + tokenSize * radiusMultiplier, + token.data.elevation, + Boolean(game.modules.get("wall-height")?.active), + ); gridlessPathfinders.set(tokenSize, pathfinder); } paintGridlessPathfindingDebug(pathfinder); @@ -261,30 +273,36 @@ export function findPath(from, to, token, previousWaypoints) { } else { const cacheLayer = cache.getCacheLayer(token); const firstNode = calculatePath(from, to, cacheLayer, previousWaypoints); - if (!firstNode) - return null; + if (!firstNode) return null; paintGriddedPathfindingDebug(firstNode, cacheLayer.tokenData); const path = []; let currentNode = firstNode; while (currentNode) { - if (path.length >= 2 && !stepCollidesWithWall(path[path.length - 2], currentNode.node, cacheLayer.tokenData)) { + if ( + path.length >= 2 && + !stepCollidesWithWall(path[path.length - 2], currentNode.node, cacheLayer.tokenData) + ) { // Replace last waypoint if the current waypoint leads to a valid path that isn't longer than the old path if (window.terrainRuler) { let startNode = getCenterFromGridPositionObj(path[path.length - 2]); let middleNode = getCenterFromGridPositionObj(path[path.length - 1]); let endNode = getCenterFromGridPositionObj(currentNode.node); - let oldPath = [{ray: new Ray(startNode, middleNode)}, {ray: new Ray(middleNode, endNode)}]; + let oldPath = [ + {ray: new Ray(startNode, middleNode)}, + {ray: new Ray(middleNode, endNode)}, + ]; let newPath = [{ray: new Ray(startNode, endNode)}]; let costFunction = buildCostFunction(token, getTokenShape(token)); // TODO Cache the used measurement for use in the next loop to improve performance - let oldDistance = terrainRuler.measureDistances(oldPath, {costFunction}).reduce((a, b) => a + b); + let oldDistance = terrainRuler + .measureDistances(oldPath, {costFunction}) + .reduce((a, b) => a + b); let newDistance = terrainRuler.measureDistances(newPath, {costFunction})[0]; // TODO We might need to check if the diagonal count has increased on 5-10-5 - if (newDistance < oldDistance) { + if (newDistance < oldDistance) { path.pop(); - } - else if (newDistance === oldDistance) { + } else if (newDistance === oldDistance) { let oldNoDiagonals = oldPath[1].ray.terrainRulerFinalState?.noDiagonals; let newNoDiagonals = newPath[0].ray.terrainRulerFinalState?.noDiagonals; // This uses === && < instead of <= because the variables might be undefined (which shall lead to a true result) @@ -292,8 +310,7 @@ export function findPath(from, to, token, previousWaypoints) { path.pop(); } } - } - else { + } else { path.pop(); } } @@ -323,18 +340,36 @@ function getNode(pos, cacheLayer, initialize = true) { const node = cacheLayer.nodes[pos.y][pos.x]; if (initialize && !node.edges) { node.edges = []; - for (const neighborPos of canvas.grid.grid.getNeighbors(pos.y, pos.x).map(([y, x]) => {return {x, y};})) { - if (neighborPos.x < 0 || neighborPos.y < 0 || neighborPos.x >= gridWidth || neighborPos.y >= gridHeight) { + for (const neighborPos of canvas.grid.grid.getNeighbors(pos.y, pos.x).map(([y, x]) => { + return {x, y}; + })) { + if ( + neighborPos.x < 0 || + neighborPos.y < 0 || + neighborPos.x >= gridWidth || + neighborPos.y >= gridHeight + ) { continue; } // TODO Work with pixels instead of grid locations if (!stepCollidesWithWall(pos, neighborPos, cacheLayer.tokenData)) { - const isDiagonal = node.x !== neighborPos.x && node.y !== neighborPos.y && canvas.grid.type === CONST.GRID_TYPES.SQUARE; + const isDiagonal = + node.x !== neighborPos.x && + node.y !== neighborPos.y && + canvas.grid.type === CONST.GRID_TYPES.SQUARE; let edgeCost; if (window.terrainRuler) { - let ray = new Ray(getCenterFromGridPositionObj(pos), getCenterFromGridPositionObj(neighborPos)); - let measuredDistance = terrainRuler.measureDistances([{ray}], {costFunction: buildCostFunction(cacheLayer.tokenData, getTokenShapeForTokenData(cacheLayer.tokenData))})[0]; + let ray = new Ray( + getCenterFromGridPositionObj(pos), + getCenterFromGridPositionObj(neighborPos), + ); + let measuredDistance = terrainRuler.measureDistances([{ray}], { + costFunction: buildCostFunction( + cacheLayer.tokenData, + getTokenShapeForTokenData(cacheLayer.tokenData), + ), + })[0]; edgeCost = Math.round(measuredDistance / canvas.dimensions.distance); if (ray.terrainRulerFinalState?.noDiagonals === 1) { edgeCost = 1.5; @@ -343,8 +378,7 @@ function getNode(pos, cacheLayer, initialize = true) { if (isDiagonal && edgeCost == 1) { edgeCost = 1.0001; } - } - else { + } else { // Count 5-10-5 diagonals as 1.5 (so two add up to 3) and 5-5-5 diagonals as 1.0001 (to discourage unnecessary diagonals) // TODO Account for difficult terrain edgeCost = isDiagonal ? (use5105 ? 1.5 : 1.0001) : 1; @@ -365,17 +399,18 @@ function calculatePath(from, to, cacheLayer, previousWaypoints) { startCost = (calcNoDiagonals(previousWaypoints) % 2) * 0.5; } - const nextNodes = new PriorityQueueSet((node1, node2) => node1.node === node2.node, node => node.estimated); + const nextNodes = new PriorityQueueSet( + (node1, node2) => node1.node === node2.node, + node => node.estimated, + ); const previousNodes = new Set(); - nextNodes.pushWithPriority( - { - node: getNode(from, cacheLayer), - cost: startCost, - estimated: startCost + estimateCost(from, to), - previous: null - } - ); + nextNodes.pushWithPriority({ + node: getNode(from, cacheLayer), + cost: startCost, + estimated: startCost + estimateCost(from, to), + previous: null, + }); while (nextNodes.hasNext()) { // Get node with cheapest estimate @@ -394,7 +429,7 @@ function calculatePath(from, to, cacheLayer, previousWaypoints) { node: neighborNode, cost: currentNode.cost + edge.cost, estimated: currentNode.cost + edge.cost + estimateCost(neighborNode, to), - previous: currentNode + previous: currentNode, }; nextNodes.pushWithPriority(neighbor); } @@ -412,8 +447,8 @@ function buildPathNodes(lastNode) { const pathNode = { node: currentNode.node, cost: currentNode.cost, - next: previousNode - } + next: previousNode, + }; previousNode = pathNode; currentNode = currentNode.previous; } @@ -444,7 +479,7 @@ function stepCollidesWithWall(from, to, tokenData) { if (isModuleActive("levels")) { stepStart.z = tokenData.elevation; stepEnd.z = tokenData.elevation; - return _levels.testCollision(stepStart, stepEnd, "collision") + return _levels.testCollision(stepStart, stepEnd, "collision"); } else { return canvas.walls.checkCollision(new Ray(stepStart, stepEnd)); } @@ -456,8 +491,7 @@ export function wipePathfindingCache() { GridlessPathfinding.free(pathfinder); } gridlessPathfinders.clear(); - if (debugGraphics) - debugGraphics.removeChildren().forEach(c => c.destroy()); + if (debugGraphics) debugGraphics.removeChildren().forEach(c => c.destroy()); } export function initializePathfinding() { @@ -467,22 +501,23 @@ export function initializePathfinding() { export function startBackgroundCaching(token) { // Background caching isn't yet supported for gridless scenes - if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) - return; + if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) return; if (game.user.isGM || game.settings.get(settingsKey, "allowPathfinding")) { cache.startBackgroundCaching(token); } } function paintGriddedPathfindingDebug(firstNode, tokenData) { - if (!CONFIG.debug.dragRuler) - return; + if (!CONFIG.debug.dragRuler) return; debugGraphics.removeChildren().forEach(c => c.destroy()); let currentNode = firstNode; while (currentNode) { let text = new PIXI.Text(currentNode.cost.toFixed(1)); - let pixels = getSnapPointForTokenDataObj(getPixelsFromGridPositionObj(currentNode.node), tokenData); + let pixels = getSnapPointForTokenDataObj( + getPixelsFromGridPositionObj(currentNode.node), + tokenData, + ); text.anchor.set(0.5, 1.0); text.x = pixels.x; text.y = pixels.y; @@ -492,8 +527,7 @@ function paintGriddedPathfindingDebug(firstNode, tokenData) { } function paintGridlessPathfindingDebug(pathfinder) { - if (!CONFIG.debug.dragRuler) - return; + if (!CONFIG.debug.dragRuler) return; debugGraphics.removeChildren().forEach(c => c.destroy()); let graphic = new PIXI.Graphics(); diff --git a/js/ruler.js b/js/ruler.js index 01c8c70..110dba8 100644 --- a/js/ruler.js +++ b/js/ruler.js @@ -1,6 +1,10 @@ -import {currentSpeedProvider, getColorForDistanceAndToken, getRangesFromSpeedProvider} from "./api.js"; +import { + currentSpeedProvider, + getColorForDistanceAndToken, + getRangesFromSpeedProvider, +} from "./api.js"; import {getHexSizeSupportTokenGridCenter} from "./compatibility.js"; -import {cancelScheduledMeasurement, measure} from "./foundry_imports.js" +import {cancelScheduledMeasurement, measure} from "./foundry_imports.js"; import {getMovementHistory} from "./movement_tracking.js"; import {settingsKey} from "./settings.js"; import {getSnapPointForEntity} from "./util.js"; @@ -8,7 +12,7 @@ import {getSnapPointForEntity} from "./util.js"; export function extendRuler() { class DragRulerRuler extends Ruler { // Functions below are overridden versions of functions in Ruler - constructor(user, {color=null}={}) { + constructor(user, {color = null} = {}) { super(user, {color}); this.previousWaypoints = []; this.previousLabels = this.addChild(new PIXI.Container()); @@ -24,8 +28,7 @@ export function extendRuler() { async moveToken(event) { // Disable moveToken if Drag Ruler is active - if (!this.isDragRuler) - return await super.moveToken(event); + if (!this.isDragRuler) return await super.moveToken(event); } toJSON() { @@ -45,30 +48,30 @@ export function extendRuler() { update(data) { // Don't show a GMs drag ruler to non GM players - if (data.draggedEntity && 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.draggedEntity) { - if (data.draggedEntityIsToken) - this.draggedEntity = canvas.tokens.get(data.draggedEntity); - else - this.draggedEntity = canvas.templates.get(data.draggedEntity); - } - else { + if (data.draggedEntityIsToken) this.draggedEntity = canvas.tokens.get(data.draggedEntity); + else this.draggedEntity = canvas.templates.get(data.draggedEntity); + } else { this.draggedEntity = undefined; } super.update(data); } - measure(destination, options={}) { + measure(destination, options = {}) { if (this.isDragRuler) { // If this is the ruler of a remote user take the waypoints as they were transmitted and don't apply any additional snapping to them - if (this.user !== game.user) - options.snap = false; + if (this.user !== game.user) options.snap = false; return measure.call(this, destination, options); - } - else { + } else { return super.measure(destination, options); } } @@ -79,18 +82,20 @@ export function extendRuler() { } // The functions below aren't present in the orignal Ruler class and are added by Drag Ruler - dragRulerAddWaypoint(point, options={}) { + dragRulerAddWaypoint(point, options = {}) { options.snap = options.snap ?? true; if (options.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)); - this.waypoints.filter(waypoint => waypoint.isPathfinding).forEach(waypoint => waypoint.isPathfinding = false); + this.waypoints + .filter(waypoint => waypoint.isPathfinding) + .forEach(waypoint => (waypoint.isPathfinding = false)); } dragRulerAddWaypointHistory(waypoints) { - waypoints.forEach(waypoint => waypoint.isPrevious = true); + waypoints.forEach(waypoint => (waypoint.isPrevious = true)); this.waypoints = this.waypoints.concat(waypoints); for (const waypoint of waypoints) { this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); @@ -102,30 +107,51 @@ export function extendRuler() { this.labels.removeChildren().forEach(c => c.destroy()); } - dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) { + dragRulerDeleteWaypoint( + event = { + preventDefault: () => { + return; + }, + }, + options = {}, + ) { this.dragRulerRemovePathfindingWaypoints(); options.snap = options.snap ?? true; if (this.waypoints.filter(w => !w.isPrevious).length > 1) { event.preventDefault(); - const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens); + const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition( + canvas.tokens, + ); const rulerOffset = this.rulerOffset; // 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); + this._removeWaypoint( + {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}, + options, + ); game.user.broadcastActivity({ruler: this}); - } - else { + } else { this.dragRulerAbortDrag(event); } } dragRulerRemovePathfindingWaypoints() { - this.waypoints.filter(waypoint => waypoint.isPathfinding).forEach(_ => this.labels.removeChild(this.labels.children[this.labels.children.length - 1]).destroy()); + this.waypoints + .filter(waypoint => waypoint.isPathfinding) + .forEach(_ => + this.labels.removeChild(this.labels.children[this.labels.children.length - 1]).destroy(), + ); this.waypoints = this.waypoints.filter(waypoint => !waypoint.isPathfinding); } - dragRulerAbortDrag(event={preventDefault: () => {return}}) { + dragRulerAbortDrag( + event = { + preventDefault: () => { + return; + }, + }, + ) { const token = this.draggedEntity; this._endMeasurement(); @@ -139,10 +165,8 @@ export function extendRuler() { } async dragRulerRecalculate(tokenIds) { - if (this._state !== Ruler.STATES.MEASURING) - return; - if (tokenIds && !tokenIds.includes(this.draggedEntity.id)) - return; + 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")) @@ -155,58 +179,71 @@ export function extendRuler() { } static dragRulerGetRaysFromWaypoints(waypoints, destination) { - if ( destination ) - waypoints = waypoints.concat([destination]); + if (destination) waypoints = waypoints.concat([destination]); return waypoints.slice(1).map((wp, i) => { - const ray = new Ray(waypoints[i], wp); + const ray = new Ray(waypoints[i], wp); ray.isPrevious = Boolean(waypoints[i].isPrevious); return ray; }); } dragRulerGetColorForDistance(distance) { - if (!this.isDragRuler) - return this.color; + if (!this.isDragRuler) return this.color; if (!this.draggedEntity.actor) { return this.color; } // Don't apply colors if the current user doesn't have at least observer permissions if (this.draggedEntity.actor.permission < 2) { // If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway - if (!(this.draggedEntity.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs"))) + if ( + !( + this.draggedEntity.actor.data.type === "character" && + game.settings.get(settingsKey, "alwaysShowSpeedForPCs") + ) + ) return this.color; } distance = Math.round(distance * 100) / 100; if (!this.dragRulerRanges) this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedEntity); - return getColorForDistanceAndToken(distance, this.draggedEntity, this.dragRulerRanges) ?? this.color; + return ( + getColorForDistanceAndToken(distance, this.draggedEntity, this.dragRulerRanges) ?? + this.color + ); } - dragRulerStart(options, measureImmediately=true) { + dragRulerStart(options, measureImmediately = true) { const entity = this.draggedEntity; const isToken = entity instanceof Token; - if (isToken && !currentSpeedProvider.usesRuler(entity)) - return; + if (isToken && !currentSpeedProvider.usesRuler(entity)) return; const ruler = canvas.controls.ruler; ruler.clear(); ruler._state = Ruler.STATES.STARTING; let entityCenter; - if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(entity)) + if ( + isToken && + canvas.grid.isHex && + game.modules.get("hex-size-support")?.active && + CONFIG.hexSizeSupport.getAltSnappingFlag(entity) + ) entityCenter = getHexSizeSupportTokenGridCenter(entity); - else - entityCenter = entity.center; + else entityCenter = entity.center; if (isToken && game.settings.get(settingsKey, "enableMovementHistory")) ruler.dragRulerAddWaypointHistory(getMovementHistory(entity)); ruler.dragRulerAddWaypoint(entityCenter, {snap: false}); - const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens); - const destination = {x: mousePosition.x + ruler.rulerOffset.x, y: mousePosition.y + ruler.rulerOffset.y}; - if (measureImmediately) - ruler.measure(destination, options); + const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition( + canvas.tokens, + ); + const destination = { + x: mousePosition.x + ruler.rulerOffset.x, + y: mousePosition.y + ruler.rulerOffset.y, + }; + if (measureImmediately) ruler.measure(destination, options); } dragRulerSendState() { game.user.broadcastActivity({ - ruler: this.toJSON() + ruler: this.toJSON(), }); } } diff --git a/js/settings.js b/js/settings.js index f4293f2..4b5a64a 100644 --- a/js/settings.js +++ b/js/settings.js @@ -1,14 +1,19 @@ -import {availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider} from "./api.js"; -import {SpeedProvider} from "./speed_provider.js" -import {wipePathfindingCache} from "./pathfinding.js" -import { early_isGM } from "./util.js"; +import { + availableSpeedProviders, + currentSpeedProvider, + getDefaultSpeedProvider, + updateSpeedProvider, +} from "./api.js"; +import {SpeedProvider} from "./speed_provider.js"; +import {wipePathfindingCache} from "./pathfinding.js"; +import {early_isGM} from "./util.js"; export const settingsKey = "drag-ruler"; export const RightClickAction = Object.freeze({ CREATE_WAYPOINT: 0, DELETE_WAYPOINT: 1, - ABORT_DRAG:2, + ABORT_DRAG: 2, }); function delayedReload() { @@ -20,14 +25,14 @@ export function registerSettings() { scope: "world", config: false, type: String, - default: "fresh install" - }) + default: "fresh install", + }); game.settings.register(settingsKey, "clientDataVersion", { scope: "client", config: false, type: String, - default: "fresh install" + default: "fresh install", }); game.settings.register(settingsKey, "rightClickAction", { @@ -68,7 +73,7 @@ export function registerSettings() { config: true, type: Boolean, default: true, - }) + }); game.settings.register(settingsKey, "showGMRulerToPlayers", { name: "drag-ruler.settings.showGMRulerToPlayers.name", @@ -77,7 +82,7 @@ export function registerSettings() { config: true, type: Boolean, default: true, - }) + }); game.settings.register(settingsKey, "enableMovementHistory", { name: "drag-ruler.settings.enableMovementHistory.name", @@ -134,7 +139,7 @@ export function registerSettings() { type: String, default: getDefaultSpeedProvider(), onChange: updateSpeedProvider, - }) + }); game.settings.registerMenu(settingsKey, "speedProviderSettings", { name: "drag-ruler.settings.speedProviderSettings.name", @@ -143,7 +148,7 @@ export function registerSettings() { icon: "fas fa-tachometer-alt", type: SpeedProviderSettings, restricted: false, - }) + }); } class SpeedProviderSettings extends FormApplication { @@ -153,43 +158,44 @@ class SpeedProviderSettings extends FormApplication { title: game.i18n.localize("drag-ruler.settings.speedProviderSettings.windowTitle"), template: "modules/drag-ruler/templates/speed_provider_settings.html", width: 600, - }) + }); } - getData(options={}) { - const data = {} - data.isGM = game.user.isGM - const selectedProvider = currentSpeedProvider.id + getData(options = {}) { + const data = {}; + data.isGM = game.user.isGM; + const selectedProvider = currentSpeedProvider.id; // Insert all speed providers into the template data data.providers = Object.values(availableSpeedProviders).map(speedProvider => { - const provider = {} - provider.id = speedProvider.id - provider.hasSettings = speedProvider instanceof SpeedProvider - if (provider.hasSettings) - provider.settings = enumerateProviderSettings(speedProvider) - let dotPosition = provider.id.indexOf(".") - if (dotPosition === -1) - dotPosition = provider.id.length - const type = provider.id.substring(0, dotPosition) - const id = provider.id.substring(dotPosition + 1) + const provider = {}; + provider.id = speedProvider.id; + provider.hasSettings = speedProvider instanceof SpeedProvider; + if (provider.hasSettings) provider.settings = enumerateProviderSettings(speedProvider); + let dotPosition = provider.id.indexOf("."); + if (dotPosition === -1) dotPosition = provider.id.length; + const type = provider.id.substring(0, dotPosition); + const id = provider.id.substring(dotPosition + 1); if (type === "native") { - provider.selectTitle = game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.choices.native") - } - else { - let name + provider.selectTitle = game.i18n.localize( + "drag-ruler.settings.speedProviderSettings.speedProvider.choices.native", + ); + } else { + let name; if (type === "module") { - name = game.modules.get(id).data.title + name = game.modules.get(id).data.title; + } else { + name = game.system.data.title; } - else { - name = game.system.data.title - } - provider.selectTitle = game.i18n.format(`drag-ruler.settings.speedProviderSettings.speedProvider.choices.${type}`, {name}) + provider.selectTitle = game.i18n.format( + `drag-ruler.settings.speedProviderSettings.speedProvider.choices.${type}`, + {name}, + ); } - provider.isSelected = provider.id === selectedProvider - return provider - }) - data.selectedProviderName = data.providers.find(provider => provider.isSelected).selectTitle + provider.isSelected = provider.id === selectedProvider; + return provider; + }); + data.selectedProviderName = data.providers.find(provider => provider.isSelected).selectTitle; data.providerSelection = { id: "speedProvider", @@ -197,122 +203,126 @@ class SpeedProviderSettings extends FormApplication { hint: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.hint"), type: String, choices: data.providers.reduce((choices, provider) => { - choices[provider.id] = provider.selectTitle - return choices + choices[provider.id] = provider.selectTitle; + return choices; }, {}), value: selectedProvider, isCheckbox: false, isSelect: true, isRange: false, - } - return data + }; + return data; } async _updateObject(event, formData) { - const selectedSpeedProvider = game.user.isGM ? formData.speedProvider : game.settings.get(settingsKey, "speedProvider") + const selectedSpeedProvider = game.user.isGM + ? formData.speedProvider + : game.settings.get(settingsKey, "speedProvider"); for (let [key, value] of Object.entries(formData)) { // Check if this is color, convert the value to an integer - const splitKey = key.split(".", 3) - if (splitKey[0] !== "native") - splitKey.shift() + const splitKey = key.split(".", 3); + if (splitKey[0] !== "native") splitKey.shift(); if (splitKey.length >= 2 && splitKey[1] == "color") { - value = parseInt(value.substring(1), 16) + value = parseInt(value.substring(1), 16); } // Don't change settings for speed providers that aren't currently active - if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider)) - continue + if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider)) continue; // Get the key for the current setting - let setting - if (key === "speedProvider") - setting = "speedProvider" - else - setting = `speedProviders.${key}` + let setting; + if (key === "speedProvider") setting = "speedProvider"; + else setting = `speedProviders.${key}`; // Get the old setting value - const oldValue = game.settings.get(settingsKey, setting) + const oldValue = game.settings.get(settingsKey, setting); // Only update the setting if it has been changed (this leaves the default in place if it hasn't been touched) - if (value !== oldValue) - game.settings.set(settingsKey, setting, value) + if (value !== oldValue) game.settings.set(settingsKey, setting, value); } // Activate the configured speed provider - updateSpeedProvider() + updateSpeedProvider(); } activateListeners(html) { - super.activateListeners(html) - html.find("select[name=speedProvider]").change(this.onSpeedProviderChange.bind(this)) + super.activateListeners(html); + html.find("select[name=speedProvider]").change(this.onSpeedProviderChange.bind(this)); } onSpeedProviderChange(event) { // Hide all module settings - document.querySelectorAll(".drag-ruler-provider-settings").forEach(element => element.style.display = "none") + document + .querySelectorAll(".drag-ruler-provider-settings") + .forEach(element => (element.style.display = "none")); // Show the settings block for the currently selected module - document.getElementById(`drag-ruler.provider.${event.currentTarget.value}`).style.display = "" + document.getElementById(`drag-ruler.provider.${event.currentTarget.value}`).style.display = ""; // Recalculate window height - this.element[0].style.height = null - this.position.height = undefined + this.element[0].style.height = null; + this.position.height = undefined; } } function toDomHex(value) { - const hex = value.toString(16) - return "#" + "0".repeat(Math.max(0, 6 - hex.length)) + hex + const hex = value.toString(16); + return "#" + "0".repeat(Math.max(0, 6 - hex.length)) + hex; } function enumerateProviderSettings(provider) { - const colorSettings = [] - const unreachableColor = {id: "unreachable", name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name"} + const colorSettings = []; + const unreachableColor = { + id: "unreachable", + name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name", + }; // Resolve settings for the colors for (const color of provider.colors.concat([unreachableColor])) { // Localize the name, if avaliable. If no name is available use the id as name - const colorName = color.name ? game.i18n.localize(color.name) : color.id - let hint + const colorName = color.name ? game.i18n.localize(color.name) : color.id; + let hint; if (color === unreachableColor) - hint = game.i18n.localize("drag-ruler.settings.speedProviderSettings.color.unreachable.hint") + hint = game.i18n.localize("drag-ruler.settings.speedProviderSettings.color.unreachable.hint"); else - hint = game.i18n.format("drag-ruler.settings.speedProviderSettings.color.hint", {colorName}) + hint = game.i18n.format("drag-ruler.settings.speedProviderSettings.color.hint", {colorName}); colorSettings.push({ id: `${provider.id}.color.${color.id}`, name: game.i18n.format("drag-ruler.settings.speedProviderSettings.color.name", {colorName}), hint: hint, type: Number, - value: toDomHex(game.settings.get(settingsKey, `speedProviders.${provider.id}.color.${color.id}`)), + value: toDomHex( + game.settings.get(settingsKey, `speedProviders.${provider.id}.color.${color.id}`), + ), isCheckbox: false, isSelect: false, isRange: false, isColor: true, - }) + }); } // Prepare regular settings - const settings = [] + const settings = []; for (const setting of provider.settings) { try { - if (setting.scope === "world" && !game.user.isGM) - continue - const s = duplicate(setting) - s.id = `${provider.id}.setting.${s.id}` - s.name = game.i18n.localize(s.name) - s.hint = game.i18n.localize(s.hint) - s.value = provider.getSetting(setting.id) - s.type = setting.type instanceof Function ? setting.type.name : "String" - s.isCheckbox = setting.type === Boolean - s.isSelect = s.choices !== undefined - s.isRange = (setting.type === Number) && s.range - s.isColor = false - settings.push(s) - } - catch (e) { - console.warn(`Drag Ruler | The following error occured while rendering setting "${setting.id}" of module/system "${this.id}. It won't be displayed.`) - console.error(e) + if (setting.scope === "world" && !game.user.isGM) continue; + const s = duplicate(setting); + s.id = `${provider.id}.setting.${s.id}`; + s.name = game.i18n.localize(s.name); + s.hint = game.i18n.localize(s.hint); + s.value = provider.getSetting(setting.id); + s.type = setting.type instanceof Function ? setting.type.name : "String"; + s.isCheckbox = setting.type === Boolean; + s.isSelect = s.choices !== undefined; + s.isRange = setting.type === Number && s.range; + s.isColor = false; + settings.push(s); + } catch (e) { + console.warn( + `Drag Ruler | The following error occured while rendering setting "${setting.id}" of module/system "${this.id}. It won't be displayed.`, + ); + console.error(e); } } - return settings.concat(colorSettings) + return settings.concat(colorSettings); } diff --git a/js/socket.js b/js/socket.js index 47fc178..bb17ee5 100644 --- a/js/socket.js +++ b/js/socket.js @@ -11,8 +11,13 @@ Hooks.once("socketlib.ready", () => { 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.combatants.get(update._id).token.id)))); + return socket + .executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates) + .then(() => + currentSpeedProvider.onMovementHistoryUpdate( + updates.map(update => canvas.tokens.get(combat.combatants.get(update._id).token.id)), + ), + ); } async function _socketUpdateCombatantDragRulerFlags(combatId, updates) { @@ -21,12 +26,15 @@ async function _socketUpdateCombatantDragRulerFlags(combatId, updates) { const requestedUpdates = updates.length; updates = updates.filter(update => { const actor = combat.combatants.get(update._id).actor; - if (!actor) - return false; + if (!actor) return false; 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`); + 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`, + ); } updates = updates.map(update => { return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}}; @@ -40,6 +48,5 @@ export function recalculate(tokens) { function _socketRecalculate(tokenIds) { const ruler = canvas.controls.ruler; - if (ruler.isDragRuler) - ruler.dragRulerRecalculate(tokenIds); + if (ruler.isDragRuler) ruler.dragRulerRecalculate(tokenIds); } diff --git a/js/speed_provider.js b/js/speed_provider.js index 78e1e17..a3b32de 100644 --- a/js/speed_provider.js +++ b/js/speed_provider.js @@ -1,5 +1,5 @@ -import {settingsKey} from "./settings.js" -import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js" +import {settingsKey} from "./settings.js"; +import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js"; /** * Base class for all speed providers. @@ -19,7 +19,7 @@ export class SpeedProvider { * Implementing this method is required for all speed providers */ get colors() { - throw new Error("A SpeedProvider must implement the colors function") + throw new Error("A SpeedProvider must implement the colors function"); } /** @@ -31,7 +31,7 @@ export class SpeedProvider { * Implementing this method is required for all speed providers */ getRanges(token) { - throw new Error("A SpeedProvider must implement the getRanges function") + throw new Error("A SpeedProvider must implement the getRanges function"); } /** @@ -44,7 +44,7 @@ export class SpeedProvider { * Implementing this method is optional and only needs to be done if you want to provide custom provider settings */ get settings() { - return [] + return []; } /** @@ -53,7 +53,7 @@ export class SpeedProvider { * Implementing this method is optional and only needs to be done if you want to provide a custom default for that color. */ get defaultUnreachableColor() { - return 0xFF0000 + return 0xff0000; } /** @@ -69,12 +69,12 @@ export class SpeedProvider { * * 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, options={}) { + getCostForStep(token, area, options = {}) { // Lookup the cost for each square occupied by the token options.token = token; const costs = area.map(space => terrainRuler.getCost(space.x, space.y, options)); // Return the maximum of the costs - return costs.reduce((max, current) => Math.max(max, current)) + return costs.reduce((max, current) => Math.max(max, current)); } /** @@ -86,7 +86,7 @@ export class SpeedProvider { * Implementing this method is optional and only needs to be done if you want to disable Drag Ruler for some tokens. */ usesRuler(token) { - return true + return true; } /** @@ -104,13 +104,14 @@ export class SpeedProvider { */ getSetting(settingId) { try { - return game.settings.get(settingsKey, `speedProviders.${this.id}.setting.${settingId}`) - } - catch (e) { + return game.settings.get(settingsKey, `speedProviders.${this.id}.setting.${settingId}`); + } catch (e) { if (this.settings.some(setting => setting.id === settingId)) { - throw e + throw e; } - throw new Error(`Drag Ruler | "${settingId}" is not a registered setting for "${this.id}". If you're the module/system developer, please add it to the return values of your Speed Providers "get settings()" function.`) + throw new Error( + `Drag Ruler | "${settingId}" is not a registered setting for "${this.id}". If you're the module/system developer, please add it to the return values of your Speed Providers "get settings()" function.`, + ); } } @@ -120,32 +121,34 @@ export class SpeedProvider { * This function should neither be called or overridden by speed provider implementations */ constructor(id) { - this.id = id + this.id = id; } } - export class GenericSpeedProvider extends SpeedProvider { get colors() { return [ - {id: "walk", default: 0x00FF00, name: "drag-ruler.genericSpeedProvider.speeds.walk"}, - {id: "dash", default: 0xFFFF00, name: "drag-ruler.genericSpeedProvider.speeds.dash"} - ] + {id: "walk", default: 0x00ff00, name: "drag-ruler.genericSpeedProvider.speeds.walk"}, + {id: "dash", default: 0xffff00, name: "drag-ruler.genericSpeedProvider.speeds.dash"}, + ]; } getRanges(token) { - const speedAttribute = this.getSetting("speedAttribute") - if (!speedAttribute) - return [] + const speedAttribute = this.getSetting("speedAttribute"); + if (!speedAttribute) return []; const tokenSpeed = parseFloat(getProperty(token, speedAttribute)); if (tokenSpeed === undefined) { - console.warn(`Drag Ruler (Generic Speed Provider) | The configured token speed attribute "${speedAttribute}" didn't return a speed value. To use colors based on drag distance set the setting to the correct value (or clear the box to disable this feature).`) - return [] + console.warn( + `Drag Ruler (Generic Speed Provider) | The configured token speed attribute "${speedAttribute}" didn't return a speed value. To use colors based on drag distance set the setting to the correct value (or clear the box to disable this feature).`, + ); + return []; } - const dashMultiplier = this.getSetting("dashMultiplier") - if (!dashMultiplier) - return [{range: tokenSpeed, color: "walk"}] - return [{range: tokenSpeed, color: "walk"}, {range: tokenSpeed * dashMultiplier, color: "dash"}] + const dashMultiplier = this.getSetting("dashMultiplier"); + if (!dashMultiplier) return [{range: tokenSpeed, color: "walk"}]; + return [ + {range: tokenSpeed, color: "walk"}, + {range: tokenSpeed * dashMultiplier, color: "dash"}, + ]; } get settings() { @@ -167,7 +170,7 @@ export class GenericSpeedProvider extends SpeedProvider { config: true, type: Number, default: getDefaultDashMultiplier(), - } - ] + }, + ]; } } diff --git a/js/systems.js b/js/systems.js index 6548da4..63fc4e3 100644 --- a/js/systems.js +++ b/js/systems.js @@ -24,13 +24,13 @@ export function getDefaultSpeedAttribute() { case "splittermond": return "actor.derivedValues.speed.value"; } - return "" + return ""; } export function getDefaultDashMultiplier() { switch (game.system.id) { case "swade": - return 0 + return 0; case "dcc": case "dnd4e": case "dnd5e": diff --git a/js/util.js b/js/util.js index c2c1bfe..975209a 100644 --- a/js/util.js +++ b/js/util.js @@ -1,10 +1,10 @@ -import {getPixelsFromGridPosition} from "./foundry_fixes.js" +import {getPixelsFromGridPosition} from "./foundry_fixes.js"; import {findVertexSnapPoint} from "./hex_support.js"; import {disableSnap} from "./keybindings.js"; export function* zip(it1, it2) { - for (let i = 0;i < Math.min(it1.length, it2.length);i++) { - yield [it1[i], it2[i]] + for (let i = 0; i < Math.min(it1.length, it2.length); i++) { + yield [it1[i], it2[i]]; } } @@ -17,7 +17,7 @@ export function* enumeratedZip(it1, it2) { } export function* iterPairs(l) { - for (let i = 1;i < l.length;i++) { + for (let i = 1; i < l.length; i++) { yield [l[i - 1], l[i]]; } } @@ -29,13 +29,14 @@ export function sum(arr) { export function buildSnapPointTokenData(token) { const tokenData = { width: token.data.width, - height: token.data.height + height: token.data.height, }; if (isModuleActive("hex-size-support")) { tokenData.hexSizeSupport = {}; tokenData.hexSizeSupport.altSnappingFlag = CONFIG.hexSizeSupport.getAltSnappingFlag(token); - tokenData.hexSizeSupport.altOrientationFlag = CONFIG.hexSizeSupport.getAltOrientationFlag(token); + tokenData.hexSizeSupport.altOrientationFlag = + CONFIG.hexSizeSupport.getAltOrientationFlag(token); tokenData.hexSizeSupport.borderSize = token.document.getFlag("hex-size-support", "borderSize"); } @@ -58,27 +59,21 @@ function getSnapPointForTokenData(x, y, tokenData) { if (tokenData.hexSizeSupport?.altSnappingFlag) { if (tokenData.hexSizeSupport.borderSize % 2 === 0) { const snapPoint = findVertexSnapPoint(x, y, tokenData.hexSizeSupport.altOrientationFlag); - return new PIXI.Point(snapPoint.x, snapPoint.y) + return new PIXI.Point(snapPoint.x, snapPoint.y); + } else { + return new PIXI.Point(...canvas.grid.getCenter(x, y)); } - else { - return new PIXI.Point(...canvas.grid.getCenter(x, y)) - } - } - else { + } else { return new PIXI.Point(...canvas.grid.getCenter(x, y)); } } const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y); let cellX, cellY; - if (tokenData.width % 2 === 0) - cellX = x - canvas.grid.h / 2; - else - cellX = x; - if (tokenData.height % 2 === 0) - cellY = y - canvas.grid.h / 2; - else - cellY = y; + if (tokenData.width % 2 === 0) cellX = x - canvas.grid.h / 2; + else cellX = x; + if (tokenData.height % 2 === 0) cellY = y - canvas.grid.h / 2; + else cellY = y; const [centerX, centerY] = canvas.grid.getCenter(cellX, cellY); let snapX, snapY; // Tiny tokens can snap to the cells corners @@ -101,11 +96,9 @@ function getSnapPointForTokenData(x, y, tokenData) { const subGridHeight = Math.floor(canvas.grid.h / 2); const subGridPosY = Math.floor(offsetY / subGridHeight); snapY = topLeftY + (subGridPosY + 0.5) * subGridHeight; - } - else if (Math.round(tokenData.height) % 2 === 1 || tokenData.height < 1) { + } else if (Math.round(tokenData.height) % 2 === 1 || tokenData.height < 1) { snapY = centerY; - } - else { + } else { snapY = centerY + canvas.grid.h / 2; } return new PIXI.Point(snapX, snapY); @@ -124,16 +117,13 @@ export function getSnapPointForMeasuredTemplate(x, y) { export function getSnapPointForEntity(x, y, entity) { const isToken = entity instanceof Token; - if (isToken) - return getSnapPointForToken(x, y, entity); - else - return getSnapPointForMeasuredTemplate(x, y); + 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 ) - return false; + if (!layer) return false; const area = getAreaFromPositionAndShape(position, shape); for (const space of area) { const [x, y] = getPixelsFromGridPosition(space.x, space.y); @@ -147,22 +137,19 @@ export function getAreaFromPositionAndShape(position, shape) { let y = position.y + space.y; if (canvas.grid.isHex) { let shiftedRow; - if (canvas.grid.grid.options.even) - shiftedRow = 1 - else - shiftedRow = 0 + if (canvas.grid.grid.options.even) shiftedRow = 1; + else shiftedRow = 0; if (canvas.grid.grid.options.columns) { if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) { y += 1; } - } - else { + } else { if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) { x += 1; } } } - return {x, y} + return {x, y}; }); } @@ -170,40 +157,53 @@ export function getTokenShape(token) { return getTokenShapeForTokenData(buildSnapPointTokenData(token), token.scene); } -export function getTokenShapeForTokenData(tokenData, scene=canvas.scene) { +export function getTokenShapeForTokenData(tokenData, scene = canvas.scene) { if (scene.data.gridType === CONST.GRID_TYPES.GRIDLESS) { - return [{x: 0, y: 0}] - } - else if (scene.data.gridType === CONST.GRID_TYPES.SQUARE) { - const topOffset = -Math.floor(tokenData.height / 2) - const leftOffset = -Math.floor(tokenData.width / 2) - const shape = [] - for (let y = 0;y < tokenData.height;y++) { - for (let x = 0;x < tokenData.width;x++) { - shape.push({x: x + leftOffset, y: y + topOffset}) + return [{x: 0, y: 0}]; + } else if (scene.data.gridType === CONST.GRID_TYPES.SQUARE) { + const topOffset = -Math.floor(tokenData.height / 2); + const leftOffset = -Math.floor(tokenData.width / 2); + const shape = []; + for (let y = 0; y < tokenData.height; y++) { + for (let x = 0; x < tokenData.width; x++) { + shape.push({x: x + leftOffset, y: y + topOffset}); } } - return shape - } - else { + return shape; + } else { // Hex grids if (game.modules.get("hex-size-support")?.active && tokenData.hexSizeSupport.altSnappingFlag) { const borderSize = tokenData.hexSizeSupport.borderSize; let shape = [{x: 0, y: 0}]; if (borderSize >= 2) - shape = shape.concat([{x: 0, y: -1}, {x: -1, y: -1}]); + shape = shape.concat([ + {x: 0, y: -1}, + {x: -1, y: -1}, + ]); if (borderSize >= 3) - shape = shape.concat([{x: 0, y: 1}, {x: -1, y: 1}, {x: -1, y: 0}, {x: 1, y: 0}]); + shape = shape.concat([ + {x: 0, y: 1}, + {x: -1, y: 1}, + {x: -1, y: 0}, + {x: 1, y: 0}, + ]); if (borderSize >= 4) - shape = shape.concat([{x: -2, y: -1}, {x: 1, y: -1}, {x: -1, y: -2}, {x: 0, y: -2}, {x: 1, y: -2}]) + shape = shape.concat([ + {x: -2, y: -1}, + {x: 1, y: -1}, + {x: -1, y: -2}, + {x: 0, y: -2}, + {x: 1, y: -2}, + ]); if (Boolean(tokenData.hexSizeSupport.altOrientationFlag) !== canvas.grid.grid.options.columns) - shape.forEach(space => space.y *= -1); + shape.forEach(space => (space.y *= -1)); if (canvas.grid.grid.options.columns) - shape = shape.map(space => {return {x: space.y, y: space.x}}); + shape = shape.map(space => { + return {x: space.y, y: space.x}; + }); return shape; - } - else { + } else { return [{x: 0, y: 0}]; } } @@ -213,11 +213,10 @@ export function getTokenSize(token) { let w, h; const hexSizeSupportBorderSize = token.data.flags["hex-size-support"]?.borderSize; if (hexSizeSupportBorderSize > 0) { - w = h = hexSizeSupportBorderSize - } - else { - w = token.data.width - h = token.data.height + w = h = hexSizeSupportBorderSize; + } else { + w = token.data.width; + h = token.data.height; } return {w, h}; } @@ -226,7 +225,7 @@ export function getTokenSize(token) { // This function applies an offset to to the waypoints that will move the ruler from the edge to the center of the cell export function applyTokenSizeOffset(waypoints, token) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { - return waypoints + return waypoints; } const tokenSize = getTokenSize(token); @@ -237,21 +236,17 @@ export function applyTokenSizeOffset(waypoints, token) { if (canvas.grid.grid.options.columns) { if (tokenSize.w % 2 === 0) { waypointOffset.x = canvas.grid.w / 2; - if (!isAltOrientation) - waypointOffset.x *= -1; + if (!isAltOrientation) waypointOffset.x *= -1; } - } - else { + } else { if (tokenSize.h % 2 === 0) { waypointOffset.y = canvas.grid.h / 2; - if (isAltOrientation) - waypointOffset.y *= -1; + if (isAltOrientation) waypointOffset.y *= -1; } } } // If hex size support isn't active leave the waypoints like they are - } - else { + } else { if (tokenSize.w % 2 === 0) { waypointOffset.x = canvas.grid.w / 2; } @@ -260,7 +255,7 @@ 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, options) { @@ -269,8 +264,7 @@ export function setSnapParameterOnOptions(sourceObject, options) { options.snapOverrideActive = true; options.snap = sourceObject.snapOverride.snap; sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues - } - else { + } else { options.snap = !disableSnap; } } @@ -280,7 +274,9 @@ export function isClose(a, b, delta) { } export function getMeasurePosition() { - const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens); + const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition( + canvas.tokens, + ); const rulerOffset = canvas.controls.ruler.rulerOffset; const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}; return measurePosition; @@ -294,5 +290,5 @@ export function early_isGM() { } export function isModuleActive(moduleName) { - return game.modules.get(moduleName)?.active + return game.modules.get(moduleName)?.active; }