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:
Manuel Vögele
2021-03-09 17:56:33 +01:00
parent f8fe4ee971
commit c4d089e8ff
5 changed files with 176 additions and 49 deletions
+2
View File
@@ -1,6 +1,8 @@
## In development ## In development
### New features ### 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. - 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. - 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. - 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
View File
@@ -1,30 +1,14 @@
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
import {getColorForDistance} from "./main.js" import {getColorForDistance} from "./main.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)
const tokenCenter = {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y} return {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
} }
export function highlightMeasurementTerrainRuler(ray, startDistance) { export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}]) {
for (const space of ray.terrainRulerVisitedSpaces) { for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
const color = getColorForDistance.call(this, startDistance, space.distance) const color = getColorForDistance.call(this, startDistance, space.distance)
canvas.grid.highlightPosition(this.name, {x, y, color: color}) highlightTokenShape.call(this, space, tokenShape, color)
} }
} }
+31 -23
View File
@@ -1,6 +1,7 @@
import {highlightMeasurementTerrainRuler} from "./compatibility.js"; import {highlightMeasurementTerrainRuler} from "./compatibility.js";
import {getGridPositionFromPixels} from "./foundry_fixes.js";
import {getColorForDistance} from "./main.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 // This is a modified version of Ruler.moveToken from foundry 0.7.9
export async function moveTokens(draggedToken, selectedTokens) { export async function moveTokens(draggedToken, selectedTokens) {
@@ -104,9 +105,12 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
return [] return []
if (snap) 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 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; const r = this.ruler;
this.destination = destination; this.destination = destination;
@@ -138,10 +142,10 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
let totalDistance = 0; let totalDistance = 0;
for (let [i, d] of distances.entries()) { for (let [i, d] of distances.entries()) {
let s = segments[i]; let s = centeredSegments[i];
s.startDistance = totalDistance s.startDistance = totalDistance
totalDistance += d; totalDistance += d;
s.last = i === (segments.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);
} }
@@ -157,27 +161,28 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
rulerColor = getColorForDistance.call(this, totalDistance) rulerColor = getColorForDistance.call(this, totalDistance)
else else
rulerColor = this.color rulerColor = this.color
const shape = getTokenShape(this.draggedToken)
for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) { for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
const { ray, label, text, last } = s; const { label, text, last } = cs;
// Draw line segment // Draw line segment
r.lineStyle(6, 0x000000, 0.5).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(ray.A.x, ray.A.y).lineTo(ray.B.x, 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 // Draw the distance label just after the endpoint of the segment
if (label) { if (label) {
label.text = text; label.text = text;
label.alpha = last ? 1.0 : 0.5; label.alpha = last ? 1.0 : 0.5;
label.visible = true; 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); label.position.set(labelPosition.x, labelPosition.y);
} }
// Highlight grid positions // Highlight grid positions
if (terrainRulerAvailable) if (terrainRulerAvailable)
highlightMeasurementTerrainRuler.call(this, cs.ray, s.startDistance) highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape)
else else
highlightMeasurementNative.call(this, ray, s.startDistance); highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape);
} }
// Draw endpoints // Draw endpoints
@@ -189,7 +194,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
return segments; 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 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);
@@ -198,7 +203,7 @@ export function highlightMeasurementNative(ray, startDistance) {
let prior = null; let prior = null;
// Iterate over ray portions // 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); let {x, y} = ray.project(t);
// Get grid position // Get grid position
@@ -208,23 +213,26 @@ export function highlightMeasurementNative(ray, startDistance) {
// Highlight the grid position // Highlight the grid position
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1); 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] const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
let color = dragRuler.getColorForDistance.call(this, startDistance, subDistance) const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
canvas.grid.highlightPosition(this.name, {x: xg, y: yg, color: color}); 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]; prior = [x1, y1];
if ( i === 0 ) continue;
// If the positions are not neighbors, also highlight their halfway point // If the positions are not neighbors, also highlight their halfway point
if ( !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 [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h); 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] const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
color = dragRuler.getColorForDistance.call(this, startDistance, subDistance) const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
canvas.grid.highlightPosition(this.name, {x: xgh, y: ygh, color: color}); 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
View File
@@ -6,6 +6,7 @@ import {measure, moveTokens, onMouseMove} from "./foundry_imports.js"
import {performMigrations} from "./migration.js" import {performMigrations} from "./migration.js"
import {registerSettings, settingsKey} from "./settings.js" import {registerSettings, settingsKey} from "./settings.js"
import {SpeedProvider} from "./speed_provider.js" import {SpeedProvider} from "./speed_provider.js"
import { getSnapPointForToken } from "./util.js"
Hooks.once("init", () => { Hooks.once("init", () => {
registerSettings() registerSettings()
@@ -158,7 +159,7 @@ function onTokenLeftDragStart(event) {
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this)) if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
tokenCenter = getHexSizeSupportTokenGridCenter(this) tokenCenter = getHexSizeSupportTokenGridCenter(this)
else else
tokenCenter = {x: this.x + canvas.grid.grid.w / 2, y: this.y + canvas.grid.grid.h / 2} tokenCenter = this.center
ruler.clear(); ruler.clear();
ruler._state = Ruler.STATES.STARTING; ruler._state = Ruler.STATES.STARTING;
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y} 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) { function addWaypoint(point, snap=true) {
if (snap) if (snap)
point = canvas.grid.getCenter(point.x, point.y); point = getSnapPointForToken(point.x, point.y, this.draggedToken)
else this.waypoints.push(new PIXI.Point(point.x, point.y));
point = [point.x, point.y]
this.waypoints.push(new PIXI.Point(point[0], point[1]));
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
} }
+134
View File
@@ -1,5 +1,139 @@
import {getPixelsFromGridPosition} from "./foundry_fixes.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]]
} }
} }
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))
}