Skip to content

Commit

Permalink
🎸 feat |Token|Use Foundry undo system to track move history
Browse files Browse the repository at this point in the history
Store token move history to flag on the token.
Now when the token move is undone, the flag is automatically changed back.
  • Loading branch information
caewok committed Jul 11, 2024
1 parent 8e538f9 commit 002ef4c
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 91 deletions.
76 changes: 52 additions & 24 deletions scripts/Token.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* globals
canvas,
CanvasAnimation,
foundry,
game,
Ruler
*/
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */

import { MODULE_ID, FLAGS } from "./const.js";
import { Settings } from "./settings.js";
import { log } from "./util.js";

Expand All @@ -18,33 +20,44 @@ PATCHES.PATHFINDING = {};
// ----- NOTE: Hooks ----- //

/**
* Hook updateToken to track token movement.
* @param {Document} document The existing Document which was updated
* @param {object} change Differential data that was used to update the document
* @param {DocumentModificationContext} options Additional options which modified the update request
* @param {string} userId The ID of the User who triggered the update workflow
* Hook preUpdateToken to track token movement
* @param {Document} document The Document instance being updated
* @param {object} changed Differential data that will be used to update the document
* @param {Partial<DatabaseUpdateOperation>} options Additional options which modify the update request
* @param {string} userId The ID of the requesting user, always game.user.id
* @returns {boolean|void} Explicitly return false to prevent update of this Document
*/
function updateToken(document, changes, _options, _userId) {
function preUpdateToken(document, changes, _options, _userId) {
const token = document.object;
if ( token.isPreview
|| !(Object.hasOwn(changes, "x")|| Object.hasOwn(changes, "y") || Object.hasOwn(changes, "elevation")) ) return;
|| !(Object.hasOwn(changes, "x") || Object.hasOwn(changes, "y") || Object.hasOwn(changes, "elevation")) ) return;

// Don't update move data if the move flag is being updated (likely due to control-z undo).
if ( foundry.utils.hasProperty(changes, `flags.${MODULE_ID}.${FLAGS.MOVEMENT_HISTORY}`) ) return;

// Store the move data in a token flag so it survives reloads and can be updated on control-z undo by another user.
// First determine the current move data.
let lastMoveDistance = 0;
let combatMoveData = {};
const ruler = canvas.controls.ruler;
if ( ruler.active && ruler.token === token ) token._lastMoveDistance = ruler.totalMoveDistance;
else token._lastMoveDistance = Ruler.measureMoveDistance(token.position, token.document._source, { token }).moveDistance;
if ( ruler.active && ruler.token === token ) lastMoveDistance = ruler.totalMoveDistance;
else lastMoveDistance = Ruler.measureMoveDistance(token.position, token.document._source, { token }).moveDistance;
if ( game.combat?.started ) {
// Store the combat move distance and the last round for which the combat move occurred.
// Map to each unique combat.
const combatId = game.combat.id;
token._combatMoveData ??= new Map();
if ( !token._combatMoveData.has(combatId) ) {
token._combatMoveData.set(combatId, { lastMoveDistance: 0, lastRound: -1 });
}
const combatData = token._combatMoveData.get(combatId);
if ( combatData.lastRound < game.combat.round ) combatData.lastMoveDistance = token._lastMoveDistance;
else combatData.lastMoveDistance += token._lastMoveDistance;
const combatData = {...token._combatMoveData};
if ( combatData.lastRound < game.combat.round ) combatData.lastMoveDistance = lastMoveDistance;
else combatData.lastMoveDistance += lastMoveDistance;
combatData.lastRound = game.combat.round;
combatMoveData = { [game.combat.id]: combatData };
}

// Combine with existing move data in the token flag.
const flagData = document.getFlag(MODULE_ID, FLAGS.MOVEMENT_HISTORY) ?? {};
foundry.utils.mergeObject(flagData, { lastMoveDistance, combatMoveData });

// Update the flag with the new data.
foundry.utils.setProperty(changes, `flags.${MODULE_ID}.${FLAGS.MOVEMENT_HISTORY}`, flagData);
}

// ----- NOTE: Wraps ----- //
Expand Down Expand Up @@ -139,16 +152,31 @@ async function _onDragLeftDrop(wrapped, event) {
* Token.prototype.lastMoveDistance
* Return the last move distance. If combat is active, return the last move since this token
* started its turn.
* @returns {number}
* @type {number}
*/
function lastMoveDistance() {
if ( game.combat?.started ) {
if ( !this._combatMoveData ) return 0;
const combatData = this._combatMoveData.get(game.combat.id);
if ( !combatData || combatData.lastRound < game.combat.round ) return 0;
const combatData = this._combatMoveData;
if ( combatData.lastRound < game.combat.round ) return 0;
return combatData.lastMoveDistance;
}
return this._lastMoveDistance || 0;
return this.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_HISTORY)?.lastMoveDistance || 0;
}

/**
* Token.prototype._combatData
* Map that stores the combat move data.
* Constructed from the relevant flag.
* @type {object}
* - @prop {number} lastMoveDistance Distance of last move during combat round
* - @prop {number} lastRound The combat round in which the last move occurred
*/
function _combatMoveData() {
const combatId = game.combat?.id;
const defaultData = { lastMoveDistance: 0, lastRound: -1 };
if ( typeof combatId === "undefined" ) return defaultData;
const combatMoveData = this.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_HISTORY)?.combatMoveData ?? { };
return combatMoveData[combatId] ?? defaultData;
}

// ----- NOTE: Patches ----- //
Expand All @@ -162,8 +190,8 @@ PATCHES.PATHFINDING.WRAPS = { _onUpdate };

PATCHES.TOKEN_RULER.MIXES = { _onDragLeftDrop, _onDragLeftCancel };

PATCHES.MOVEMENT_TRACKING.HOOKS = { updateToken };
PATCHES.MOVEMENT_TRACKING.GETTERS = { lastMoveDistance };
PATCHES.MOVEMENT_TRACKING.HOOKS = { preUpdateToken };
PATCHES.MOVEMENT_TRACKING.GETTERS = { lastMoveDistance, _combatMoveData };

// ----- NOTE: Helper functions ----- //

Expand Down
64 changes: 0 additions & 64 deletions scripts/TokenLayer.js

This file was deleted.

3 changes: 2 additions & 1 deletion scripts/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export const FLAGS = {
MOVEMENT_PENALTY: "movementPenalty",
SCENE: {
BACKGROUND_ELEVATION: "backgroundElevation"
}
},
MOVEMENT_HISTORY: "movementHistory"
};

export const MODULES_ACTIVE = { API: {} };
Expand Down
2 changes: 0 additions & 2 deletions scripts/patching.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { PATCHES as PATCHES_TokenPF } from "./pathfinding/Token.js";

// Movement tracking
import { PATCHES as PATCHES_TokenHUD } from "./token_hud.js";
import { PATCHES as PATCHES_TokenLayer } from "./TokenLayer.js";

// Settings
import { PATCHES as PATCHES_ClientSettings } from "./ModuleSettingsAbstract.js";
Expand All @@ -32,7 +31,6 @@ const PATCHES = {
DrawingConfig: PATCHES_DrawingConfig,
Ruler: PATCHES_Ruler,
Token: mergeObject(mergeObject(PATCHES_Token, PATCHES_TokenPF), PATCHES_TokenHUD),
TokenLayer: PATCHES_TokenLayer,
Wall: PATCHES_Wall
};

Expand Down

0 comments on commit 002ef4c

Please sign in to comment.