Track the path taken by tokens in combat and display it when reactivating DragRuler in the same turn
This commit is contained in:
@@ -6,9 +6,9 @@ export function getHexSizeSupportTokenGridCenter(token) {
|
||||
return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y}
|
||||
}
|
||||
|
||||
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}]) {
|
||||
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
||||
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
|
||||
const color = getColorForDistance.call(this, startDistance, space.distance)
|
||||
highlightTokenShape.call(this, space, tokenShape, color)
|
||||
highlightTokenShape.call(this, space, tokenShape, color, alpha)
|
||||
}
|
||||
}
|
||||
|
||||
+19
-11
@@ -2,6 +2,7 @@ import { getCostFromSpeedProvider } from "./api.js";
|
||||
import {highlightMeasurementTerrainRuler} from "./compatibility.js";
|
||||
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
||||
import {getColorForDistance} from "./main.js"
|
||||
import {trackRays} from "./movement_tracking.js"
|
||||
import {applyTokenSizeOffset, getAreaFromPositionAndShape, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
||||
|
||||
// This is a modified version of Ruler.moveToken from foundry 0.7.9
|
||||
@@ -16,7 +17,7 @@ export async function moveTokens(draggedToken, selectedTokens) {
|
||||
|
||||
// Get the movement rays and check collision along each Ray
|
||||
// These rays are center-to-center for the purposes of collision checking
|
||||
const rays = this._getRaysFromWaypoints(this.waypoints, this.destination);
|
||||
const rays = this.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination);
|
||||
if (!game.user.isGM) {
|
||||
const hasCollision = selectedTokens.some(token => {
|
||||
const offset = calculateTokenOffset(token, draggedToken)
|
||||
@@ -45,11 +46,13 @@ export async function moveTokens(draggedToken, selectedTokens) {
|
||||
|
||||
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
|
||||
async function animateToken(token, rays, tokenOffset, wasPaused) {
|
||||
const offsetRays = rays.map(ray => applyOffsetToRay(ray, tokenOffset))
|
||||
const offsetRays = rays.filter(r => !r.isPrevious).map(ray => applyOffsetToRay(ray, tokenOffset));
|
||||
trackRays(token, offsetRays);
|
||||
|
||||
// Determine offset relative to the Token top-left.
|
||||
// This is important so we can position the token relative to the ruler origin for non-1x1 tokens.
|
||||
const origin = [this.waypoints[0].x + tokenOffset.x, this.waypoints[0].y + tokenOffset.y]
|
||||
const firstWaypoint = this.waypoints.find(w => !w.isPrevious);
|
||||
const origin = [firstWaypoint.x + tokenOffset.x, firstWaypoint.y + tokenOffset.y];
|
||||
let dx, dy
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
dx = token.data.x - origin[0]
|
||||
@@ -76,7 +79,9 @@ function calculateTokenOffset(tokenA, tokenB) {
|
||||
}
|
||||
|
||||
function applyOffsetToRay(ray, offset) {
|
||||
return new Ray({x: ray.A.x + offset.x, y: ray.A.y + offset.y}, {x: ray.B.x + offset.x, y: ray.B.y + offset.y})
|
||||
const newRay = new Ray({x: ray.A.x + offset.x, y: ray.A.y + offset.y}, {x: ray.B.x + offset.x, y: ray.B.y + offset.y});
|
||||
newRay.isPrevious = ray.isPrevious;
|
||||
return newRay;
|
||||
}
|
||||
|
||||
// This is a modified version of Ruler._onMouseMove from foundry 0.7.9
|
||||
@@ -130,6 +135,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
const label = this.labels.children[i];
|
||||
const ray = new Ray(origin, dest);
|
||||
const centeredRay = new Ray(centeredOrigin, centeredDest)
|
||||
ray.isPrevious = Boolean(origin.isPrevious);
|
||||
centeredRay.isPrevious = ray.isPrevious;
|
||||
if (ray.distance < 10) {
|
||||
if (label) label.visible = false;
|
||||
continue;
|
||||
@@ -173,8 +180,9 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
const { label, text, last } = cs;
|
||||
|
||||
// Draw line segment
|
||||
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);
|
||||
const opacityMultiplier = s.ray.isPrevious ? 0.33 : 1;
|
||||
r.lineStyle(6, 0x000000, 0.5 * opacityMultiplier).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y)
|
||||
.lineStyle(4, rulerColor, 0.25 * opacityMultiplier).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y);
|
||||
|
||||
// Draw the distance label just after the endpoint of the segment
|
||||
if (label) {
|
||||
@@ -187,9 +195,9 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
|
||||
// Highlight grid positions
|
||||
if (terrainRulerAvailable)
|
||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape)
|
||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
||||
else
|
||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape);
|
||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
||||
}
|
||||
|
||||
// Draw endpoints
|
||||
@@ -201,7 +209,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
return segments;
|
||||
}
|
||||
|
||||
export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0, y: 0}]) {
|
||||
export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
||||
const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1;
|
||||
const nMax = Math.max(Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), 1);
|
||||
const tMax = Array.fromRange(nMax+1).map(t => t / nMax);
|
||||
@@ -237,9 +245,9 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 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, alpha);
|
||||
}
|
||||
|
||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color);
|
||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,6 +5,7 @@ import {getHexSizeSupportTokenGridCenter} from "./compatibility.js"
|
||||
import {moveTokens, onMouseMove} from "./foundry_imports.js"
|
||||
import {performMigrations} from "./migration.js"
|
||||
import {DragRulerRuler} from "./ruler.js";
|
||||
import {getMovementHistory} from "./movement_tracking.js";
|
||||
import {registerSettings, settingsKey} from "./settings.js"
|
||||
import {SpeedProvider} from "./speed_provider.js"
|
||||
|
||||
@@ -120,6 +121,7 @@ function onTokenLeftDragStart(event) {
|
||||
ruler.clear();
|
||||
ruler._state = Ruler.STATES.STARTING;
|
||||
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y}
|
||||
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
|
||||
ruler.dragRulerAddWaypoint(tokenCenter, false);
|
||||
}
|
||||
|
||||
@@ -146,9 +148,7 @@ function onTokenDragLeftCancel(event) {
|
||||
return false
|
||||
if (ruler._state === Ruler.STATES.MEASURING) {
|
||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
||||
if (ruler.waypoints.length > 1)
|
||||
event.preventDefault()
|
||||
ruler.dragRulerDeleteWaypoint();
|
||||
ruler.dragRulerDeleteWaypoint(event);
|
||||
}
|
||||
else {
|
||||
event.preventDefault()
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
function initTrackingFlag(combatant) {
|
||||
const initialFlag = {passedWaypoints: [], trackedRound: 0};
|
||||
let dragRulerFlag = combatant.flags?.dragRuler;
|
||||
if (dragRulerFlag) {
|
||||
if (isNaN(dragRulerFlag.trackedRound)) {
|
||||
mergeObject(dragRulerFlag, initialFlag);
|
||||
}
|
||||
}
|
||||
else {
|
||||
combatant.flags.dragRuler = initialFlag;
|
||||
}
|
||||
}
|
||||
|
||||
function getInitializedCombatant(token, combat) {
|
||||
const combatant = combat.getCombatantByToken(token.data._id);
|
||||
if (!combatant)
|
||||
return undefined;
|
||||
initTrackingFlag(combatant);
|
||||
return combatant;
|
||||
}
|
||||
|
||||
export async function trackRays(token, rays) {
|
||||
// Only track movement if the current token is participating in the active combat
|
||||
const combat = game.combat;
|
||||
if (!combat)
|
||||
return;
|
||||
if (!combat.started)
|
||||
return;
|
||||
const combatant = getInitializedCombatant(token, combat);
|
||||
if (!combatant)
|
||||
return;
|
||||
|
||||
// Check if we have entered a new round. If so, remove the currently stored path
|
||||
if (combat.data.round > combatant.flags.dragRuler.trackedRound) {
|
||||
combatant.flags.dragRuler.passedWaypoints = [];
|
||||
combatant.flags.dragRuler.trackedRound = combat.data.round;
|
||||
}
|
||||
|
||||
// Add the passed waypoints to the combatant
|
||||
const waypoints = combatant.flags.dragRuler.passedWaypoints;
|
||||
for (const ray of rays) {
|
||||
// Ignore rays that have the same start and end coordinates
|
||||
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y)
|
||||
waypoints.push(ray.A);
|
||||
}
|
||||
await combat.updateEmbeddedEntity("Combatant", {_id: combatant._id, flags: combatant.flags}, {diff: false});
|
||||
}
|
||||
|
||||
export function getMovementHistory(token) {
|
||||
const combat = game.combat;
|
||||
if (!combat)
|
||||
return [];
|
||||
const combatant = combat.getCombatantByToken(token.data._id);
|
||||
if (!combatant)
|
||||
return [];
|
||||
const dragRulerFlags = combatant.flags.dragRuler;
|
||||
if (!dragRulerFlags)
|
||||
return [];
|
||||
if (combat.data.round > dragRulerFlags.trackedRound)
|
||||
return [];
|
||||
return dragRulerFlags.passedWaypoints ?? [];
|
||||
}
|
||||
+34
-3
@@ -4,6 +4,18 @@ import {getSnapPointForToken} from "./util.js";
|
||||
|
||||
export class DragRulerRuler extends Ruler {
|
||||
// Functions below are overridden versions of functions in Ruler
|
||||
constructor(user, {color=null}={}) {
|
||||
super(user, {color});
|
||||
this.previousWaypoints = [];
|
||||
this.previousLabels = this.addChild(new PIXI.Container());
|
||||
}
|
||||
|
||||
clear() {
|
||||
super.clear();
|
||||
this.previousWaypoints = [];
|
||||
this.previousLabels.removeChildren().forEach(c => c.destroy());
|
||||
}
|
||||
|
||||
async moveToken(event) {
|
||||
// This function is invoked by left clicking
|
||||
if (!this.isDragRuler)
|
||||
@@ -57,8 +69,17 @@ export class DragRulerRuler extends Ruler {
|
||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||
}
|
||||
|
||||
dragRulerDeleteWaypoint() {
|
||||
if (this.waypoints.length > 1) {
|
||||
dragRulerAddWaypointHistory(waypoints) {
|
||||
waypoints = waypoints.map(waypoint => {return {x: waypoint.x, y: waypoint.y, isPrevious: true}});
|
||||
this.waypoints = this.waypoints.concat(waypoints);
|
||||
for (const waypoint of waypoints) {
|
||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||
}
|
||||
}
|
||||
|
||||
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}) {
|
||||
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
||||
event.preventDefault();
|
||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
||||
const rulerOffset = this.rulerOffset;
|
||||
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y});
|
||||
@@ -74,7 +95,17 @@ export class DragRulerRuler extends Ruler {
|
||||
|
||||
// This will cancel the current drag operation
|
||||
// Pass in a fake event that hopefully is enough to allow other modules to function
|
||||
token._onDragLeftCancel({preventDefault: () => {return}});
|
||||
token._onDragLeftCancel(event);
|
||||
}
|
||||
}
|
||||
|
||||
dragRulerGetRaysFromWaypoints(waypoints, destination) {
|
||||
if ( destination )
|
||||
waypoints = waypoints.concat([destination]);
|
||||
return waypoints.slice(1).map((wp, i) => {
|
||||
const ray = new Ray(waypoints[i], wp);
|
||||
ray.isPrevious = Boolean(waypoints[i].isPrevious);
|
||||
return ray;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -39,11 +39,14 @@ export function getSnapPointForToken(x, y, token) {
|
||||
return new PIXI.Point(snappedX + canvas.grid.w / 2, snappedY + canvas.grid.h / 2)
|
||||
}
|
||||
|
||||
export function highlightTokenShape(position, shape, color) {
|
||||
export function highlightTokenShape(position, shape, color, alpha) {
|
||||
const layer = canvas.grid.highlightLayers[this.name];
|
||||
if ( !layer )
|
||||
return false;
|
||||
const area = getAreaFromPositionAndShape(position, shape);
|
||||
for (const space of area) {
|
||||
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
|
||||
canvas.grid.highlightPosition(this.name, {x, y, color})
|
||||
canvas.grid.grid.highlightGridPosition(layer, {x, y, color, alpha: 0.25 * alpha});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user