Always draw the ruler from the token's center for larger tokens (even on grids). Highlight all spaces occupied by larger tokens.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
## In development
|
||||
### New features
|
||||
- If the [Terrain Ruler module](https://foundryvtt.com/packages/terrain-ruler/) is installed and activated, Drag Ruler will now take difficult terrain that was placed with the [TerrainLayer module](https://foundryvtt.com/packages/TerrainLayer/) into account.
|
||||
- The ruler will now always be drawn from the tokens center (even for tokens larger than 1x1) on all grid types (on Hex Grids this requires the Hex Token Size Support module).
|
||||
- For tokens larger than 1x1 the highlighted path will now reflect the tokens size (on Hex Grids this requires the Hex Token Size Support module)
|
||||
- The GM's Drag Ruler can now be hidden from non GM players via a setting.
|
||||
- When multiple different colors apply to a single grid space because the path crosses itself the color representing ranges further away will take priortiy over colors representing closer ranges.
|
||||
|
||||
|
||||
+5
-21
@@ -1,30 +1,14 @@
|
||||
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
|
||||
import {getColorForDistance} from "./main.js"
|
||||
import {highlightTokenShape} from "./util.js"
|
||||
|
||||
export function getHexSizeSupportTokenGridCenter(token) {
|
||||
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token)
|
||||
const tokenCenter = {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y}
|
||||
if (token.getFlag("hex-size-support", "borderSize") % 2 === 1)
|
||||
return tokenCenter
|
||||
if (canvas.grid.grid.columns) {
|
||||
let hexOffset = canvas.grid.w / 2
|
||||
if (!CONFIG.hexSizeSupport.getAltOrientationFlag(token))
|
||||
hexOffset *= -1
|
||||
tokenCenter.x += hexOffset
|
||||
}
|
||||
else {
|
||||
let hexOffset = canvas.grid.h / 2
|
||||
if (CONFIG.hexSizeSupport.getAltOrientationFlag(token))
|
||||
hexOffset *= -1
|
||||
tokenCenter.y += hexOffset
|
||||
}
|
||||
return tokenCenter
|
||||
return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y}
|
||||
}
|
||||
|
||||
export function highlightMeasurementTerrainRuler(ray, startDistance) {
|
||||
for (const space of ray.terrainRulerVisitedSpaces) {
|
||||
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
|
||||
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}]) {
|
||||
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
|
||||
const color = getColorForDistance.call(this, startDistance, space.distance)
|
||||
canvas.grid.highlightPosition(this.name, {x, y, color: color})
|
||||
highlightTokenShape.call(this, space, tokenShape, color)
|
||||
}
|
||||
}
|
||||
|
||||
+32
-24
@@ -1,6 +1,7 @@
|
||||
import {highlightMeasurementTerrainRuler} from "./compatibility.js";
|
||||
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
||||
import {getColorForDistance} from "./main.js"
|
||||
import {zip} from "./util.js"
|
||||
import {applyTokenSizeOffset, getSnapPointForToken, getTokenShape, getTokenSize, highlightTokenShape, zip} from "./util.js";
|
||||
|
||||
// This is a modified version of Ruler.moveToken from foundry 0.7.9
|
||||
export async function moveTokens(draggedToken, selectedTokens) {
|
||||
@@ -104,9 +105,12 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
return []
|
||||
|
||||
if (snap)
|
||||
destination = new PIXI.Point(...canvas.grid.getCenter(destination.x, destination.y));
|
||||
destination = getSnapPointForToken(destination.x, destination.y, this.draggedToken)
|
||||
|
||||
const waypoints = this.waypoints.concat([destination]);
|
||||
const centeredWaypoints = waypoints.map(w => new PIXI.Point(...canvas.grid.getCenter(w.x, w.y)))
|
||||
// Move the waypoints to the center of the grid if a size is used that measures from edge to edge
|
||||
const centeredWaypoints = applyTokenSizeOffset(waypoints, this.draggedToken)
|
||||
|
||||
const r = this.ruler;
|
||||
this.destination = destination;
|
||||
|
||||
@@ -138,10 +142,10 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
|
||||
let totalDistance = 0;
|
||||
for (let [i, d] of distances.entries()) {
|
||||
let s = segments[i];
|
||||
let s = centeredSegments[i];
|
||||
s.startDistance = totalDistance
|
||||
totalDistance += d;
|
||||
s.last = i === (segments.length - 1);
|
||||
s.last = i === (centeredSegments.length - 1);
|
||||
s.distance = d;
|
||||
s.text = this._getSegmentLabel(d, totalDistance, s.last);
|
||||
}
|
||||
@@ -157,27 +161,28 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
rulerColor = getColorForDistance.call(this, totalDistance)
|
||||
else
|
||||
rulerColor = this.color
|
||||
const shape = getTokenShape(this.draggedToken)
|
||||
for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
|
||||
const { ray, label, text, last } = s;
|
||||
const { label, text, last } = cs;
|
||||
|
||||
// Draw line segment
|
||||
r.lineStyle(6, 0x000000, 0.5).moveTo(ray.A.x, ray.A.y).lineTo(ray.B.x, ray.B.y)
|
||||
.lineStyle(4, rulerColor, 0.25).moveTo(ray.A.x, ray.A.y).lineTo(ray.B.x, ray.B.y);
|
||||
r.lineStyle(6, 0x000000, 0.5).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y)
|
||||
.lineStyle(4, rulerColor, 0.25).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y);
|
||||
|
||||
// Draw the distance label just after the endpoint of the segment
|
||||
if (label) {
|
||||
label.text = text;
|
||||
label.alpha = last ? 1.0 : 0.5;
|
||||
label.visible = true;
|
||||
let labelPosition = ray.project((ray.distance + 50) / ray.distance);
|
||||
let labelPosition = cs.ray.project((cs.ray.distance + 50) / cs.ray.distance);
|
||||
label.position.set(labelPosition.x, labelPosition.y);
|
||||
}
|
||||
|
||||
// Highlight grid positions
|
||||
if (terrainRulerAvailable)
|
||||
highlightMeasurementTerrainRuler.call(this, cs.ray, s.startDistance)
|
||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape)
|
||||
else
|
||||
highlightMeasurementNative.call(this, ray, s.startDistance);
|
||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape);
|
||||
}
|
||||
|
||||
// Draw endpoints
|
||||
@@ -189,7 +194,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
return segments;
|
||||
}
|
||||
|
||||
export function highlightMeasurementNative(ray, startDistance) {
|
||||
export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0, y: 0}]) {
|
||||
const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1;
|
||||
const nMax = Math.max(Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), 1);
|
||||
const tMax = Array.fromRange(nMax+1).map(t => t / nMax);
|
||||
@@ -198,7 +203,7 @@ export function highlightMeasurementNative(ray, startDistance) {
|
||||
let prior = null;
|
||||
|
||||
// Iterate over ray portions
|
||||
for ( let [i, t] of tMax.entries() ) {
|
||||
for ( let [i, t] of tMax.reverse().entries() ) {
|
||||
let {x, y} = ray.project(t);
|
||||
|
||||
// Get grid position
|
||||
@@ -208,23 +213,26 @@ export function highlightMeasurementNative(ray, startDistance) {
|
||||
|
||||
// Highlight the grid position
|
||||
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
|
||||
let subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||
let color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
canvas.grid.highlightPosition(this.name, {x: xg, y: yg, color: color});
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
|
||||
// Skip the first one
|
||||
prior = [x1, y1];
|
||||
if ( i === 0 ) continue;
|
||||
|
||||
// If the positions are not neighbors, also highlight their halfway point
|
||||
if ( !canvas.grid.isNeighbor(x0, y0, x1, y1) ) {
|
||||
let th = tMax[i - 1] + (0.5 / nMax);
|
||||
if (i > 0 && !canvas.grid.isNeighbor(x0, y0, x1, y1)) {
|
||||
let th = tMax[i - 1] - (0.5 / nMax);
|
||||
let {x, y} = ray.project(th);
|
||||
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
||||
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
|
||||
subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
||||
color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
canvas.grid.highlightPosition(this.name, {x: xgh, y: ygh, color: color});
|
||||
}
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color);
|
||||
}
|
||||
|
||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -6,6 +6,7 @@ import {measure, moveTokens, onMouseMove} from "./foundry_imports.js"
|
||||
import {performMigrations} from "./migration.js"
|
||||
import {registerSettings, settingsKey} from "./settings.js"
|
||||
import {SpeedProvider} from "./speed_provider.js"
|
||||
import { getSnapPointForToken } from "./util.js"
|
||||
|
||||
Hooks.once("init", () => {
|
||||
registerSettings()
|
||||
@@ -158,7 +159,7 @@ function onTokenLeftDragStart(event) {
|
||||
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
|
||||
tokenCenter = getHexSizeSupportTokenGridCenter(this)
|
||||
else
|
||||
tokenCenter = {x: this.x + canvas.grid.grid.w / 2, y: this.y + canvas.grid.grid.h / 2}
|
||||
tokenCenter = this.center
|
||||
ruler.clear();
|
||||
ruler._state = Ruler.STATES.STARTING;
|
||||
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y}
|
||||
@@ -216,10 +217,8 @@ function onRulerMoveToken(event) {
|
||||
|
||||
function addWaypoint(point, snap=true) {
|
||||
if (snap)
|
||||
point = canvas.grid.getCenter(point.x, point.y);
|
||||
else
|
||||
point = [point.x, point.y]
|
||||
this.waypoints.push(new PIXI.Point(point[0], point[1]));
|
||||
point = getSnapPointForToken(point.x, point.y, this.draggedToken)
|
||||
this.waypoints.push(new PIXI.Point(point.x, point.y));
|
||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||
}
|
||||
|
||||
|
||||
+134
@@ -1,5 +1,139 @@
|
||||
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
|
||||
|
||||
export function* zip(it1, it2) {
|
||||
for (let i = 0;i < Math.min(it1.length, it2.length);i++) {
|
||||
yield [it1[i], it2[i]]
|
||||
}
|
||||
}
|
||||
|
||||
export function getSnapPointForToken(x, y, token) {
|
||||
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
||||
if (token.getFlag("hex-size-support", "borderSize") % 2 === 0) {
|
||||
const snapPoint = CONFIG.hexSizeSupport.findVertexSnapPoint(x, y, token, canvas.grid.grid)
|
||||
return new PIXI.Point(snapPoint.x, snapPoint.y)
|
||||
}
|
||||
else {
|
||||
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
||||
}
|
||||
}
|
||||
if (canvas.grid.isHex || token.data.width % 2 === 1) {
|
||||
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
||||
}
|
||||
const [snappedX, snappedY] = canvas.grid.getCenter(x - canvas.grid.w / 2, y - canvas.grid.h / 2)
|
||||
return new PIXI.Point(snappedX + canvas.grid.w / 2, snappedY + canvas.grid.h / 2)
|
||||
}
|
||||
|
||||
export function highlightTokenShape(position, shape, color) {
|
||||
for (const space of shape) {
|
||||
let gridX = position.x + space.x;
|
||||
let gridY = position.y + space.y;
|
||||
if (canvas.grid.isHex) {
|
||||
let shiftedRow;
|
||||
if (canvas.grid.grid.options.even)
|
||||
shiftedRow = 1
|
||||
else
|
||||
shiftedRow = 0
|
||||
if (canvas.grid.grid.options.columns) {
|
||||
if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
|
||||
gridY += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) {
|
||||
gridX += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
const [x, y] = getPixelsFromGridPosition(gridX, gridY);
|
||||
canvas.grid.highlightPosition(this.name, {x, y, color})
|
||||
}
|
||||
}
|
||||
|
||||
export function getTokenShape(token) {
|
||||
if (token.scene.data.gridType === CONST.GRID_TYPES.GRIDLESS)
|
||||
throw new Error("getTokenShape cannot be called for tokens on gridless maps")
|
||||
if (token.scene.data.gridType === CONST.GRID_TYPES.SQUARE) {
|
||||
const topOffset = -Math.floor(token.data.height / 2)
|
||||
const leftOffset = -Math.floor(token.data.width / 2)
|
||||
const shape = []
|
||||
for (let y = 0;y < token.data.height;y++) {
|
||||
for (let x = 0;x < token.data.width;x++) {
|
||||
shape.push({x: x + leftOffset, y: y + topOffset})
|
||||
}
|
||||
}
|
||||
return shape
|
||||
}
|
||||
else {
|
||||
// Hex grids
|
||||
if (game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
||||
const borderSize = token.data.flags["hex-size-support"].borderSize;
|
||||
let shape = [{x: 0, y: 0}];
|
||||
if (borderSize >= 2)
|
||||
shape = shape.concat([{x: 0, y: -1}, {x: -1, y: -1}]);
|
||||
if (borderSize >= 3)
|
||||
shape = shape.concat([{x: 0, y: 1}, {x: -1, y: 1}, {x: -1, y: 0}, {x: 1, y: 0}]);
|
||||
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}])
|
||||
|
||||
if (Boolean(CONFIG.hexSizeSupport.getAltOrientationFlag(token)) !== canvas.grid.grid.options.columns)
|
||||
shape.forEach(space => space.y *= -1);
|
||||
if (canvas.grid.grid.options.columns)
|
||||
shape = shape.map(space => {return {x: space.y, y: space.x}});
|
||||
return shape;
|
||||
}
|
||||
else {
|
||||
return [{x: 0, y: 0}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getTokenSize(token) {
|
||||
let w, h;
|
||||
const hexSizeSupportBorderSize = token.data.flags["hex-size-support"]?.borderSize;
|
||||
if (hexSizeSupportBorderSize > 0) {
|
||||
w = h = hexSizeSupportBorderSize
|
||||
}
|
||||
else {
|
||||
w = token.data.width
|
||||
h = token.data.height
|
||||
}
|
||||
return {w, h};
|
||||
}
|
||||
|
||||
// Tokens that have a size divisible by two (2x2, 4x4, 2x1) have their ruler at the edge of a 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) {
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
return waypoints
|
||||
}
|
||||
|
||||
const tokenSize = getTokenSize(token);
|
||||
const waypointOffset = {x: 0, y: 0};
|
||||
if (canvas.grid.isHex) {
|
||||
const isAltOrientation = CONFIG.hexSizeSupport.getAltOrientationFlag(token);
|
||||
if (canvas.grid.grid.options.columns) {
|
||||
if (tokenSize.w % 2 === 0) {
|
||||
waypointOffset.x = canvas.grid.w / 2;
|
||||
if (!isAltOrientation)
|
||||
waypointOffset.x *= -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tokenSize.h % 2 === 0) {
|
||||
waypointOffset.y = canvas.grid.h / 2;
|
||||
if (isAltOrientation)
|
||||
waypointOffset.y *= -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tokenSize.w % 2 === 0) {
|
||||
waypointOffset.x = canvas.grid.w / 2;
|
||||
}
|
||||
if (tokenSize.h % 2 === 0) {
|
||||
waypointOffset.y = canvas.grid.h / 2;
|
||||
}
|
||||
}
|
||||
|
||||
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user