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}
|
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()) {
|
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
|
||||||
const color = getColorForDistance.call(this, startDistance, space.distance)
|
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 {highlightMeasurementTerrainRuler} from "./compatibility.js";
|
||||||
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
||||||
import {getColorForDistance} from "./main.js"
|
import {getColorForDistance} from "./main.js"
|
||||||
|
import {trackRays} from "./movement_tracking.js"
|
||||||
import {applyTokenSizeOffset, getAreaFromPositionAndShape, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
import {applyTokenSizeOffset, getAreaFromPositionAndShape, getSnapPointForToken, getTokenShape, 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
|
||||||
@@ -16,7 +17,7 @@ export async function moveTokens(draggedToken, selectedTokens) {
|
|||||||
|
|
||||||
// Get the movement rays and check collision along each Ray
|
// Get the movement rays and check collision along each Ray
|
||||||
// These rays are center-to-center for the purposes of collision checking
|
// 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) {
|
if (!game.user.isGM) {
|
||||||
const hasCollision = selectedTokens.some(token => {
|
const hasCollision = selectedTokens.some(token => {
|
||||||
const offset = calculateTokenOffset(token, draggedToken)
|
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
|
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
|
||||||
async function animateToken(token, rays, tokenOffset, wasPaused) {
|
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.
|
// 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.
|
// 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
|
let dx, dy
|
||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
dx = token.data.x - origin[0]
|
dx = token.data.x - origin[0]
|
||||||
@@ -76,7 +79,9 @@ function calculateTokenOffset(tokenA, tokenB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyOffsetToRay(ray, offset) {
|
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
|
// 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 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);
|
||||||
|
centeredRay.isPrevious = ray.isPrevious;
|
||||||
if (ray.distance < 10) {
|
if (ray.distance < 10) {
|
||||||
if (label) label.visible = false;
|
if (label) label.visible = false;
|
||||||
continue;
|
continue;
|
||||||
@@ -173,8 +180,9 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
const { label, text, last } = cs;
|
const { label, text, last } = cs;
|
||||||
|
|
||||||
// Draw line segment
|
// 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)
|
const opacityMultiplier = s.ray.isPrevious ? 0.33 : 1;
|
||||||
.lineStyle(4, rulerColor, 0.25).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).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) {
|
||||||
@@ -187,9 +195,9 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
|
|
||||||
// Highlight grid positions
|
// Highlight grid positions
|
||||||
if (terrainRulerAvailable)
|
if (terrainRulerAvailable)
|
||||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape)
|
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
||||||
else
|
else
|
||||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape);
|
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw endpoints
|
// Draw endpoints
|
||||||
@@ -201,7 +209,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
return segments;
|
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 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);
|
||||||
@@ -237,9 +245,9 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
|
|||||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
|
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
|
||||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
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 {moveTokens, onMouseMove} from "./foundry_imports.js"
|
||||||
import {performMigrations} from "./migration.js"
|
import {performMigrations} from "./migration.js"
|
||||||
import {DragRulerRuler} from "./ruler.js";
|
import {DragRulerRuler} from "./ruler.js";
|
||||||
|
import {getMovementHistory} from "./movement_tracking.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"
|
||||||
|
|
||||||
@@ -120,6 +121,7 @@ function onTokenLeftDragStart(event) {
|
|||||||
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}
|
||||||
|
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
|
||||||
ruler.dragRulerAddWaypoint(tokenCenter, false);
|
ruler.dragRulerAddWaypoint(tokenCenter, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,9 +148,7 @@ function onTokenDragLeftCancel(event) {
|
|||||||
return false
|
return false
|
||||||
if (ruler._state === Ruler.STATES.MEASURING) {
|
if (ruler._state === Ruler.STATES.MEASURING) {
|
||||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
||||||
if (ruler.waypoints.length > 1)
|
ruler.dragRulerDeleteWaypoint(event);
|
||||||
event.preventDefault()
|
|
||||||
ruler.dragRulerDeleteWaypoint();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
event.preventDefault()
|
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 {
|
export class DragRulerRuler extends Ruler {
|
||||||
// Functions below are overridden versions of functions in 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) {
|
async moveToken(event) {
|
||||||
// This function is invoked by left clicking
|
// This function is invoked by left clicking
|
||||||
if (!this.isDragRuler)
|
if (!this.isDragRuler)
|
||||||
@@ -57,8 +69,17 @@ export class DragRulerRuler extends Ruler {
|
|||||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||||
}
|
}
|
||||||
|
|
||||||
dragRulerDeleteWaypoint() {
|
dragRulerAddWaypointHistory(waypoints) {
|
||||||
if (this.waypoints.length > 1) {
|
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 mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
||||||
const rulerOffset = this.rulerOffset;
|
const rulerOffset = this.rulerOffset;
|
||||||
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y});
|
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
|
// This will cancel the current drag operation
|
||||||
// Pass in a fake event that hopefully is enough to allow other modules to function
|
// 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)
|
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);
|
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);
|
||||||
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