|
|
- "use strict";
-
- import {
- getColorForDistanceAndToken,
- getMovedDistanceFromToken,
- getRangesFromSpeedProvider,
- initApi,
- registerModule,
- registerSystem,
- } from "./api.js";
- import {checkDependencies} from "./compatibility.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 {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
- import {extendRuler} from "./ruler.js";
- import {registerSettings, RightClickAction, settingsKey} from "./settings.js";
- import {recalculate} from "./socket.js";
- import {SpeedProvider} from "./speed_provider.js";
- import {getEntityCenter, setSnapParameterOnOptions} from "./util.js";
-
- CONFIG.debug.dragRuler = false;
- export let debugGraphics = undefined;
-
- Hooks.once("init", () => {
- registerSettings();
- registerKeybindings();
- initApi();
- hookDragHandlers(Token);
- hookDragHandlers(MeasuredTemplate);
- libWrapper.register(
- "drag-ruler",
- "TokenLayer.prototype.undoHistory",
- tokenLayerUndoHistory,
- "WRAPPER",
- );
-
- extendRuler();
-
- window.dragRuler = {
- getRangesFromSpeedProvider,
- getColorForDistanceAndToken,
- getMovedDistanceFromToken,
- registerModule,
- registerSystem,
- recalculate,
- resetMovementHistory,
- };
- });
-
- Hooks.once("ready", () => {
- performMigrations();
- checkDependencies();
- 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 => {
- ruler.draggedEntity = null;
- 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: '<i class="fas fa-undo-alt"></i>',
- callback: li => resetMovementHistory(ui.combat.viewed, li.data("combatant-id")),
- };
- menu.splice(1, 0, entry);
- });
-
- function forwardIfUnahndled(newFn) {
- return function (oldFn, ...args) {
- const eventHandled = newFn(...args);
- if (!eventHandled) oldFn(...args);
- };
- }
-
- function hookDragHandlers(entityType) {
- 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",
- );
- 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",
- );
- }
-
- async function tokenLayerUndoHistory(wrapped) {
- const historyEntry = this.history[this.history.length - 1];
- const returnValue = await wrapped();
- if (historyEntry.type === "update") {
- for (const entry of historyEntry.data) {
- const token = canvas.tokens.get(entry._id);
- removeLastHistoryEntryIfAt(token, entry.x, entry.y);
- }
- }
- return returnValue;
- }
-
- function onEntityLeftDragStart(wrapped, event) {
- wrapped(event);
- const isToken = this instanceof Token;
- const ruler = canvas.controls.ruler;
- ruler.draggedEntity = this;
- const entityCenter = getEntityCenter(this);
- ruler.rulerOffset = {
- x: entityCenter.x - event.data.origin.x,
- y: entityCenter.y - event.data.origin.y,
- };
- if (game.settings.get(settingsKey, "autoStartMeasurement")) {
- let options = {};
- setSnapParameterOnOptions(ruler, options);
- ruler.dragRulerStart(options, false);
- }
- }
-
- function onEntityLeftDragMoveSnap(wrapped, event) {
- applyGridlessSnapping.call(this, event);
- onEntityLeftDragMove.call(this, wrapped, event);
- }
-
- function onEntityLeftDragMove(wrapped, event) {
- wrapped(event);
- const ruler = canvas.controls.ruler;
- if (ruler.isDragRuler) onMouseMove.call(ruler, event);
- }
-
- function onEntityDragLeftDrop(event) {
- const ruler = canvas.controls.ruler;
- if (!ruler.isDragRuler) {
- ruler.draggedEntity = undefined;
- 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;
- // This can happen if the user presses ESC during drag (maybe there are other ways too)
- 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;
- moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
- 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 rightClickAction = game.settings.get(settingsKey, "rightClickAction");
- let options = {};
- setSnapParameterOnOptions(ruler, options);
-
- if (ruler._state === Ruler.STATES.INACTIVE) {
- if (rightClickAction !== RightClickAction.CREATE_WAYPOINT) return false;
- ruler.dragRulerStart(options);
- event.preventDefault();
- } else if (ruler._state === Ruler.STATES.MEASURING) {
- switch (rightClickAction) {
- case RightClickAction.CREATE_WAYPOINT:
- event.preventDefault();
- ruler.dragRulerAddWaypoint(ruler.destination, options);
- break;
- case RightClickAction.DELETE_WAYPOINT:
- ruler.dragRulerDeleteWaypoint(event, options);
- break;
- case RightClickAction.ABORT_DRAG:
- ruler.dragRulerAbortDrag();
- break;
- }
- }
- return true;
- }
-
- 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;
-
- const rasterWidth = 35 / canvas.stage.scale.x;
- const tokenX = event.data.destination.x;
- const tokenY = event.data.destination.y;
- const destination = {x: tokenX + ruler.rulerOffset.x, y: tokenY + ruler.rulerOffset.y};
- const ranges = getRangesFromSpeedProvider(ruler.draggedEntity);
-
- const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
- if (terrainRulerAvailable) {
- const segments = ruler.constructor
- .dragRulerGetRaysFromWaypoints(ruler.waypoints, destination)
- .map(ray => {
- return {ray};
- });
- const pinpointDistances = new Map();
- for (const range of ranges) {
- pinpointDistances.set(range.range, null);
- }
- 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]);
- const rasterLocation = targetDistance[1];
- if (rasterLocation) {
- const deltaX = destination.x - rasterLocation.x;
- const deltaY = destination.y - rasterLocation.y;
- const rasterDistance = Math.hypot(deltaX, deltaY);
- if (rasterDistance < rasterWidth) {
- event.data.destination.x = rasterLocation.x - ruler.rulerOffset.x;
- event.data.destination.y = rasterLocation.y - ruler.rulerOffset.y;
- }
- }
- } else {
- let waypointDistance = 0;
- let origin = event.data.origin;
- if (ruler.waypoints.length > 1) {
- const segments = ruler.constructor
- .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};
- }
-
- const deltaX = tokenX - origin.x;
- const deltaY = tokenY - origin.y;
- const distance = Math.hypot(deltaX, deltaY);
- // targetRange will be the largest range that's still smaller than distance
- let targetDistance = ranges
- .map(range => range.range)
- .map(range => range - waypointDistance)
- .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;
- }
- }
- }
- }
|