Reformat with prettier

This commit is contained in:
Manuel Vögele
2022-09-28 07:35:16 +02:00
parent 71424cd284
commit 5e82049686
19 changed files with 956 additions and 710 deletions
+4
View File
@@ -0,0 +1,4 @@
printWidth: 100
trailingComma: "all"
bracketSpacing: false
arrowParens: "avoid"
+91 -70
View File
@@ -1,105 +1,116 @@
import {measureDistances} from "./compatibility.js"; import {measureDistances} from "./compatibility.js";
import {getMovementHistory} from "./movement_tracking.js"; import {getMovementHistory} from "./movement_tracking.js";
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js" import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js";
import {settingsKey} from "./settings.js" import {settingsKey} from "./settings.js";
import {getAreaFromPositionAndShape, getTokenShape} from "./util.js"; import {getAreaFromPositionAndShape, getTokenShape} from "./util.js";
export const availableSpeedProviders = {} export const availableSpeedProviders = {};
export let currentSpeedProvider = undefined export let currentSpeedProvider = undefined;
function register(module, type, speedProvider) { function register(module, type, speedProvider) {
const id = `${type}.${module.id}` const id = `${type}.${module.id}`;
let providerInstance let providerInstance;
if (speedProvider.prototype instanceof SpeedProvider) { if (speedProvider.prototype instanceof SpeedProvider) {
providerInstance = new speedProvider(id) providerInstance = new speedProvider(id);
} } else {
else { console.warn(
console.warn(`Drag Ruler | The ${type} '${module.id}' uses the old, deprecated version of the Drag Ruler API. ` + `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. " + "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.`); `Please update the ${type} ${module.id} to stay compatible with future Drag Ruler versions.`,
speedProvider.id = id );
speedProvider.usesRuler = () => true speedProvider.id = id;
providerInstance = speedProvider speedProvider.usesRuler = () => true;
providerInstance = speedProvider;
} }
setupProvider(providerInstance) setupProvider(providerInstance);
} }
function setupProvider(speedProvider) { function setupProvider(speedProvider) {
if (speedProvider instanceof 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])) { for (const color of speedProvider.colors.concat([unreachableColor])) {
game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.color.${color.id}`, { game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.color.${color.id}`, {
config: false, config: false,
scope: "client", scope: "client",
type: Number, type: Number,
default: color.default, default: color.default,
}) });
} }
for (const setting of speedProvider.settings) { for (const setting of speedProvider.settings) {
setting.config = false setting.config = false;
game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.setting.${setting.id}`, setting) game.settings.register(
settingsKey,
`speedProviders.${speedProvider.id}.setting.${setting.id}`,
setting,
);
} }
} }
availableSpeedProviders[speedProvider.id] = speedProvider availableSpeedProviders[speedProvider.id] = speedProvider;
game.settings.settings.get("drag-ruler.speedProvider").default = getDefaultSpeedProvider() game.settings.settings.get("drag-ruler.speedProvider").default = getDefaultSpeedProvider();
updateSpeedProvider() updateSpeedProvider();
} }
export function getDefaultSpeedProvider() { export function getDefaultSpeedProvider() {
const providerIds = Object.keys(availableSpeedProviders) const providerIds = Object.keys(availableSpeedProviders);
// Game systems take the highest precedence for the being the default // Game systems take the highest precedence for the being the default
const gameSystem = providerIds.find(key => key.startsWith("system.")) const gameSystem = providerIds.find(key => key.startsWith("system."));
if (gameSystem) if (gameSystem) return gameSystem;
return gameSystem
// If no game system is registered modules are next up. // 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 // 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 // settingKeys should always be sorted the same way so this should achive a stable default
const module = providerIds.find(key => key.startsWith("module.")) const module = providerIds.find(key => key.startsWith("module."));
if (module) if (module) return module;
return module
// If neither a game system or a module is found fall back to the native implementation // 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() { export function updateSpeedProvider() {
// If the configured provider is registered use that one. If not use the default provider // If the configured provider is registered use that one. If not use the default provider
const configuredProvider = game.settings.get("drag-ruler", "speedProvider") const configuredProvider = game.settings.get("drag-ruler", "speedProvider");
currentSpeedProvider = availableSpeedProviders[configuredProvider] ?? availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default] currentSpeedProvider =
availableSpeedProviders[configuredProvider] ??
availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default];
} }
export function initApi() { export function initApi() {
const genericSpeedProviderInstance = new GenericSpeedProvider("native") const genericSpeedProviderInstance = new GenericSpeedProvider("native");
setupProvider(genericSpeedProviderInstance) setupProvider(genericSpeedProviderInstance);
} }
export function getRangesFromSpeedProvider(token) { export function getRangesFromSpeedProvider(token) {
try { try {
if (currentSpeedProvider instanceof Function) if (currentSpeedProvider instanceof Function) return currentSpeedProvider(token, 0x00ff00);
return currentSpeedProvider(token, 0x00FF00) const ranges = currentSpeedProvider.getRanges(token);
const ranges = currentSpeedProvider.getRanges(token)
for (const range of ranges) { 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 return ranges;
} } catch (e) {
catch (e) { console.error(e);
console.error(e) return [];
return []
} }
} }
export function getUnreachableColorFromSpeedProvider() { export function getUnreachableColorFromSpeedProvider() {
if (currentSpeedProvider instanceof Function) if (currentSpeedProvider instanceof Function) return 0xff0000;
return 0xFF0000
try { try {
return game.settings.get(settingsKey, `speedProviders.${currentSpeedProvider.id}.color.unreachable`) return game.settings.get(
} settingsKey,
catch (e) { `speedProviders.${currentSpeedProvider.id}.color.unreachable`,
console.error(e) );
return 0xFF0000 } catch (e) {
console.error(e);
return 0xff0000;
} }
} }
@@ -109,8 +120,7 @@ export function getCostFromSpeedProvider(token, area, options) {
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area, options); return SpeedProvider.prototype.getCostForStep.call(undefined, token, area, options);
} }
return currentSpeedProvider.getCostForStep(token, area, options); return currentSpeedProvider.getCostForStep(token, area, options);
} } catch (e) {
catch (e) {
console.error(e); console.error(e);
return 1; return 1;
} }
@@ -120,66 +130,77 @@ export function getColorForDistanceAndToken(distance, token, ranges=null) {
if (!ranges) { if (!ranges) {
ranges = getRangesFromSpeedProvider(token); ranges = getRangesFromSpeedProvider(token);
} }
if (ranges.length === 0) if (ranges.length === 0) return null;
return null; const currentRange = ranges.reduce(
const currentRange = ranges.reduce((minRange, currentRange) => { (minRange, currentRange) => {
if (distance <= currentRange.range && currentRange.range < minRange.range) if (distance <= currentRange.range && currentRange.range < minRange.range)
return currentRange; return currentRange;
return minRange; return minRange;
}, {range: Infinity, color: getUnreachableColorFromSpeedProvider()}); },
{range: Infinity, color: getUnreachableColorFromSpeedProvider()},
);
return currentRange.color; return currentRange.color;
} }
export function getMovedDistanceFromToken(token) { export function getMovedDistanceFromToken(token) {
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active; const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
const history = getMovementHistory(token); 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 shape = getTokenShape(token);
const distances = measureDistances(segments, token, shape, {enableTerrainRuler: terrainRulerAvailable}); const distances = measureDistances(segments, token, shape, {
enableTerrainRuler: terrainRulerAvailable,
});
// Sum up the distances // Sum up the distances
return distances.reduce((acc, val) => acc + val, 0); return distances.reduce((acc, val) => acc + val, 0);
} }
export function buildCostFunction(token, shape) { 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) { export function registerModule(moduleId, speedProvider) {
// Check if a module with the given id exists and is currently enabled // 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 it doesn't the calling module did something wrong. Log a warning and ignore this module
if (!module) { if (!module) {
console.warn( console.warn(
`Drag Ruler | A module tried to register with the id "${moduleId}". However no active module with this id was found.` + `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. " + "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 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.") "If this call was made form a game system instead of a module please use `registerSystem` instead.",
return );
return;
} }
// Using Drag Ruler's id is not allowed // Using Drag Ruler's id is not allowed
if (moduleId === "drag-ruler") { if (moduleId === "drag-ruler") {
console.warn( console.warn(
`Drag Ruler | A module tried to register with the id "${moduleId}", which is not allowed. This api registration call was ignored. ` + `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 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." "If this call was made form a game system instead of a module please use `registerSystem` instead.",
) );
return return;
} }
register(module, "module", speedProvider) register(module, "module", speedProvider);
} }
export function registerSystem(systemId, 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 the current system id doesn't match the provided id something went wrong. Log a warning and ignore this module
if (system.id != systemId) { if (system.id != systemId) {
console.warn( console.warn(
`Drag Ruler | A system tried to register with the id "${systemId}". However the active system has a different id.` + `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. " + "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 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.") "If this call was made form a module instead of a game system please use `registerModule` instead.",
return );
return;
} }
register(system, "system", speedProvider) register(system, "system", speedProvider);
} }
+45 -21
View File
@@ -3,19 +3,24 @@ import {settingsKey} from "./settings.js";
import {highlightTokenShape} from "./util.js"; import {highlightTokenShape} from "./util.js";
export function getHexSizeSupportTokenGridCenter(token) { export function getHexSizeSupportTokenGridCenter(token) {
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token) const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token);
return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y} 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()) { for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
const color = this.dragRulerGetColorForDistance(startDistance + space.distance); 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 = {}) { export function measureDistances(segments, entity, shape, options = {}) {
const opts = duplicate(options) const opts = duplicate(options);
if (canvas.grid.diagonalRule === "EUCL") { if (canvas.grid.diagonalRule === "EUCL") {
opts.ignoreGrid = true; opts.ignoreGrid = true;
opts.gridSpaes = false; opts.gridSpaes = false;
@@ -25,14 +30,20 @@ export function measureDistances(segments, entity, shape, options={}) {
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces); const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
const previousSegments = segments.slice(0, firstNewSegmentIndex); const previousSegments = segments.slice(0, firstNewSegmentIndex);
const newSegments = segments.slice(firstNewSegmentIndex); const newSegments = segments.slice(firstNewSegmentIndex);
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance); const distances = previousSegments.map(
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces)); 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); opts.costFunction = buildCostFunction(entity, shape);
if (previousSegments.length > 0) if (previousSegments.length > 0)
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState; opts.terrainRulerInitialState =
previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
return distances.concat(terrainRuler.measureDistances(newSegments, opts)); return distances.concat(terrainRuler.measureDistances(newSegments, opts));
} } else {
else {
// If another module wants to enable grid measurements but disable grid highlighting, // 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 // manually set the *duplicate* option's gridSpaces value to true for the Foundry logic to work properly
if (!opts.ignoreGrid) { if (!opts.ignoreGrid) {
@@ -44,23 +55,31 @@ export function measureDistances(segments, entity, shape, options={}) {
export function checkDependencies() { export function checkDependencies() {
if (!game.modules.get("socketlib")?.active) { 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) { if (game.user.isGM) {
new Dialog({ new Dialog({
title: game.i18n.localize("drag-ruler.dependencies.socketlib.title"), title: game.i18n.localize("drag-ruler.dependencies.socketlib.title"),
content: `<h2>${game.i18n.localize("drag-ruler.dependencies.socketlib.title")}</h2><p>${game.i18n.localize("drag-ruler.dependencies.socketlib.text")}</p>`, content: `<h2>${game.i18n.localize(
"drag-ruler.dependencies.socketlib.title",
)}</h2><p>${game.i18n.localize("drag-ruler.dependencies.socketlib.text")}</p>`,
buttons: { buttons: {
ok: { ok: {
icon: '<i class="fas fa-check"></i>', icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("drag-ruler.dependencies.ok") label: game.i18n.localize("drag-ruler.dependencies.ok"),
} },
}, },
}).render(true); }).render(true);
} }
} } else if (
else if (!game.modules.get("terrain-ruler")?.active && game.user.isGM && !game.settings.get(settingsKey, "neverShowTerrainRulerHint")) { !game.modules.get("terrain-ruler")?.active &&
game.user.isGM &&
!game.settings.get(settingsKey, "neverShowTerrainRulerHint")
) {
const lastHint = game.settings.get(settingsKey, "lastTerrainRulerHintTime"); const lastHint = game.settings.get(settingsKey, "lastTerrainRulerHintTime");
if (Date.now() - lastHint > 604800000) { // One week if (Date.now() - lastHint > 604800000) {
// One week
let enabledTerrainModule; let enabledTerrainModule;
if (game.modules.get("enhanced-terrain-layer")?.active) { if (game.modules.get("enhanced-terrain-layer")?.active) {
enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title; enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title;
@@ -68,20 +87,25 @@ export function checkDependencies() {
if (enabledTerrainModule) { if (enabledTerrainModule) {
new Dialog({ new Dialog({
title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"), title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"),
content: `<h2>${game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title")}</h2><p>${game.i18n.format("drag-ruler.dependencies.terrain-ruler.text", {moduleName: enabledTerrainModule})}</p>`, content: `<h2>${game.i18n.localize(
"drag-ruler.dependencies.terrain-ruler.title",
)}</h2><p>${game.i18n.format("drag-ruler.dependencies.terrain-ruler.text", {
moduleName: enabledTerrainModule,
})}</p>`,
buttons: { buttons: {
ok: { ok: {
icon: '<i class="fas fa-check"></i>', icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("drag-ruler.dependencies.ok"), 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: { neverShowAgain: {
icon: '<i class="fas fa-times"></i>', icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.neverShowAgain"), label: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.neverShowAgain"),
callback: () => game.settings.set(settingsKey, "neverShowTerrainRulerHint", true), 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); }).render(true);
} }
} }
+2 -2
View File
@@ -110,8 +110,8 @@ export class ProcessOnceQueue {
const newNode = { const newNode = {
value: element, value: element,
next: null, next: null,
previous: null previous: null,
} };
if (!this.first) { if (!this.first) {
this.first = newNode; this.first = newNode;
+5 -6
View File
@@ -3,18 +3,17 @@
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705 // https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
export function getPixelsFromGridPosition(xGrid, yGrid) { export function getPixelsFromGridPosition(xGrid, yGrid) {
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) { 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 // 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 // https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
export function getGridPositionFromPixels(xPixel, yPixel) { export function getGridPositionFromPixels(xPixel, yPixel) {
const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel) const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel);
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return [y, x];
return [y, x] return [x, y];
return [x, y]
} }
export function getGridPositionFromPixelsObj(o) { export function getGridPositionFromPixelsObj(o) {
+119 -57
View File
@@ -1,12 +1,25 @@
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js"; 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 {Line} from "./geometry.js";
import {disableSnap, moveWithoutAnimation} from "./keybindings.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 {findPath, isPathfindingEnabled} from "./pathfinding.js";
import {settingsKey} from "./settings.js"; import {settingsKey} from "./settings.js";
import {recalculate} from "./socket.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 // This is a modified version of Ruler.moveToken from foundry 0.7.9
export async function moveEntities(draggedEntity, selectedEntities) { export async function moveEntities(draggedEntity, selectedEntities) {
@@ -27,12 +40,14 @@ export async function moveEntities(draggedEntity, selectedEntities) {
if (!game.user.isGM && draggedEntity instanceof Token) { if (!game.user.isGM && draggedEntity instanceof Token) {
const hasCollision = selectedEntities.some(token => { const hasCollision = selectedEntities.some(token => {
const offset = calculateEntityOffset(token, draggedEntity); 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) { if (window.WallHeight) {
window.WallHeight.addBoundsToRays(offsetRays, draggedEntity); window.WallHeight.addBoundsToRays(offsetRays, draggedEntity);
} }
return offsetRays.some(r => canvas.walls.checkCollision(r)); return offsetRays.some(r => canvas.walls.checkCollision(r));
}) });
if (hasCollision) { if (hasCollision) {
ui.notifications.error(game.i18n.localize("ERROR.TokenCollide")); ui.notifications.error(game.i18n.localize("ERROR.TokenCollide"));
this._endMeasurement(); this._endMeasurement();
@@ -46,8 +61,7 @@ export async function moveEntities(draggedEntity, selectedEntities) {
await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused); await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused);
// Once all animations are complete we can clear the ruler // Once all animations are complete we can clear the ruler
if (this.draggedEntity?.id === draggedEntity.id) if (this.draggedEntity?.id === draggedEntity.id) this._endMeasurement();
this._endMeasurement();
} }
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9 // This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
@@ -65,8 +79,7 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
dx = entity.data.x - origin[0]; dx = entity.data.x - origin[0];
dy = entity.data.y - origin[1]; dy = entity.data.y - origin[1];
} } else {
else {
dx = entity.data.x - origin[0]; dx = entity.data.x - origin[0];
dy = entity.data.y - origin[1]; dy = entity.data.y - origin[1];
} }
@@ -92,18 +105,29 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
const updates = entityPaths.map(({entity, path}) => { const updates = entityPaths.map(({entity, path}) => {
return {x: path.B.x, y: path.B.y, _id: entity.id}; 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) 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 // This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
if (this.cancelMovement) { 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; break;
} }
} }
if (isToken) if (isToken)
trackRays(entities, entityAnimationData.map(({rays}) => rays)).then(() => recalculate(entities)); trackRays(
entities,
entityAnimationData.map(({rays}) => rays),
).then(() => recalculate(entities));
} }
function calculateEntityOffset(entityA, entityB) { function calculateEntityOffset(entityA, entityB) {
@@ -111,7 +135,10 @@ function calculateEntityOffset(entityA, entityB) {
} }
function applyOffsetToRay(ray, offset) { 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; newRay.isPrevious = ray.isPrevious;
return newRay; return newRay;
} }
@@ -121,7 +148,10 @@ export function onMouseMove(event) {
if (this._state === Ruler.STATES.MOVING) return; if (this._state === Ruler.STATES.MOVING) return;
// Extract event data // 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 // Hide any existing Token HUD
canvas.hud.token.clear(); canvas.hud.token.clear();
@@ -140,12 +170,21 @@ function scheduleMeasurement(destination, event) {
event._measureTime = Date.now(); event._measureTime = Date.now();
this._state = Ruler.STATES.MEASURING; this._state = Ruler.STATES.MEASURING;
cancelScheduledMeasurement.call(this); cancelScheduledMeasurement.call(this);
} } else {
else {
this.deferredMeasurementData = {destination, event}; this.deferredMeasurementData = {destination, event};
if (!this.deferredMeasurementTimeout) { if (!this.deferredMeasurementTimeout) {
this.deferredMeasurementPromise = new Promise((resolve, reject) => this.deferredMeasurementResolve = resolve); this.deferredMeasurementPromise = new Promise(
this.deferredMeasurementTimeout = window.setTimeout(() => scheduleMeasurement.call(this, this.deferredMeasurementData.destination, this.deferredMeasurementData.event), measurementInterval); (resolve, reject) => (this.deferredMeasurementResolve = resolve),
);
this.deferredMeasurementTimeout = window.setTimeout(
() =>
scheduleMeasurement.call(
this,
this.deferredMeasurementData.destination,
this.deferredMeasurementData.event,
),
measurementInterval,
);
} }
} }
} }
@@ -158,10 +197,8 @@ export function cancelScheduledMeasurement() {
// This is a modified version of Ruler.measure form foundry 0.7.9 // This is a modified version of Ruler.measure form foundry 0.7.9
export function measure(destination, options = {}) { export function measure(destination, options = {}) {
const isToken = this.draggedEntity instanceof Token; const isToken = this.draggedEntity instanceof Token;
if (isToken && !this.draggedEntity.isVisible) if (isToken && !this.draggedEntity.isVisible) return [];
return []
options.snap = options.snap ?? !disableSnap; options.snap = options.snap ?? !disableSnap;
@@ -175,26 +212,28 @@ export function measure(destination, options={}) {
const from = getGridPositionFromPixelsObj(this.waypoints[this.waypoints.length - 1]); const from = getGridPositionFromPixelsObj(this.waypoints[this.waypoints.length - 1]);
const to = getGridPositionFromPixelsObj(destination); const to = getGridPositionFromPixelsObj(destination);
let path = findPath(from, to, this.draggedEntity, this.waypoints); let path = findPath(from, to, this.draggedEntity, this.waypoints);
if (path) if (path) path.shift();
path.shift();
if (path && path.length > 0) { 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 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); path = path.slice(1);
// If snapping is enabled, the last point of the path is already handled by the ruler // If snapping is enabled, the last point of the path is already handled by the ruler
if (options.snap) if (options.snap) path = path.slice(0, path.length - 1);
path = path.slice(0, path.length - 1);
for (const point of path) { for (const point of path) {
point.isPathfinding = true; point.isPathfinding = true;
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
} }
this.waypoints = this.waypoints.concat(path); 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 // 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]; destination = this.waypoints[this.waypoints.length - 1];
} }
@@ -217,24 +256,26 @@ export function measure(destination, options={}) {
const waypoints = this.waypoints.concat([destination]); const waypoints = this.waypoints.concat([destination]);
// Move the waypoints to the center of the grid if a size is used that measures from edge to edge // Move the waypoints to the center of the grid if a size is used that measures from edge to edge
const centeredWaypoints = isToken ? applyTokenSizeOffset(waypoints, this.draggedEntity) : duplicate(waypoints); const centeredWaypoints = isToken
? applyTokenSizeOffset(waypoints, this.draggedEntity)
: duplicate(waypoints);
// Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly // Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly
if (!options.enableTerrainRuler && !options.ignoreGrid) if (!options.enableTerrainRuler && !options.ignoreGrid)
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y)); centeredWaypoints.forEach(w => ([w.x, w.y] = canvas.grid.getCenter(w.x, w.y)));
const r = this.ruler; const r = this.ruler;
this.destination = destination; this.destination = destination;
// Iterate over waypoints and construct segment rays // Iterate over waypoints and construct segment rays
const segments = []; const segments = [];
const centeredSegments = [] const centeredSegments = [];
for (let [i, dest] of waypoints.slice(1).entries()) { for (let [i, dest] of waypoints.slice(1).entries()) {
const centeredDest = centeredWaypoints[i + 1] const centeredDest = centeredWaypoints[i + 1];
const origin = waypoints[i]; const origin = waypoints[i];
const centeredOrigin = centeredWaypoints[i] const centeredOrigin = centeredWaypoints[i];
const label = this.labels.children[i]; const label = this.labels.children[i];
const ray = new Ray(origin, dest); const ray = new Ray(origin, dest);
const centeredRay = new Ray(centeredOrigin, centeredDest) const centeredRay = new Ray(centeredOrigin, centeredDest);
ray.isPrevious = Boolean(origin.isPrevious); ray.isPrevious = Boolean(origin.isPrevious);
centeredRay.isPrevious = ray.isPrevious; centeredRay.isPrevious = ray.isPrevious;
ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces; ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces;
@@ -246,10 +287,9 @@ export function measure(destination, options={}) {
continue; continue;
} }
segments.push({ray, label}); segments.push({ray, label});
centeredSegments.push({ray: centeredRay, label}) centeredSegments.push({ray: centeredRay, label});
} }
const shape = isToken ? getTokenShape(this.draggedEntity) : null; const shape = isToken ? getTokenShape(this.draggedEntity) : null;
// Compute measured distance // Compute measured distance
@@ -258,9 +298,9 @@ export function measure(destination, options={}) {
let totalDistance = 0; let totalDistance = 0;
for (let [i, d] of distances.entries()) { for (let [i, d] of distances.entries()) {
let s = centeredSegments[i]; let s = centeredSegments[i];
s.startDistance = totalDistance s.startDistance = totalDistance;
totalDistance += d; totalDistance += d;
s.last = i === (centeredSegments.length - 1); s.last = i === centeredSegments.length - 1;
s.distance = d; s.distance = d;
s.text = this._getSegmentLabel(d, totalDistance, s.last); s.text = this._getSegmentLabel(d, totalDistance, s.last);
} }
@@ -271,18 +311,24 @@ export function measure(destination, options={}) {
// Draw measured path // Draw measured path
r.clear(); r.clear();
let rulerColor let rulerColor;
if (!options.gridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) if (!options.gridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
rulerColor = this.dragRulerGetColorForDistance(totalDistance); rulerColor = this.dragRulerGetColorForDistance(totalDistance);
else else rulerColor = this.color;
rulerColor = this.color for (const [i, s, cs] of enumeratedZip(
for (const [i, s, cs] of enumeratedZip([...segments].reverse(), [...centeredSegments].reverse())) { [...segments].reverse(),
[...centeredSegments].reverse(),
)) {
const {label, text, last} = cs; const {label, text, last} = cs;
// Draw line segment // Draw line segment
const opacityMultiplier = s.ray.isPrevious ? 0.33 : 1; 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) r.lineStyle(6, 0x000000, 0.5 * opacityMultiplier)
.lineStyle(4, rulerColor, 0.25 * opacityMultiplier).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y); .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 // Draw the distance label just after the endpoint of the segment
if (label) { if (label) {
@@ -296,11 +342,14 @@ export function measure(destination, options={}) {
const rayLabelXHitY = rayLine.calcY(labelPosition.x); const rayLabelXHitY = rayLine.calcY(labelPosition.x);
let innerDistance; let innerDistance;
// If ray hits top or bottom side of label // If ray hits top or bottom side of label
if (rayLine.isVertical || rayLabelXHitY < labelPosition.y || rayLabelXHitY > labelPosition.y + label.height) if (
innerDistance = Math.abs((label.height / 2) / Math.sin(s.ray.angle)); 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 // If ray hits left or right side of label
else else innerDistance = Math.abs(label.width / 2 / Math.cos(s.ray.angle));
innerDistance = Math.abs((label.width / 2) / Math.cos(s.ray.angle));
labelPosition = s.ray.project((s.ray.distance + 50 + innerDistance) / s.ray.distance); labelPosition = s.ray.project((s.ray.distance + 50 + innerDistance) / s.ray.distance);
labelPosition.x -= label.width / 2; labelPosition.x -= label.width / 2;
labelPosition.y -= label.height / 2; labelPosition.y -= label.height / 2;
@@ -310,9 +359,14 @@ export function measure(destination, options={}) {
// Highlight grid positions // Highlight grid positions
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) { if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) {
if (options.enableTerrainRuler) { if (options.enableTerrainRuler) {
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier) highlightMeasurementTerrainRuler.call(
} this,
else { cs.ray,
cs.startDistance,
shape,
opacityMultiplier,
);
} else {
const previousSegments = centeredSegments.slice(0, segments.length - 1 - i); const previousSegments = centeredSegments.slice(0, segments.length - 1 - i);
highlightMeasurementNative.call(this, cs.ray, previousSegments, shape, opacityMultiplier); highlightMeasurementNative.call(this, cs.ray, previousSegments, shape, opacityMultiplier);
} }
@@ -328,9 +382,17 @@ export function measure(destination, options={}) {
return segments; 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 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 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 tMax = Array.fromRange(nMax + 1).map(t => t / nMax);
// Track prior position // Track prior position
@@ -341,7 +403,7 @@ export function highlightMeasurementNative(ray, previousSegments, tokenShape=[{x
let {x, y} = ray.project(t); let {x, y} = ray.project(t);
// Get grid position // 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); let [x1, y1] = canvas.grid.grid.getGridPositionFromPixels(x, y);
if (x0 === x1 && y0 === y1) continue; if (x0 === x1 && y0 === y1) continue;
@@ -358,7 +420,7 @@ export function highlightMeasurementNative(ray, previousSegments, tokenShape=[{x
// If the positions are not neighbors, also highlight their halfway point // If the positions are not neighbors, also highlight their halfway point
if (i > 0 && !canvas.grid.isNeighbor(x0, y0, x1, y1)) { 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 {x, y} = ray.project(th);
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y); let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
let [xghtl, yghtl] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h); let [xghtl, yghtl] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
+32 -35
View File
@@ -18,76 +18,73 @@ export function findVertexSnapPoint(x, y, altOrientationFlag) {
} }
function findSnapPointRows(x, y, h, w, alt) { function findSnapPointRows(x, y, h, w, alt) {
let xOffset = 0.0 let xOffset = 0.0;
if (canvas.grid.grid.even) { if (canvas.grid.grid.even) {
xOffset = -0.5 xOffset = -0.5;
} }
let yOffset1 = 0.75 let yOffset1 = 0.75;
let yOffset2 = 0.00 let yOffset2 = 0.0;
if (alt) { if (alt) {
yOffset1 = 0.25 yOffset1 = 0.25;
yOffset2 = 1.00 yOffset2 = 1.0;
} }
let row1 = calculateSnapPointsRows(x, y, h, w, 0.5 + xOffset, yOffset1); let row1 = calculateSnapPointsRows(x, y, h, w, 0.5 + xOffset, yOffset1);
let row2 = calculateSnapPointsRows(x, y, h, w, 1.0 + xOffset, yOffset2); 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 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 dist2 = Math.pow(row2.x - x, 2) + Math.pow(row2.y - y, 2);
if (dist1 < dist2) { if (dist1 < dist2) {
return row1 return row1;
} } else {
else { return row2;
return row2
} }
} }
function calculateSnapPointsRows(x, y, h, w, xOff, yOff) { function calculateSnapPointsRows(x, y, h, w, xOff, yOff) {
let c = Math.floor(((x + ((0.5 - xOff) * w)) / w) + 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 r = Math.floor((y + (0.75 - yOff) * h) / (1.5 * h) + 1);
let snapX = (c * w) - ((1 - xOff) * w) let snapX = c * w - (1 - xOff) * w;
let snapY = (r * h * 1.5) - ((1.5 - yOff) * h) 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) { function findSnapPointCols(x, y, h, w, alt) {
let yOffset = 0.0 let yOffset = 0.0;
if (canvas.grid.grid.even) { if (canvas.grid.grid.even) {
yOffset = -0.5 yOffset = -0.5;
} }
let xOffset1 = 0.25 let xOffset1 = 0.25;
let xOffset2 = 1.00 let xOffset2 = 1.0;
if (alt) { if (alt) {
xOffset1 = 0.75 xOffset1 = 0.75;
xOffset2 = 0.00 xOffset2 = 0.0;
} }
let row1 = calculateSnapPointsCols(x, y, h, w, xOffset1, 0.5 + yOffset); let row1 = calculateSnapPointsCols(x, y, h, w, xOffset1, 0.5 + yOffset);
let row2 = calculateSnapPointsCols(x, y, h, w, xOffset2, 1.0 + 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 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 dist2 = Math.pow(row2.x - x, 2) + Math.pow(row2.y - y, 2);
if (dist1 < dist2) { if (dist1 < dist2) {
return row1 return row1;
} else {
return row2;
} }
else {
return row2
}
} }
function calculateSnapPointsCols(x, y, h, w, xOff, yOff) { function calculateSnapPointsCols(x, y, h, w, xOff, yOff) {
let c = Math.floor(((x + ((0.75 - xOff) * w)) / (1.5 * w)) + 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 r = Math.floor((y + (0.5 - yOff) * h) / h + 1);
let snapX = (c * w * 1.5) - ((1.5 - xOff) * w) let snapX = c * w * 1.5 - (1.5 - xOff) * w;
let snapY = (r * h) - ((1 - yOff) * h) let snapY = r * h - (1 - yOff) * h;
return {x: snapX, y: snapY} return {x: snapX, y: snapY};
} }
+27 -29
View File
@@ -9,18 +9,22 @@ export function registerKeybindings() {
game.keybindings.register(settingsKey, "cancelDrag", { game.keybindings.register(settingsKey, "cancelDrag", {
name: "drag-ruler.keybindings.cancelDrag", name: "drag-ruler.keybindings.cancelDrag",
onDown: cancelDrag, onDown: cancelDrag,
uneditable: [{ uneditable: [
{
key: "Escape", key: "Escape",
}], },
],
precedence: -1, precedence: -1,
}); });
game.keybindings.register(settingsKey, "createWaypoint", { game.keybindings.register(settingsKey, "createWaypoint", {
name: "drag-ruler.keybindings.createWaypoint", name: "drag-ruler.keybindings.createWaypoint",
onDown: handleCreateWaypoint, onDown: handleCreateWaypoint,
editable: [{ editable: [
key: "Space" {
}], key: "Space",
},
],
precedence: -1, precedence: -1,
}); });
@@ -35,9 +39,11 @@ export function registerKeybindings() {
hint: "drag-ruler.keybindings.disableSnap.hint", hint: "drag-ruler.keybindings.disableSnap.hint",
onDown: handleDisableSnap, onDown: handleDisableSnap,
onUp: handleDisableSnap, onUp: handleDisableSnap,
editable: [{ editable: [
{
key: "ShiftLeft", key: "ShiftLeft",
}], },
],
precedence: -1, precedence: -1,
}); });
@@ -46,9 +52,11 @@ export function registerKeybindings() {
hint: "drag-ruler.keybindings.moveWithoutAnimation.hint", hint: "drag-ruler.keybindings.moveWithoutAnimation.hint",
onDown: handleMoveWithoutAnimation, onDown: handleMoveWithoutAnimation,
onUp: handleMoveWithoutAnimation, onUp: handleMoveWithoutAnimation,
editable: [{ editable: [
{
key: "AltLeft", key: "AltLeft",
}], },
],
precedence: -1, precedence: -1,
}); });
@@ -64,8 +72,7 @@ export function registerKeybindings() {
function handleDeleteWaypoint() { function handleDeleteWaypoint() {
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
if (!ruler?.draggedEntity) if (!ruler?.draggedEntity) return false;
return false;
ruler.dragRulerDeleteWaypoint(); ruler.dragRulerDeleteWaypoint();
return true; return true;
} }
@@ -74,16 +81,14 @@ function handleCreateWaypoint() {
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
// .draggedEntity is used here because .isDragRuler only returns true once the ruler started measuring // .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 // Ruler can end up being undefined here if no canvas is active
if (!ruler?.draggedEntity) if (!ruler?.draggedEntity) return false;
return false;
let options = {}; let options = {};
setSnapParameterOnOptions(ruler, options); setSnapParameterOnOptions(ruler, options);
if (ruler._state === Ruler.STATES.INACTIVE) { if (ruler._state === Ruler.STATES.INACTIVE) {
ruler.dragRulerStart(options); ruler.dragRulerStart(options);
} } else {
else {
ruler.dragRulerAddWaypoint(getMeasurePosition(), options); ruler.dragRulerAddWaypoint(getMeasurePosition(), options);
} }
return true; return true;
@@ -91,8 +96,7 @@ function handleCreateWaypoint() {
function cancelDrag() { function cancelDrag() {
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
if (!ruler?.draggedEntity) if (!ruler?.draggedEntity) return false;
return false;
ruler.dragRulerAbortDrag(); ruler.dragRulerAbortDrag();
return true; return true;
} }
@@ -101,10 +105,8 @@ function handleDisableSnap(event) {
disableSnap = !event.up; disableSnap = !event.up;
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
if (!ruler?.isDragRuler) if (!ruler?.isDragRuler) return false;
return false; if (ruler._state !== Ruler.STATES.MEASURING) return false;
if (ruler._state !== Ruler.STATES.MEASURING)
return false;
ruler.measure(getMeasurePosition(), {snap: !disableSnap}); ruler.measure(getMeasurePosition(), {snap: !disableSnap});
ruler.dragRulerSendState(); ruler.dragRulerSendState();
@@ -115,10 +117,8 @@ function handleMoveWithoutAnimation(event) {
moveWithoutAnimation = !event.up; moveWithoutAnimation = !event.up;
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
if (!ruler?.isDragRuler) if (!ruler?.isDragRuler) return false;
return false; if (ruler._state !== Ruler.STATES.MEASURING) return false;
if (ruler._state !== Ruler.STATES.MEASURING)
return false;
ruler.measure(getMeasurePosition(), {snap: !disableSnap}); ruler.measure(getMeasurePosition(), {snap: !disableSnap});
ruler.dragRulerSendState(); ruler.dragRulerSendState();
@@ -129,10 +129,8 @@ function handleTogglePathfinding(event) {
togglePathfinding = !event.up; togglePathfinding = !event.up;
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
if (!ruler?.isDragRuler) if (!ruler?.isDragRuler) return false;
return false; if (ruler._state !== Ruler.STATES.MEASURING) return false;
if (ruler._state !== Ruler.STATES.MEASURING)
return false;
ruler.measure(getMeasurePosition(), {snap: !disableSnap}); ruler.measure(getMeasurePosition(), {snap: !disableSnap});
ruler.dragRulerSendState(); ruler.dragRulerSendState();
+37 -20
View File
@@ -1,18 +1,20 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro // Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro
"use strict";
'use strict';
// A shim for the libWrapper library // A shim for the libWrapper library
export let libWrapper = undefined; export let libWrapper = undefined;
export const VERSIONS = [1, 11, 0]; export const VERSIONS = [1, 11, 0];
export const TGT_SPLIT_RE = new RegExp("([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])", 'g'); export const TGT_SPLIT_RE = new RegExp(
export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", 'g'); "([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])",
"g",
);
export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", "g");
// Main shim code // Main shim code
Hooks.once('init', () => { Hooks.once("init", () => {
// Check if the real module is already loaded - if so, use it // 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; libWrapper = globalThis.libWrapper;
@@ -21,24 +23,33 @@ Hooks.once('init', () => {
// Fallback implementation // Fallback implementation
libWrapper = class { libWrapper = class {
static get is_fallback() { return true }; static get is_fallback() {
return true;
}
static get WRAPPER() { return 'WRAPPER' }; static get WRAPPER() {
static get MIXED() { return 'MIXED' }; return "WRAPPER";
static get OVERRIDE() { return 'OVERRIDE' }; }
static get MIXED() {
return "MIXED";
}
static get OVERRIDE() {
return "OVERRIDE";
}
static register(package_id, target, fn, type = "MIXED", {chain = undefined} = {}) { static register(package_id, target, fn, type = "MIXED", {chain = undefined} = {}) {
const is_setter = target.endsWith('#set'); const is_setter = target.endsWith("#set");
target = !is_setter ? target : target.slice(0, -4); 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 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 root_nm = split.splice(0, 1)[0];
let obj, fn_name; let obj, fn_name;
if (split.length == 0) { if (split.length == 0) {
obj = globalThis; obj = globalThis;
fn_name = root_nm; fn_name = root_nm;
} } else {
else {
const _eval = eval; const _eval = eval;
fn_name = split.pop(); 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));
@@ -51,22 +62,28 @@ Hooks.once('init', () => {
if (descriptor) break; if (descriptor) break;
iObj = Object.getPrototypeOf(iObj); 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; 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 (!is_setter) {
if (descriptor.value) { if (descriptor.value) {
original = descriptor.value; original = descriptor.value;
descriptor.value = wrapper; descriptor.value = wrapper;
} } else {
else {
original = descriptor.get; original = descriptor.get;
descriptor.get = wrapper; descriptor.get = wrapper;
} }
} } else {
else {
if (!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`; if (!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`;
original = descriptor.set; original = descriptor.set;
descriptor.set = wrapper; descriptor.set = wrapper;
@@ -75,5 +92,5 @@ Hooks.once('init', () => {
descriptor.configurable = true; descriptor.configurable = true;
Object.defineProperty(obj, fn_name, descriptor); Object.defineProperty(obj, fn_name, descriptor);
} }
} };
}); });
+114 -73
View File
@@ -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 {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 {disableSnap, registerKeybindings} from "./keybindings.js";
import {libWrapper} from "./libwrapper_shim.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 {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 {extendRuler} from "./ruler.js";
import {registerSettings, RightClickAction, settingsKey} from "./settings.js" import {registerSettings, RightClickAction, settingsKey} from "./settings.js";
import {recalculate} from "./socket.js"; import {recalculate} from "./socket.js";
import {SpeedProvider} from "./speed_provider.js" import {SpeedProvider} from "./speed_provider.js";
import {setSnapParameterOnOptions} from "./util.js"; 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; CONFIG.debug.dragRuler = false;
export let debugGraphics = undefined; export let debugGraphics = undefined;
@@ -37,7 +48,7 @@ initGridlessPathfinding().then(() => {
}); });
// Whenever a token the current user controls updates, start caching // Whenever a token the current user controls updates, start caching
Hooks.on("updateToken", (document) => { Hooks.on("updateToken", document => {
const token = document.object; const token = document.object;
if (token._controlled) { if (token._controlled) {
startBackgroundCaching(token); startBackgroundCaching(token);
@@ -46,12 +57,17 @@ initGridlessPathfinding().then(() => {
}); });
Hooks.once("init", () => { Hooks.once("init", () => {
registerSettings() registerSettings();
registerKeybindings() registerKeybindings();
initApi() initApi();
hookDragHandlers(Token); hookDragHandlers(Token);
hookDragHandlers(MeasuredTemplate); hookDragHandlers(MeasuredTemplate);
libWrapper.register("drag-ruler", "TokenLayer.prototype.undoHistory", tokenLayerUndoHistory, "WRAPPER"); libWrapper.register(
"drag-ruler",
"TokenLayer.prototype.undoHistory",
tokenLayerUndoHistory,
"WRAPPER",
);
extendRuler(); extendRuler();
@@ -62,16 +78,15 @@ Hooks.once("init", () => {
registerSystem, registerSystem,
recalculate, recalculate,
resetMovementHistory, resetMovementHistory,
} };
}) });
Hooks.once("ready", () => { Hooks.once("ready", () => {
performMigrations() performMigrations();
checkDependencies(); checkDependencies();
Hooks.callAll("dragRuler.ready", SpeedProvider) Hooks.callAll("dragRuler.ready", SpeedProvider);
if (CONFIG.debug.dragRuler) if (CONFIG.debug.dragRuler) debugGraphics = canvas.controls.addChild(new PIXI.Container());
debugGraphics = canvas.controls.addChild(new PIXI.Container()); });
})
Hooks.on("canvasReady", () => { Hooks.on("canvasReady", () => {
canvas.controls.rulers.children.forEach(ruler => { canvas.controls.rulers.children.forEach(ruler => {
@@ -79,16 +94,16 @@ Hooks.on("canvasReady", () => {
Object.defineProperty(ruler, "isDragRuler", { Object.defineProperty(ruler, "isDragRuler", {
get: function isDragRuler() { get: function isDragRuler() {
return Boolean(this.draggedEntity) && this._state !== Ruler.STATES.INACTIVE; return Boolean(this.draggedEntity) && this._state !== Ruler.STATES.INACTIVE;
} },
}) });
}) });
}) });
Hooks.on("getCombatTrackerEntryContext", function (html, menu) { Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
const entry = { const entry = {
name: "drag-ruler.resetMovementHistory", name: "drag-ruler.resetMovementHistory",
icon: '<i class="fas fa-undo-alt"></i>', icon: '<i class="fas fa-undo-alt"></i>',
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); menu.splice(1, 0, entry);
}); });
@@ -96,20 +111,44 @@ Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
function forwardIfUnahndled(newFn) { function forwardIfUnahndled(newFn) {
return function (oldFn, ...args) { return function (oldFn, ...args) {
const eventHandled = newFn(...args); const eventHandled = newFn(...args);
if (!eventHandled) if (!eventHandled) oldFn(...args);
oldFn(...args);
}; };
} }
function hookDragHandlers(entityType) { function hookDragHandlers(entityType) {
const entityName = entityType.name const entityName = entityType.name;
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftStart`, onEntityLeftDragStart, "WRAPPER"); libWrapper.register(
"drag-ruler",
`${entityName}.prototype._onDragLeftStart`,
onEntityLeftDragStart,
"WRAPPER",
);
if (entityType === Token) if (entityType === Token)
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMoveSnap, "WRAPPER"); libWrapper.register(
"drag-ruler",
`${entityName}.prototype._onDragLeftMove`,
onEntityLeftDragMoveSnap,
"WRAPPER",
);
else else
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMove, "WRAPPER"); libWrapper.register(
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftDrop`, forwardIfUnahndled(onEntityDragLeftDrop), "MIXED"); "drag-ruler",
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftCancel`, forwardIfUnahndled(onEntityDragLeftCancel), "MIXED"); `${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) { async function tokenLayerUndoHistory(wrapped) {
@@ -127,14 +166,21 @@ async function tokenLayerUndoHistory(wrapped) {
function onEntityLeftDragStart(wrapped, event) { function onEntityLeftDragStart(wrapped, event) {
wrapped(event); wrapped(event);
const isToken = this instanceof Token; const isToken = this instanceof Token;
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler;
ruler.draggedEntity = this; ruler.draggedEntity = this;
let entityCenter; 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); entityCenter = getHexSizeSupportTokenGridCenter(this);
else else entityCenter = this.center;
entityCenter = this.center; ruler.rulerOffset = {
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y}; x: entityCenter.x - event.data.origin.x,
y: entityCenter.y - event.data.origin.y,
};
if (game.settings.get(settingsKey, "autoStartMeasurement")) { if (game.settings.get(settingsKey, "autoStartMeasurement")) {
let options = {}; let options = {};
setSnapParameterOnOptions(ruler, options); setSnapParameterOnOptions(ruler, options);
@@ -149,48 +195,42 @@ function onEntityLeftDragMoveSnap(wrapped, event) {
function onEntityLeftDragMove(wrapped, event) { function onEntityLeftDragMove(wrapped, event) {
wrapped(event); wrapped(event);
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler;
if (ruler.isDragRuler) if (ruler.isDragRuler) onMouseMove.call(ruler, event);
onMouseMove.call(ruler, event)
} }
function onEntityDragLeftDrop(event) { function onEntityDragLeftDrop(event) {
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler;
if (!ruler.isDragRuler) { if (!ruler.isDragRuler) {
ruler.draggedEntity = undefined; ruler.draggedEntity = undefined;
return false return false;
} }
// When we're dragging a measured template no token will ever be selected, // 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 // 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) // This can happen if the user presses ESC during drag (maybe there are other ways too)
if (selectedTokens.length === 0) if (selectedTokens.length === 0) selectedTokens.push(ruler.draggedEntity);
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 // 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) if (ruler._state === Ruler.STATES.STARTING) onMouseMove.call(ruler, event);
onMouseMove.call(ruler, event); ruler._state = Ruler.STATES.MOVING;
ruler._state = Ruler.STATES.MOVING
moveEntities.call(ruler, ruler.draggedEntity, selectedTokens); moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
return true return true;
} }
function onEntityDragLeftCancel(event) { function onEntityDragLeftCancel(event) {
// This function is invoked by right clicking // This function is invoked by right clicking
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler;
if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING) if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING) return false;
return false
const rightClickAction = game.settings.get(settingsKey, "rightClickAction"); const rightClickAction = game.settings.get(settingsKey, "rightClickAction");
let options = {}; let options = {};
setSnapParameterOnOptions(ruler, options); setSnapParameterOnOptions(ruler, options);
if (ruler._state === Ruler.STATES.INACTIVE) { if (ruler._state === Ruler.STATES.INACTIVE) {
if (rightClickAction !== RightClickAction.CREATE_WAYPOINT) if (rightClickAction !== RightClickAction.CREATE_WAYPOINT) return false;
return false;
ruler.dragRulerStart(options); ruler.dragRulerStart(options);
event.preventDefault(); event.preventDefault();
} } else if (ruler._state === Ruler.STATES.MEASURING) {
else if (ruler._state === Ruler.STATES.MEASURING) {
switch (rightClickAction) { switch (rightClickAction) {
case RightClickAction.CREATE_WAYPOINT: case RightClickAction.CREATE_WAYPOINT:
event.preventDefault(); event.preventDefault();
@@ -209,14 +249,10 @@ function onEntityDragLeftCancel(event) {
function applyGridlessSnapping(event) { function applyGridlessSnapping(event) {
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
if (!game.settings.get(settingsKey, "useGridlessRaster")) if (!game.settings.get(settingsKey, "useGridlessRaster")) return;
return; if (!ruler.isDragRuler) return;
if (!ruler.isDragRuler) if (disableSnap) return;
return; if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return;
if (disableSnap)
return;
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS)
return;
const rasterWidth = 35 / canvas.stage.scale.x; const rasterWidth = 35 / canvas.stage.scale.x;
const tokenX = event.data.destination.x; const tokenX = event.data.destination.x;
@@ -226,7 +262,9 @@ function applyGridlessSnapping(event) {
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active; const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
if (terrainRulerAvailable) { 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(); const pinpointDistances = new Map();
for (const range of ranges) { for (const range of ranges) {
pinpointDistances.set(range.range, null); pinpointDistances.set(range.range, null);
@@ -234,7 +272,7 @@ function applyGridlessSnapping(event) {
terrainRuler.measureDistances(segments, {pinpointDistances}); terrainRuler.measureDistances(segments, {pinpointDistances});
const targetDistance = Array.from(pinpointDistances.entries()) const targetDistance = Array.from(pinpointDistances.entries())
.filter(([_key, val]) => val) .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]; const rasterLocation = targetDistance[1];
if (rasterLocation) { if (rasterLocation) {
const deltaX = destination.x - rasterLocation.x; const deltaX = destination.x - rasterLocation.x;
@@ -245,12 +283,15 @@ function applyGridlessSnapping(event) {
event.data.destination.y = rasterLocation.y - ruler.rulerOffset.y; event.data.destination.y = rasterLocation.y - ruler.rulerOffset.y;
} }
} }
} } else {
else {
let waypointDistance = 0; let waypointDistance = 0;
let origin = event.data.origin; let origin = event.data.origin;
if (ruler.waypoints.length > 1) { 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; origin = segments.pop().ray.A;
waypointDistance = canvas.grid.measureDistances(segments).reduce((a, b) => a + b); waypointDistance = canvas.grid.measureDistances(segments).reduce((a, b) => a + b);
origin = {x: origin.x - ruler.rulerOffset.x, y: origin.y - ruler.rulerOffset.y}; origin = {x: origin.x - ruler.rulerOffset.x, y: origin.y - ruler.rulerOffset.y};
@@ -263,13 +304,13 @@ function applyGridlessSnapping(event) {
let targetDistance = ranges let targetDistance = ranges
.map(range => range.range) .map(range => range.range)
.map(range => range - waypointDistance) .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) .filter(range => range < distance)
.reduce((a, b) => Math.max(a, b), 0); .reduce((a, b) => Math.max(a, b), 0);
if (targetDistance) { if (targetDistance) {
if (distance < targetDistance + rasterWidth) { if (distance < targetDistance + rasterWidth) {
event.data.destination.x = origin.x + deltaX * targetDistance / distance; event.data.destination.x = origin.x + (deltaX * targetDistance) / distance;
event.data.destination.y = origin.y + deltaY * targetDistance / distance; event.data.destination.y = origin.y + (deltaY * targetDistance) / distance;
} }
} }
} }
+12 -12
View File
@@ -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() { export async function performMigrations() {
if (game.user.isGM) if (game.user.isGM) await performWorldMigraionts();
await performWorldMigraionts() await performClientMigrations();
await performClientMigrations()
} }
async function performWorldMigraionts() { async function performWorldMigraionts() {
let dataVersion = game.settings.get(settingsKey, "dataVersion"); let dataVersion = game.settings.get(settingsKey, "dataVersion");
if (dataVersion === currentDataVersion) if (dataVersion === currentDataVersion) return;
return;
if (dataVersion === "fresh install") { if (dataVersion === "fresh install") {
game.settings.set(settingsKey, "dataVersion", currentDataVersion) game.settings.set(settingsKey, "dataVersion", currentDataVersion);
return return;
} }
if (dataVersion === "1.3.0") { if (dataVersion === "1.3.0") {
dataVersion = "1.10.0" dataVersion = "1.10.0";
} }
game.settings.set(settingsKey, "dataVersion", dataVersion); game.settings.set(settingsKey, "dataVersion", dataVersion);
@@ -31,13 +29,15 @@ async function performClientMigrations() {
if (dataVersion === "fresh install") { if (dataVersion === "fresh install") {
// Start of migration from unnamed version (< 1.10.0). TODO Remove in a future version // 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) { if (swapSpacebarRightClick) {
game.settings.set(settingsKey, "rightClickAction", RightClickAction.CREATE_WAYPOINT); game.settings.set(settingsKey, "rightClickAction", RightClickAction.CREATE_WAYPOINT);
await game.keybindings.set(settingsKey, "createWaypoint", []); await game.keybindings.set(settingsKey, "createWaypoint", []);
await game.keybindings.set(settingsKey, "deleteWaypoint", [{key: "Space"}]); await game.keybindings.set(settingsKey, "deleteWaypoint", [{key: "Space"}]);
} }
// End of migration from unnamed version // End of migration from unnamed version
game.settings.set(settingsKey, "clientDataVersion", currentDataVersion) game.settings.set(settingsKey, "clientDataVersion", currentDataVersion);
} }
} }
+21 -25
View File
@@ -9,38 +9,35 @@ function initTrackingFlag(combatant) {
if (isNaN(dragRulerFlag.trackedRound)) { if (isNaN(dragRulerFlag.trackedRound)) {
mergeObject(dragRulerFlag, initialFlag); mergeObject(dragRulerFlag, initialFlag);
} }
} } else {
else {
combatant.data.flags.dragRuler = initialFlag; combatant.data.flags.dragRuler = initialFlag;
} }
} }
function getInitializedCombatant(token, combat) { function getInitializedCombatant(token, combat) {
const combatant = combat.getCombatantByToken(token.id); const combatant = combat.getCombatantByToken(token.id);
if (!combatant) if (!combatant) return undefined;
return undefined;
initTrackingFlag(combatant); initTrackingFlag(combatant);
return combatant; return combatant;
} }
export async function trackRays(tokens, tokenRays) { export async function trackRays(tokens, tokenRays) {
const combat = game.combat; const combat = game.combat;
if (!combat) if (!combat) return;
return; if (!combat.started) return;
if (!combat.started)
return;
if (!(tokens instanceof Array)) { if (!(tokens instanceof Array)) {
tokens = [tokens]; tokens = [tokens];
tokenRays = [tokenRays]; 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); await updateCombatantDragRulerFlags(combat, updates);
} }
function calculateUpdate(combat, token, rays) { function calculateUpdate(combat, token, rays) {
const combatant = getInitializedCombatant(token, combat); const combatant = getInitializedCombatant(token, combat);
if (!combatant) if (!combatant) return;
return;
// Check if we have entered a new round. If so, remove the currently stored path // Check if we have entered a new round. If so, remove the currently stored path
if (combat.data.round > combatant.data.flags.dragRuler.trackedRound) { 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 // Ignore rays that have the same start and end coordinates
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) { if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
if (terrainRulerAvailable) { if (terrainRulerAvailable) {
measureDistances([{ray}], token, getTokenShape(token), {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.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
ray.A.dragRulerFinalState = ray.terrainRulerFinalState; ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
} }
@@ -68,23 +68,18 @@ function calculateUpdate(combat, token, rays) {
export function getMovementHistory(token) { export function getMovementHistory(token) {
const combat = game.combat; const combat = game.combat;
if (!combat) if (!combat) return [];
return [];
const combatant = combat.getCombatantByToken(token.id); const combatant = combat.getCombatantByToken(token.id);
if (!combatant) if (!combatant) return [];
return [];
const dragRulerFlags = combatant.data.flags.dragRuler; const dragRulerFlags = combatant.data.flags.dragRuler;
if (!dragRulerFlags) if (!dragRulerFlags) return [];
return []; if (combat.data.round > dragRulerFlags.trackedRound) return [];
if (combat.data.round > dragRulerFlags.trackedRound)
return [];
return dragRulerFlags.passedWaypoints ?? []; return dragRulerFlags.passedWaypoints ?? [];
} }
export async function removeLastHistoryEntryIfAt(token, x, y) { export async function removeLastHistoryEntryIfAt(token, x, y) {
const history = getMovementHistory(token); const history = getMovementHistory(token);
if (history.length === 0) if (history.length === 0) return;
return;
const entry = history[history.length - 1]; 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)) { if (!isClose(x + token.w / 2, entry.x, 0.1) || !isClose(y + token.h / 2, entry.y, 0.1)) {
return; return;
@@ -92,14 +87,15 @@ export async function removeLastHistoryEntryIfAt(token, x, y) {
history.pop(); history.pop();
const combat = game.combat; const combat = game.combat;
const combatant = combat.getCombatantByToken(token.id); 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) { export async function resetMovementHistory(combat, combatantId) {
const combatant = combat.combatants.get(combatantId); const combatant = combat.combatants.get(combatantId);
const dragRulerFlags = combatant.data.flags.dragRuler; const dragRulerFlags = combatant.data.flags.dragRuler;
if (!dragRulerFlags) if (!dragRulerFlags) return;
return;
dragRulerFlags.passedWaypoints = null; dragRulerFlags.passedWaypoints = null;
dragRulerFlags.trackedRound = null; dragRulerFlags.trackedRound = null;
dragRulerFlags.rulerState = null; dragRulerFlags.rulerState = null;
+93 -59
View File
@@ -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 {moveWithoutAnimation, togglePathfinding} from "./keybindings.js";
import {debugGraphics} from "./main.js"; import {debugGraphics} from "./main.js";
import {settingsKey} from "./settings.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 * as GridlessPathfinding from "../wasm/gridless_pathfinding.js";
import {PriorityQueueSet, ProcessOnceQueue} from "./data_structures.js"; import {PriorityQueueSet, ProcessOnceQueue} from "./data_structures.js";
@@ -60,8 +71,8 @@ class Cache {
this.background = { this.background = {
nextJobId: null, nextJobId: null,
nextTimeoutId: null, nextTimeoutId: null,
nextAnimationFrameId: null nextAnimationFrameId: null,
} };
} }
clear() { clear() {
@@ -98,8 +109,10 @@ class Cache {
// Check if we already have the max number of layers. If we do, // 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 // get rid of the one that hasn't been used for the longest
if (this.layers.size >= Cache.maxCacheLayers) { if (this.layers.size >= Cache.maxCacheLayers) {
const oldestCache = Array.from(this.layers.values()) const oldestCache = Array.from(this.layers.values()).reduce(
.reduce((layer1, layer2) => (layer1?.lastUsed < layer2.lastUsed) ? layer1 : layer2, null); (layer1, layer2) => (layer1?.lastUsed < layer2.lastUsed ? layer1 : layer2),
null,
);
this.layers.delete(oldestCache.cacheId); this.layers.delete(oldestCache.cacheId);
} }
@@ -119,7 +132,7 @@ class Cache {
*/ */
startBackgroundCaching(token) { startBackgroundCaching(token) {
const cacheLayer = this.getCacheLayer(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]); 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 // Find the latest-used cache that has nodes left to cache
const latestCache = this.getLatestCacheWithNonEmptyQueue(); const latestCache = this.getLatestCacheWithNonEmptyQueue();
if (latestCache) { if (latestCache) {
this.background.nextJobId = window.requestIdleCallback( this.background.nextJobId = window.requestIdleCallback(() =>
() => this.runBackgroundCache(latestCache) this.runBackgroundCache(latestCache),
); );
this.resetAnimationFrameTimeout(); this.resetAnimationFrameTimeout();
} }
@@ -152,13 +165,10 @@ class Cache {
this.cancelTimeout(); this.cancelTimeout();
this.cancelAnimationFrame(); this.cancelAnimationFrame();
this.background.nextTimeoutId = window.setTimeout( this.background.nextTimeoutId = window.setTimeout(() => {
() => {
this.scheduleAnimationFrameCache(); this.scheduleAnimationFrameCache();
this.background.nextTimeoutId = null; this.background.nextTimeoutId = null;
}, }, Cache.backgroundCachingTimeoutMillis);
Cache.backgroundCachingTimeoutMillis
);
} }
/** /**
@@ -167,8 +177,8 @@ class Cache {
scheduleAnimationFrameCache() { scheduleAnimationFrameCache() {
const latestCache = this.getLatestCacheWithNonEmptyQueue(); const latestCache = this.getLatestCacheWithNonEmptyQueue();
if (latestCache) { if (latestCache) {
this.background.nextAnimationFrameId = window.requestAnimationFrame( this.background.nextAnimationFrameId = window.requestAnimationFrame(() =>
() => this.runAnimationCache(latestCache) this.runAnimationCache(latestCache),
); );
} }
} }
@@ -179,7 +189,7 @@ class Cache {
getLatestCacheWithNonEmptyQueue() { getLatestCacheWithNonEmptyQueue() {
return Array.from(this.layers.values()) return Array.from(this.layers.values())
.filter(layer => layer.queue.hasNext()) .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; let gridWidth, gridHeight;
export function isPathfindingEnabled() { export function isPathfindingEnabled() {
if (this.user !== game.user) if (this.user !== game.user) return false;
return false; if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding")) return false;
if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding")) if (moveWithoutAnimation) return false;
return false;
if (moveWithoutAnimation)
return false;
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding; return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
} }
@@ -253,7 +260,12 @@ export function findPath(from, to, token, previousWaypoints) {
let pathfinder = gridlessPathfinders.get(tokenSize); let pathfinder = gridlessPathfinders.get(tokenSize);
if (!pathfinder) { if (!pathfinder) {
let radiusMultiplier = game.settings.get(settingsKey, "pathfindingRadius"); 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); gridlessPathfinders.set(tokenSize, pathfinder);
} }
paintGridlessPathfindingDebug(pathfinder); paintGridlessPathfindingDebug(pathfinder);
@@ -261,30 +273,36 @@ export function findPath(from, to, token, previousWaypoints) {
} else { } else {
const cacheLayer = cache.getCacheLayer(token); const cacheLayer = cache.getCacheLayer(token);
const firstNode = calculatePath(from, to, cacheLayer, previousWaypoints); const firstNode = calculatePath(from, to, cacheLayer, previousWaypoints);
if (!firstNode) if (!firstNode) return null;
return null;
paintGriddedPathfindingDebug(firstNode, cacheLayer.tokenData); paintGriddedPathfindingDebug(firstNode, cacheLayer.tokenData);
const path = []; const path = [];
let currentNode = firstNode; let currentNode = firstNode;
while (currentNode) { 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 // Replace last waypoint if the current waypoint leads to a valid path that isn't longer than the old path
if (window.terrainRuler) { if (window.terrainRuler) {
let startNode = getCenterFromGridPositionObj(path[path.length - 2]); let startNode = getCenterFromGridPositionObj(path[path.length - 2]);
let middleNode = getCenterFromGridPositionObj(path[path.length - 1]); let middleNode = getCenterFromGridPositionObj(path[path.length - 1]);
let endNode = getCenterFromGridPositionObj(currentNode.node); 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 newPath = [{ray: new Ray(startNode, endNode)}];
let costFunction = buildCostFunction(token, getTokenShape(token)); let costFunction = buildCostFunction(token, getTokenShape(token));
// TODO Cache the used measurement for use in the next loop to improve performance // 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]; let newDistance = terrainRuler.measureDistances(newPath, {costFunction})[0];
// TODO We might need to check if the diagonal count has increased on 5-10-5 // TODO We might need to check if the diagonal count has increased on 5-10-5
if (newDistance < oldDistance) { if (newDistance < oldDistance) {
path.pop(); path.pop();
} } else if (newDistance === oldDistance) {
else if (newDistance === oldDistance) {
let oldNoDiagonals = oldPath[1].ray.terrainRulerFinalState?.noDiagonals; let oldNoDiagonals = oldPath[1].ray.terrainRulerFinalState?.noDiagonals;
let newNoDiagonals = newPath[0].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) // 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(); path.pop();
} }
} }
} } else {
else {
path.pop(); path.pop();
} }
} }
@@ -323,18 +340,36 @@ function getNode(pos, cacheLayer, initialize = true) {
const node = cacheLayer.nodes[pos.y][pos.x]; const node = cacheLayer.nodes[pos.y][pos.x];
if (initialize && !node.edges) { if (initialize && !node.edges) {
node.edges = []; node.edges = [];
for (const neighborPos of canvas.grid.grid.getNeighbors(pos.y, pos.x).map(([y, x]) => {return {x, y};})) { for (const neighborPos of canvas.grid.grid.getNeighbors(pos.y, pos.x).map(([y, x]) => {
if (neighborPos.x < 0 || neighborPos.y < 0 || neighborPos.x >= gridWidth || neighborPos.y >= gridHeight) { return {x, y};
})) {
if (
neighborPos.x < 0 ||
neighborPos.y < 0 ||
neighborPos.x >= gridWidth ||
neighborPos.y >= gridHeight
) {
continue; continue;
} }
// TODO Work with pixels instead of grid locations // TODO Work with pixels instead of grid locations
if (!stepCollidesWithWall(pos, neighborPos, cacheLayer.tokenData)) { 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; let edgeCost;
if (window.terrainRuler) { if (window.terrainRuler) {
let ray = new Ray(getCenterFromGridPositionObj(pos), getCenterFromGridPositionObj(neighborPos)); let ray = new Ray(
let measuredDistance = terrainRuler.measureDistances([{ray}], {costFunction: buildCostFunction(cacheLayer.tokenData, getTokenShapeForTokenData(cacheLayer.tokenData))})[0]; getCenterFromGridPositionObj(pos),
getCenterFromGridPositionObj(neighborPos),
);
let measuredDistance = terrainRuler.measureDistances([{ray}], {
costFunction: buildCostFunction(
cacheLayer.tokenData,
getTokenShapeForTokenData(cacheLayer.tokenData),
),
})[0];
edgeCost = Math.round(measuredDistance / canvas.dimensions.distance); edgeCost = Math.round(measuredDistance / canvas.dimensions.distance);
if (ray.terrainRulerFinalState?.noDiagonals === 1) { if (ray.terrainRulerFinalState?.noDiagonals === 1) {
edgeCost = 1.5; edgeCost = 1.5;
@@ -343,8 +378,7 @@ function getNode(pos, cacheLayer, initialize = true) {
if (isDiagonal && edgeCost == 1) { if (isDiagonal && edgeCost == 1) {
edgeCost = 1.0001; 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) // 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 // TODO Account for difficult terrain
edgeCost = isDiagonal ? (use5105 ? 1.5 : 1.0001) : 1; edgeCost = isDiagonal ? (use5105 ? 1.5 : 1.0001) : 1;
@@ -365,17 +399,18 @@ function calculatePath(from, to, cacheLayer, previousWaypoints) {
startCost = (calcNoDiagonals(previousWaypoints) % 2) * 0.5; 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(); const previousNodes = new Set();
nextNodes.pushWithPriority( nextNodes.pushWithPriority({
{
node: getNode(from, cacheLayer), node: getNode(from, cacheLayer),
cost: startCost, cost: startCost,
estimated: startCost + estimateCost(from, to), estimated: startCost + estimateCost(from, to),
previous: null previous: null,
} });
);
while (nextNodes.hasNext()) { while (nextNodes.hasNext()) {
// Get node with cheapest estimate // Get node with cheapest estimate
@@ -394,7 +429,7 @@ function calculatePath(from, to, cacheLayer, previousWaypoints) {
node: neighborNode, node: neighborNode,
cost: currentNode.cost + edge.cost, cost: currentNode.cost + edge.cost,
estimated: currentNode.cost + edge.cost + estimateCost(neighborNode, to), estimated: currentNode.cost + edge.cost + estimateCost(neighborNode, to),
previous: currentNode previous: currentNode,
}; };
nextNodes.pushWithPriority(neighbor); nextNodes.pushWithPriority(neighbor);
} }
@@ -412,8 +447,8 @@ function buildPathNodes(lastNode) {
const pathNode = { const pathNode = {
node: currentNode.node, node: currentNode.node,
cost: currentNode.cost, cost: currentNode.cost,
next: previousNode next: previousNode,
} };
previousNode = pathNode; previousNode = pathNode;
currentNode = currentNode.previous; currentNode = currentNode.previous;
} }
@@ -444,7 +479,7 @@ function stepCollidesWithWall(from, to, tokenData) {
if (isModuleActive("levels")) { if (isModuleActive("levels")) {
stepStart.z = tokenData.elevation; stepStart.z = tokenData.elevation;
stepEnd.z = tokenData.elevation; stepEnd.z = tokenData.elevation;
return _levels.testCollision(stepStart, stepEnd, "collision") return _levels.testCollision(stepStart, stepEnd, "collision");
} else { } else {
return canvas.walls.checkCollision(new Ray(stepStart, stepEnd)); return canvas.walls.checkCollision(new Ray(stepStart, stepEnd));
} }
@@ -456,8 +491,7 @@ export function wipePathfindingCache() {
GridlessPathfinding.free(pathfinder); GridlessPathfinding.free(pathfinder);
} }
gridlessPathfinders.clear(); gridlessPathfinders.clear();
if (debugGraphics) if (debugGraphics) debugGraphics.removeChildren().forEach(c => c.destroy());
debugGraphics.removeChildren().forEach(c => c.destroy());
} }
export function initializePathfinding() { export function initializePathfinding() {
@@ -467,22 +501,23 @@ export function initializePathfinding() {
export function startBackgroundCaching(token) { export function startBackgroundCaching(token) {
// Background caching isn't yet supported for gridless scenes // Background caching isn't yet supported for gridless scenes
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) return;
return;
if (game.user.isGM || game.settings.get(settingsKey, "allowPathfinding")) { if (game.user.isGM || game.settings.get(settingsKey, "allowPathfinding")) {
cache.startBackgroundCaching(token); cache.startBackgroundCaching(token);
} }
} }
function paintGriddedPathfindingDebug(firstNode, tokenData) { function paintGriddedPathfindingDebug(firstNode, tokenData) {
if (!CONFIG.debug.dragRuler) if (!CONFIG.debug.dragRuler) return;
return;
debugGraphics.removeChildren().forEach(c => c.destroy()); debugGraphics.removeChildren().forEach(c => c.destroy());
let currentNode = firstNode; let currentNode = firstNode;
while (currentNode) { while (currentNode) {
let text = new PIXI.Text(currentNode.cost.toFixed(1)); 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.anchor.set(0.5, 1.0);
text.x = pixels.x; text.x = pixels.x;
text.y = pixels.y; text.y = pixels.y;
@@ -492,8 +527,7 @@ function paintGriddedPathfindingDebug(firstNode, tokenData) {
} }
function paintGridlessPathfindingDebug(pathfinder) { function paintGridlessPathfindingDebug(pathfinder) {
if (!CONFIG.debug.dragRuler) if (!CONFIG.debug.dragRuler) return;
return;
debugGraphics.removeChildren().forEach(c => c.destroy()); debugGraphics.removeChildren().forEach(c => c.destroy());
let graphic = new PIXI.Graphics(); let graphic = new PIXI.Graphics();
+81 -44
View File
@@ -1,6 +1,10 @@
import {currentSpeedProvider, getColorForDistanceAndToken, getRangesFromSpeedProvider} from "./api.js"; import {
currentSpeedProvider,
getColorForDistanceAndToken,
getRangesFromSpeedProvider,
} from "./api.js";
import {getHexSizeSupportTokenGridCenter} from "./compatibility.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 {getMovementHistory} from "./movement_tracking.js";
import {settingsKey} from "./settings.js"; import {settingsKey} from "./settings.js";
import {getSnapPointForEntity} from "./util.js"; import {getSnapPointForEntity} from "./util.js";
@@ -24,8 +28,7 @@ export function extendRuler() {
async moveToken(event) { async moveToken(event) {
// Disable moveToken if Drag Ruler is active // Disable moveToken if Drag Ruler is active
if (!this.isDragRuler) if (!this.isDragRuler) return await super.moveToken(event);
return await super.moveToken(event);
} }
toJSON() { toJSON() {
@@ -45,16 +48,18 @@ export function extendRuler() {
update(data) { update(data) {
// Don't show a GMs drag ruler to non GM players // Don't show a GMs drag ruler to non GM players
if (data.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; return;
if (data.draggedEntity) { if (data.draggedEntity) {
if (data.draggedEntityIsToken) if (data.draggedEntityIsToken) this.draggedEntity = canvas.tokens.get(data.draggedEntity);
this.draggedEntity = canvas.tokens.get(data.draggedEntity); else this.draggedEntity = canvas.templates.get(data.draggedEntity);
else } else {
this.draggedEntity = canvas.templates.get(data.draggedEntity);
}
else {
this.draggedEntity = undefined; this.draggedEntity = undefined;
} }
@@ -64,11 +69,9 @@ export function extendRuler() {
measure(destination, options = {}) { measure(destination, options = {}) {
if (this.isDragRuler) { 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 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) if (this.user !== game.user) options.snap = false;
options.snap = false;
return measure.call(this, destination, options); return measure.call(this, destination, options);
} } else {
else {
return super.measure(destination, options); return super.measure(destination, options);
} }
} }
@@ -86,11 +89,13 @@ export function extendRuler() {
} }
this.waypoints.push(new PIXI.Point(point.x, point.y)); this.waypoints.push(new PIXI.Point(point.x, point.y));
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
this.waypoints.filter(waypoint => waypoint.isPathfinding).forEach(waypoint => waypoint.isPathfinding = false); this.waypoints
.filter(waypoint => waypoint.isPathfinding)
.forEach(waypoint => (waypoint.isPathfinding = false));
} }
dragRulerAddWaypointHistory(waypoints) { dragRulerAddWaypointHistory(waypoints) {
waypoints.forEach(waypoint => waypoint.isPrevious = true); waypoints.forEach(waypoint => (waypoint.isPrevious = true));
this.waypoints = this.waypoints.concat(waypoints); this.waypoints = this.waypoints.concat(waypoints);
for (const waypoint of waypoints) { for (const waypoint of waypoints) {
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
@@ -102,30 +107,51 @@ export function extendRuler() {
this.labels.removeChildren().forEach(c => c.destroy()); this.labels.removeChildren().forEach(c => c.destroy());
} }
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) { dragRulerDeleteWaypoint(
event = {
preventDefault: () => {
return;
},
},
options = {},
) {
this.dragRulerRemovePathfindingWaypoints(); this.dragRulerRemovePathfindingWaypoints();
options.snap = options.snap ?? true; options.snap = options.snap ?? true;
if (this.waypoints.filter(w => !w.isPrevious).length > 1) { if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
event.preventDefault(); event.preventDefault();
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens); const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(
canvas.tokens,
);
const rulerOffset = this.rulerOffset; const rulerOffset = this.rulerOffset;
// Options are not passed to _removeWaypoint in vanilla Foundry. // 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) // 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}); game.user.broadcastActivity({ruler: this});
} } else {
else {
this.dragRulerAbortDrag(event); this.dragRulerAbortDrag(event);
} }
} }
dragRulerRemovePathfindingWaypoints() { 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); this.waypoints = this.waypoints.filter(waypoint => !waypoint.isPathfinding);
} }
dragRulerAbortDrag(event={preventDefault: () => {return}}) { dragRulerAbortDrag(
event = {
preventDefault: () => {
return;
},
},
) {
const token = this.draggedEntity; const token = this.draggedEntity;
this._endMeasurement(); this._endMeasurement();
@@ -139,10 +165,8 @@ export function extendRuler() {
} }
async dragRulerRecalculate(tokenIds) { async dragRulerRecalculate(tokenIds) {
if (this._state !== Ruler.STATES.MEASURING) if (this._state !== Ruler.STATES.MEASURING) return;
return; if (tokenIds && !tokenIds.includes(this.draggedEntity.id)) return;
if (tokenIds && !tokenIds.includes(this.draggedEntity.id))
return;
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious); const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
this.dragRulerClearWaypoints(); this.dragRulerClearWaypoints();
if (game.settings.get(settingsKey, "enableMovementHistory")) if (game.settings.get(settingsKey, "enableMovementHistory"))
@@ -155,8 +179,7 @@ export function extendRuler() {
} }
static dragRulerGetRaysFromWaypoints(waypoints, destination) { static dragRulerGetRaysFromWaypoints(waypoints, destination) {
if ( destination ) if (destination) waypoints = waypoints.concat([destination]);
waypoints = waypoints.concat([destination]);
return waypoints.slice(1).map((wp, i) => { 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); ray.isPrevious = Boolean(waypoints[i].isPrevious);
@@ -165,48 +188,62 @@ export function extendRuler() {
} }
dragRulerGetColorForDistance(distance) { dragRulerGetColorForDistance(distance) {
if (!this.isDragRuler) if (!this.isDragRuler) return this.color;
return this.color;
if (!this.draggedEntity.actor) { if (!this.draggedEntity.actor) {
return this.color; return this.color;
} }
// Don't apply colors if the current user doesn't have at least observer permissions // Don't apply colors if the current user doesn't have at least observer permissions
if (this.draggedEntity.actor.permission < 2) { if (this.draggedEntity.actor.permission < 2) {
// If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway // If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway
if (!(this.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; return this.color;
} }
distance = Math.round(distance * 100) / 100; distance = Math.round(distance * 100) / 100;
if (!this.dragRulerRanges) if (!this.dragRulerRanges)
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedEntity); 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 entity = this.draggedEntity;
const isToken = entity instanceof Token; const isToken = entity instanceof Token;
if (isToken && !currentSpeedProvider.usesRuler(entity)) if (isToken && !currentSpeedProvider.usesRuler(entity)) return;
return;
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
ruler.clear(); ruler.clear();
ruler._state = Ruler.STATES.STARTING; ruler._state = Ruler.STATES.STARTING;
let entityCenter; 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); entityCenter = getHexSizeSupportTokenGridCenter(entity);
else else entityCenter = entity.center;
entityCenter = entity.center;
if (isToken && game.settings.get(settingsKey, "enableMovementHistory")) if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
ruler.dragRulerAddWaypointHistory(getMovementHistory(entity)); ruler.dragRulerAddWaypointHistory(getMovementHistory(entity));
ruler.dragRulerAddWaypoint(entityCenter, {snap: false}); ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens); const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(
const destination = {x: mousePosition.x + ruler.rulerOffset.x, y: mousePosition.y + ruler.rulerOffset.y}; canvas.tokens,
if (measureImmediately) );
ruler.measure(destination, options); const destination = {
x: mousePosition.x + ruler.rulerOffset.x,
y: mousePosition.y + ruler.rulerOffset.y,
};
if (measureImmediately) ruler.measure(destination, options);
} }
dragRulerSendState() { dragRulerSendState() {
game.user.broadcastActivity({ game.user.broadcastActivity({
ruler: this.toJSON() ruler: this.toJSON(),
}); });
} }
} }
+102 -92
View File
@@ -1,6 +1,11 @@
import {availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider} from "./api.js"; import {
import {SpeedProvider} from "./speed_provider.js" availableSpeedProviders,
import {wipePathfindingCache} from "./pathfinding.js" currentSpeedProvider,
getDefaultSpeedProvider,
updateSpeedProvider,
} from "./api.js";
import {SpeedProvider} from "./speed_provider.js";
import {wipePathfindingCache} from "./pathfinding.js";
import {early_isGM} from "./util.js"; import {early_isGM} from "./util.js";
export const settingsKey = "drag-ruler"; export const settingsKey = "drag-ruler";
@@ -20,14 +25,14 @@ export function registerSettings() {
scope: "world", scope: "world",
config: false, config: false,
type: String, type: String,
default: "fresh install" default: "fresh install",
}) });
game.settings.register(settingsKey, "clientDataVersion", { game.settings.register(settingsKey, "clientDataVersion", {
scope: "client", scope: "client",
config: false, config: false,
type: String, type: String,
default: "fresh install" default: "fresh install",
}); });
game.settings.register(settingsKey, "rightClickAction", { game.settings.register(settingsKey, "rightClickAction", {
@@ -68,7 +73,7 @@ export function registerSettings() {
config: true, config: true,
type: Boolean, type: Boolean,
default: true, default: true,
}) });
game.settings.register(settingsKey, "showGMRulerToPlayers", { game.settings.register(settingsKey, "showGMRulerToPlayers", {
name: "drag-ruler.settings.showGMRulerToPlayers.name", name: "drag-ruler.settings.showGMRulerToPlayers.name",
@@ -77,7 +82,7 @@ export function registerSettings() {
config: true, config: true,
type: Boolean, type: Boolean,
default: true, default: true,
}) });
game.settings.register(settingsKey, "enableMovementHistory", { game.settings.register(settingsKey, "enableMovementHistory", {
name: "drag-ruler.settings.enableMovementHistory.name", name: "drag-ruler.settings.enableMovementHistory.name",
@@ -134,7 +139,7 @@ export function registerSettings() {
type: String, type: String,
default: getDefaultSpeedProvider(), default: getDefaultSpeedProvider(),
onChange: updateSpeedProvider, onChange: updateSpeedProvider,
}) });
game.settings.registerMenu(settingsKey, "speedProviderSettings", { game.settings.registerMenu(settingsKey, "speedProviderSettings", {
name: "drag-ruler.settings.speedProviderSettings.name", name: "drag-ruler.settings.speedProviderSettings.name",
@@ -143,7 +148,7 @@ export function registerSettings() {
icon: "fas fa-tachometer-alt", icon: "fas fa-tachometer-alt",
type: SpeedProviderSettings, type: SpeedProviderSettings,
restricted: false, restricted: false,
}) });
} }
class SpeedProviderSettings extends FormApplication { class SpeedProviderSettings extends FormApplication {
@@ -153,43 +158,44 @@ class SpeedProviderSettings extends FormApplication {
title: game.i18n.localize("drag-ruler.settings.speedProviderSettings.windowTitle"), title: game.i18n.localize("drag-ruler.settings.speedProviderSettings.windowTitle"),
template: "modules/drag-ruler/templates/speed_provider_settings.html", template: "modules/drag-ruler/templates/speed_provider_settings.html",
width: 600, width: 600,
}) });
} }
getData(options = {}) { getData(options = {}) {
const data = {} const data = {};
data.isGM = game.user.isGM data.isGM = game.user.isGM;
const selectedProvider = currentSpeedProvider.id const selectedProvider = currentSpeedProvider.id;
// Insert all speed providers into the template data // Insert all speed providers into the template data
data.providers = Object.values(availableSpeedProviders).map(speedProvider => { data.providers = Object.values(availableSpeedProviders).map(speedProvider => {
const provider = {} const provider = {};
provider.id = speedProvider.id provider.id = speedProvider.id;
provider.hasSettings = speedProvider instanceof SpeedProvider provider.hasSettings = speedProvider instanceof SpeedProvider;
if (provider.hasSettings) if (provider.hasSettings) provider.settings = enumerateProviderSettings(speedProvider);
provider.settings = enumerateProviderSettings(speedProvider) let dotPosition = provider.id.indexOf(".");
let dotPosition = provider.id.indexOf(".") if (dotPosition === -1) dotPosition = provider.id.length;
if (dotPosition === -1) const type = provider.id.substring(0, dotPosition);
dotPosition = provider.id.length const id = provider.id.substring(dotPosition + 1);
const type = provider.id.substring(0, dotPosition)
const id = provider.id.substring(dotPosition + 1)
if (type === "native") { if (type === "native") {
provider.selectTitle = game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.choices.native") provider.selectTitle = game.i18n.localize(
} "drag-ruler.settings.speedProviderSettings.speedProvider.choices.native",
else { );
let name } else {
let name;
if (type === "module") { if (type === "module") {
name = game.modules.get(id).data.title name = game.modules.get(id).data.title;
} else {
name = game.system.data.title;
} }
else { provider.selectTitle = game.i18n.format(
name = game.system.data.title `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;
provider.isSelected = provider.id === selectedProvider });
return provider data.selectedProviderName = data.providers.find(provider => provider.isSelected).selectTitle;
})
data.selectedProviderName = data.providers.find(provider => provider.isSelected).selectTitle
data.providerSelection = { data.providerSelection = {
id: "speedProvider", id: "speedProvider",
@@ -197,122 +203,126 @@ class SpeedProviderSettings extends FormApplication {
hint: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.hint"), hint: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.hint"),
type: String, type: String,
choices: data.providers.reduce((choices, provider) => { choices: data.providers.reduce((choices, provider) => {
choices[provider.id] = provider.selectTitle choices[provider.id] = provider.selectTitle;
return choices return choices;
}, {}), }, {}),
value: selectedProvider, value: selectedProvider,
isCheckbox: false, isCheckbox: false,
isSelect: true, isSelect: true,
isRange: false, isRange: false,
} };
return data return data;
} }
async _updateObject(event, formData) { 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)) { for (let [key, value] of Object.entries(formData)) {
// Check if this is color, convert the value to an integer // Check if this is color, convert the value to an integer
const splitKey = key.split(".", 3) const splitKey = key.split(".", 3);
if (splitKey[0] !== "native") if (splitKey[0] !== "native") splitKey.shift();
splitKey.shift()
if (splitKey.length >= 2 && splitKey[1] == "color") { 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 // Don't change settings for speed providers that aren't currently active
if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider)) if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider)) continue;
continue
// Get the key for the current setting // Get the key for the current setting
let setting let setting;
if (key === "speedProvider") if (key === "speedProvider") setting = "speedProvider";
setting = "speedProvider" else setting = `speedProviders.${key}`;
else
setting = `speedProviders.${key}`
// Get the old setting value // 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) // Only update the setting if it has been changed (this leaves the default in place if it hasn't been touched)
if (value !== oldValue) if (value !== oldValue) game.settings.set(settingsKey, setting, value);
game.settings.set(settingsKey, setting, value)
} }
// Activate the configured speed provider // Activate the configured speed provider
updateSpeedProvider() updateSpeedProvider();
} }
activateListeners(html) { activateListeners(html) {
super.activateListeners(html) super.activateListeners(html);
html.find("select[name=speedProvider]").change(this.onSpeedProviderChange.bind(this)) html.find("select[name=speedProvider]").change(this.onSpeedProviderChange.bind(this));
} }
onSpeedProviderChange(event) { onSpeedProviderChange(event) {
// Hide all module settings // 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 // 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 // Recalculate window height
this.element[0].style.height = null this.element[0].style.height = null;
this.position.height = undefined this.position.height = undefined;
} }
} }
function toDomHex(value) { function toDomHex(value) {
const hex = value.toString(16) const hex = value.toString(16);
return "#" + "0".repeat(Math.max(0, 6 - hex.length)) + hex return "#" + "0".repeat(Math.max(0, 6 - hex.length)) + hex;
} }
function enumerateProviderSettings(provider) { function enumerateProviderSettings(provider) {
const colorSettings = [] const colorSettings = [];
const unreachableColor = {id: "unreachable", name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name"} const unreachableColor = {
id: "unreachable",
name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name",
};
// Resolve settings for the colors // Resolve settings for the colors
for (const color of provider.colors.concat([unreachableColor])) { for (const color of provider.colors.concat([unreachableColor])) {
// Localize the name, if avaliable. If no name is available use the id as name // 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 const colorName = color.name ? game.i18n.localize(color.name) : color.id;
let hint let hint;
if (color === unreachableColor) 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 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({ colorSettings.push({
id: `${provider.id}.color.${color.id}`, id: `${provider.id}.color.${color.id}`,
name: game.i18n.format("drag-ruler.settings.speedProviderSettings.color.name", {colorName}), name: game.i18n.format("drag-ruler.settings.speedProviderSettings.color.name", {colorName}),
hint: hint, hint: hint,
type: Number, 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, isCheckbox: false,
isSelect: false, isSelect: false,
isRange: false, isRange: false,
isColor: true, isColor: true,
}) });
} }
// Prepare regular settings // Prepare regular settings
const settings = [] const settings = [];
for (const setting of provider.settings) { for (const setting of provider.settings) {
try { try {
if (setting.scope === "world" && !game.user.isGM) if (setting.scope === "world" && !game.user.isGM) continue;
continue const s = duplicate(setting);
const s = duplicate(setting) s.id = `${provider.id}.setting.${s.id}`;
s.id = `${provider.id}.setting.${s.id}` s.name = game.i18n.localize(s.name);
s.name = game.i18n.localize(s.name) s.hint = game.i18n.localize(s.hint);
s.hint = game.i18n.localize(s.hint) s.value = provider.getSetting(setting.id);
s.value = provider.getSetting(setting.id) s.type = setting.type instanceof Function ? setting.type.name : "String";
s.type = setting.type instanceof Function ? setting.type.name : "String" s.isCheckbox = setting.type === Boolean;
s.isCheckbox = setting.type === Boolean s.isSelect = s.choices !== undefined;
s.isSelect = s.choices !== undefined s.isRange = setting.type === Number && s.range;
s.isRange = (setting.type === Number) && s.range s.isColor = false;
s.isColor = false settings.push(s);
settings.push(s) } catch (e) {
} console.warn(
catch (e) { `Drag Ruler | The following error occured while rendering setting "${setting.id}" of module/system "${this.id}. It won't be displayed.`,
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) console.error(e);
} }
} }
return settings.concat(colorSettings) return settings.concat(colorSettings);
} }
+14 -7
View File
@@ -11,8 +11,13 @@ Hooks.once("socketlib.ready", () => {
export function updateCombatantDragRulerFlags(combat, updates) { export function updateCombatantDragRulerFlags(combat, updates) {
const combatId = combat.id; const combatId = combat.id;
// TODO Check if canvas.tokens.get is still neccessary in future foundry versions // TODO Check if canvas.tokens.get is still neccessary in future foundry versions
return socket.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates) return socket
.then(() => currentSpeedProvider.onMovementHistoryUpdate(updates.map(update => canvas.tokens.get(combat.combatants.get(update._id).token.id)))); .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) { async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
@@ -21,12 +26,15 @@ async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
const requestedUpdates = updates.length; const requestedUpdates = updates.length;
updates = updates.filter(update => { updates = updates.filter(update => {
const actor = combat.combatants.get(update._id).actor; const actor = combat.combatants.get(update._id).actor;
if (!actor) if (!actor) return false;
return false;
return actor.testUserPermission(user, "OWNER"); return actor.testUserPermission(user, "OWNER");
}); });
if (updates.length !== requestedUpdates) { 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 => { updates = updates.map(update => {
return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}}; return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}};
@@ -40,6 +48,5 @@ export function recalculate(tokens) {
function _socketRecalculate(tokenIds) { function _socketRecalculate(tokenIds) {
const ruler = canvas.controls.ruler; const ruler = canvas.controls.ruler;
if (ruler.isDragRuler) if (ruler.isDragRuler) ruler.dragRulerRecalculate(tokenIds);
ruler.dragRulerRecalculate(tokenIds);
} }
+32 -29
View File
@@ -1,5 +1,5 @@
import {settingsKey} from "./settings.js" import {settingsKey} from "./settings.js";
import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js" import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js";
/** /**
* Base class for all speed providers. * Base class for all speed providers.
@@ -19,7 +19,7 @@ export class SpeedProvider {
* Implementing this method is required for all speed providers * Implementing this method is required for all speed providers
*/ */
get colors() { 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 * Implementing this method is required for all speed providers
*/ */
getRanges(token) { 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 * Implementing this method is optional and only needs to be done if you want to provide custom provider settings
*/ */
get 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. * Implementing this method is optional and only needs to be done if you want to provide a custom default for that color.
*/ */
get defaultUnreachableColor() { get defaultUnreachableColor() {
return 0xFF0000 return 0xff0000;
} }
/** /**
@@ -74,7 +74,7 @@ export class SpeedProvider {
options.token = token; options.token = token;
const costs = area.map(space => terrainRuler.getCost(space.x, space.y, options)); const costs = area.map(space => terrainRuler.getCost(space.x, space.y, options));
// Return the maximum of the costs // Return the maximum of the costs
return costs.reduce((max, current) => Math.max(max, current)) return costs.reduce((max, current) => Math.max(max, current));
} }
/** /**
@@ -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. * Implementing this method is optional and only needs to be done if you want to disable Drag Ruler for some tokens.
*/ */
usesRuler(token) { usesRuler(token) {
return true return true;
} }
/** /**
@@ -104,13 +104,14 @@ export class SpeedProvider {
*/ */
getSetting(settingId) { getSetting(settingId) {
try { try {
return game.settings.get(settingsKey, `speedProviders.${this.id}.setting.${settingId}`) return game.settings.get(settingsKey, `speedProviders.${this.id}.setting.${settingId}`);
} } catch (e) {
catch (e) {
if (this.settings.some(setting => setting.id === settingId)) { 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 * This function should neither be called or overridden by speed provider implementations
*/ */
constructor(id) { constructor(id) {
this.id = id this.id = id;
} }
} }
export class GenericSpeedProvider extends SpeedProvider { export class GenericSpeedProvider extends SpeedProvider {
get colors() { get colors() {
return [ return [
{id: "walk", default: 0x00FF00, name: "drag-ruler.genericSpeedProvider.speeds.walk"}, {id: "walk", default: 0x00ff00, name: "drag-ruler.genericSpeedProvider.speeds.walk"},
{id: "dash", default: 0xFFFF00, name: "drag-ruler.genericSpeedProvider.speeds.dash"} {id: "dash", default: 0xffff00, name: "drag-ruler.genericSpeedProvider.speeds.dash"},
] ];
} }
getRanges(token) { getRanges(token) {
const speedAttribute = this.getSetting("speedAttribute") const speedAttribute = this.getSetting("speedAttribute");
if (!speedAttribute) if (!speedAttribute) return [];
return []
const tokenSpeed = parseFloat(getProperty(token, speedAttribute)); const tokenSpeed = parseFloat(getProperty(token, speedAttribute));
if (tokenSpeed === undefined) { 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).`) console.warn(
return [] `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") const dashMultiplier = this.getSetting("dashMultiplier");
if (!dashMultiplier) if (!dashMultiplier) return [{range: tokenSpeed, color: "walk"}];
return [{range: tokenSpeed, color: "walk"}] return [
return [{range: tokenSpeed, color: "walk"}, {range: tokenSpeed * dashMultiplier, color: "dash"}] {range: tokenSpeed, color: "walk"},
{range: tokenSpeed * dashMultiplier, color: "dash"},
];
} }
get settings() { get settings() {
@@ -167,7 +170,7 @@ export class GenericSpeedProvider extends SpeedProvider {
config: true, config: true,
type: Number, type: Number,
default: getDefaultDashMultiplier(), default: getDefaultDashMultiplier(),
} },
] ];
} }
} }
+2 -2
View File
@@ -24,13 +24,13 @@ export function getDefaultSpeedAttribute() {
case "splittermond": case "splittermond":
return "actor.derivedValues.speed.value"; return "actor.derivedValues.speed.value";
} }
return "" return "";
} }
export function getDefaultDashMultiplier() { export function getDefaultDashMultiplier() {
switch (game.system.id) { switch (game.system.id) {
case "swade": case "swade":
return 0 return 0;
case "dcc": case "dcc":
case "dnd4e": case "dnd4e":
case "dnd5e": case "dnd5e":
+67 -71
View File
@@ -1,10 +1,10 @@
import {getPixelsFromGridPosition} from "./foundry_fixes.js" import {getPixelsFromGridPosition} from "./foundry_fixes.js";
import {findVertexSnapPoint} from "./hex_support.js"; import {findVertexSnapPoint} from "./hex_support.js";
import {disableSnap} from "./keybindings.js"; import {disableSnap} from "./keybindings.js";
export function* zip(it1, it2) { export function* zip(it1, it2) {
for (let i = 0; i < Math.min(it1.length, it2.length); i++) { for (let i = 0; i < Math.min(it1.length, it2.length); i++) {
yield [it1[i], it2[i]] yield [it1[i], it2[i]];
} }
} }
@@ -29,13 +29,14 @@ export function sum(arr) {
export function buildSnapPointTokenData(token) { export function buildSnapPointTokenData(token) {
const tokenData = { const tokenData = {
width: token.data.width, width: token.data.width,
height: token.data.height height: token.data.height,
}; };
if (isModuleActive("hex-size-support")) { if (isModuleActive("hex-size-support")) {
tokenData.hexSizeSupport = {}; tokenData.hexSizeSupport = {};
tokenData.hexSizeSupport.altSnappingFlag = CONFIG.hexSizeSupport.getAltSnappingFlag(token); 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"); 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?.altSnappingFlag) {
if (tokenData.hexSizeSupport.borderSize % 2 === 0) { if (tokenData.hexSizeSupport.borderSize % 2 === 0) {
const snapPoint = findVertexSnapPoint(x, y, tokenData.hexSizeSupport.altOrientationFlag); 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 { } else {
return new PIXI.Point(...canvas.grid.getCenter(x, y))
}
}
else {
return new PIXI.Point(...canvas.grid.getCenter(x, y)); return new PIXI.Point(...canvas.grid.getCenter(x, y));
} }
} }
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y); const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
let cellX, cellY; let cellX, cellY;
if (tokenData.width % 2 === 0) if (tokenData.width % 2 === 0) cellX = x - canvas.grid.h / 2;
cellX = x - canvas.grid.h / 2; else cellX = x;
else if (tokenData.height % 2 === 0) cellY = y - canvas.grid.h / 2;
cellX = x; else cellY = y;
if (tokenData.height % 2 === 0)
cellY = y - canvas.grid.h / 2;
else
cellY = y;
const [centerX, centerY] = canvas.grid.getCenter(cellX, cellY); const [centerX, centerY] = canvas.grid.getCenter(cellX, cellY);
let snapX, snapY; let snapX, snapY;
// Tiny tokens can snap to the cells corners // 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 subGridHeight = Math.floor(canvas.grid.h / 2);
const subGridPosY = Math.floor(offsetY / subGridHeight); const subGridPosY = Math.floor(offsetY / subGridHeight);
snapY = topLeftY + (subGridPosY + 0.5) * 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; snapY = centerY;
} } else {
else {
snapY = centerY + canvas.grid.h / 2; snapY = centerY + canvas.grid.h / 2;
} }
return new PIXI.Point(snapX, snapY); return new PIXI.Point(snapX, snapY);
@@ -124,16 +117,13 @@ export function getSnapPointForMeasuredTemplate(x, y) {
export function getSnapPointForEntity(x, y, entity) { export function getSnapPointForEntity(x, y, entity) {
const isToken = entity instanceof Token; const isToken = entity instanceof Token;
if (isToken) if (isToken) return getSnapPointForToken(x, y, entity);
return getSnapPointForToken(x, y, entity); else return getSnapPointForMeasuredTemplate(x, y);
else
return getSnapPointForMeasuredTemplate(x, y);
} }
export function highlightTokenShape(position, shape, color, alpha) { export function highlightTokenShape(position, shape, color, alpha) {
const layer = canvas.grid.highlightLayers[this.name]; const layer = canvas.grid.highlightLayers[this.name];
if ( !layer ) if (!layer) return false;
return false;
const area = getAreaFromPositionAndShape(position, shape); const area = getAreaFromPositionAndShape(position, shape);
for (const space of area) { for (const space of area) {
const [x, y] = getPixelsFromGridPosition(space.x, space.y); const [x, y] = getPixelsFromGridPosition(space.x, space.y);
@@ -147,22 +137,19 @@ export function getAreaFromPositionAndShape(position, shape) {
let y = position.y + space.y; let y = position.y + space.y;
if (canvas.grid.isHex) { if (canvas.grid.isHex) {
let shiftedRow; let shiftedRow;
if (canvas.grid.grid.options.even) if (canvas.grid.grid.options.even) shiftedRow = 1;
shiftedRow = 1 else shiftedRow = 0;
else
shiftedRow = 0
if (canvas.grid.grid.options.columns) { if (canvas.grid.grid.options.columns) {
if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) { if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
y += 1; y += 1;
} }
} } else {
else {
if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) { if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) {
x += 1; x += 1;
} }
} }
} }
return {x, y} return {x, y};
}); });
} }
@@ -172,38 +159,51 @@ export function getTokenShape(token) {
export function getTokenShapeForTokenData(tokenData, scene = canvas.scene) { export function getTokenShapeForTokenData(tokenData, scene = canvas.scene) {
if (scene.data.gridType === CONST.GRID_TYPES.GRIDLESS) { if (scene.data.gridType === CONST.GRID_TYPES.GRIDLESS) {
return [{x: 0, y: 0}] return [{x: 0, y: 0}];
} } else if (scene.data.gridType === CONST.GRID_TYPES.SQUARE) {
else if (scene.data.gridType === CONST.GRID_TYPES.SQUARE) { const topOffset = -Math.floor(tokenData.height / 2);
const topOffset = -Math.floor(tokenData.height / 2) const leftOffset = -Math.floor(tokenData.width / 2);
const leftOffset = -Math.floor(tokenData.width / 2) const shape = [];
const shape = []
for (let y = 0; y < tokenData.height; y++) { for (let y = 0; y < tokenData.height; y++) {
for (let x = 0; x < tokenData.width; x++) { for (let x = 0; x < tokenData.width; x++) {
shape.push({x: x + leftOffset, y: y + topOffset}) shape.push({x: x + leftOffset, y: y + topOffset});
} }
} }
return shape return shape;
} } else {
else {
// Hex grids // Hex grids
if (game.modules.get("hex-size-support")?.active && tokenData.hexSizeSupport.altSnappingFlag) { if (game.modules.get("hex-size-support")?.active && tokenData.hexSizeSupport.altSnappingFlag) {
const borderSize = tokenData.hexSizeSupport.borderSize; const borderSize = tokenData.hexSizeSupport.borderSize;
let shape = [{x: 0, y: 0}]; let shape = [{x: 0, y: 0}];
if (borderSize >= 2) 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) 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) 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) 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) 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; return shape;
} } else {
else {
return [{x: 0, y: 0}]; return [{x: 0, y: 0}];
} }
} }
@@ -213,11 +213,10 @@ export function getTokenSize(token) {
let w, h; let w, h;
const hexSizeSupportBorderSize = token.data.flags["hex-size-support"]?.borderSize; const hexSizeSupportBorderSize = token.data.flags["hex-size-support"]?.borderSize;
if (hexSizeSupportBorderSize > 0) { if (hexSizeSupportBorderSize > 0) {
w = h = hexSizeSupportBorderSize w = h = hexSizeSupportBorderSize;
} } else {
else { w = token.data.width;
w = token.data.width h = token.data.height;
h = token.data.height
} }
return {w, h}; 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 // 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) { export function applyTokenSizeOffset(waypoints, token) {
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
return waypoints return waypoints;
} }
const tokenSize = getTokenSize(token); const tokenSize = getTokenSize(token);
@@ -237,21 +236,17 @@ export function applyTokenSizeOffset(waypoints, token) {
if (canvas.grid.grid.options.columns) { if (canvas.grid.grid.options.columns) {
if (tokenSize.w % 2 === 0) { if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2; waypointOffset.x = canvas.grid.w / 2;
if (!isAltOrientation) if (!isAltOrientation) waypointOffset.x *= -1;
waypointOffset.x *= -1;
} }
} } else {
else {
if (tokenSize.h % 2 === 0) { if (tokenSize.h % 2 === 0) {
waypointOffset.y = canvas.grid.h / 2; waypointOffset.y = canvas.grid.h / 2;
if (isAltOrientation) if (isAltOrientation) waypointOffset.y *= -1;
waypointOffset.y *= -1;
} }
} }
} }
// If hex size support isn't active leave the waypoints like they are // If hex size support isn't active leave the waypoints like they are
} } else {
else {
if (tokenSize.w % 2 === 0) { if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2; 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) { export function setSnapParameterOnOptions(sourceObject, options) {
@@ -269,8 +264,7 @@ export function setSnapParameterOnOptions(sourceObject, options) {
options.snapOverrideActive = true; options.snapOverrideActive = true;
options.snap = sourceObject.snapOverride.snap; options.snap = sourceObject.snapOverride.snap;
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
} } else {
else {
options.snap = !disableSnap; options.snap = !disableSnap;
} }
} }
@@ -280,7 +274,9 @@ export function isClose(a, b, delta) {
} }
export function getMeasurePosition() { 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 rulerOffset = canvas.controls.ruler.rulerOffset;
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}; const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
return measurePosition; return measurePosition;
@@ -294,5 +290,5 @@ export function early_isGM() {
} }
export function isModuleActive(moduleName) { export function isModuleActive(moduleName) {
return game.modules.get(moduleName)?.active return game.modules.get(moduleName)?.active;
} }