diff --git a/README.md b/README.md index c518104..33d3b46 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,14 @@ Fantasy Computerworks Logo -A module made by [Fantasy Computerworks](http://fantasycomputer.works/). +A module made by Fantasy Computerworks. Other works by us: - [Fantasy Calendar](https://app.fantasy-calendar.com) - The best calendar creator and management app on the internet - [Sequencer](https://foundryvtt.com/packages/sequencer) - Wow your players by playing visual effects on the canvas - [Item Piles](https://foundryvtt.com/packages/item-piles) - Drag & drop items into the scene to drop item piles that you can then easily pick up +- [Potato Or Not](https://foundryvtt.com/packages/potato-or-not) - Automatically set up Foundry to the best visual settings for your players' potato computers - [Tagger](https://foundryvtt.com/packages/tagger) - Tag objects in the scene and retrieve them with a powerful API -- [Rest Recovery](https://foundryvtt.com/packages/rest-recovery) - Automate most D&D 5e long and short rest mechanics Like what we've done? Buy us a coffee! @@ -50,9 +50,9 @@ In the module settings, you can set the following options: ### Token Movement Speed -#### Default: 10 +#### Default: 6 -This sets the default animation speed of moving tokens, in spaces per second (10 is Foundry default). +This sets the default animation speed of moving tokens, in spaces per second (6 is Foundry default). ### Token Movement Duration @@ -90,246 +90,62 @@ Each token can override the module's default movement animation settings. ## API -### Changes to `TokenDocument#update` +### `TokenDocument#update` -This accepts an additional optional parameter in its second parameter: +As of Foundry v10, this natively accepts an additional optional parameter in its second parameter called `animation`: ```js token.document.update({ x: ..., y: ... -}, {animate: true, animation: { speed: 10, duration: 0, ease: "linear" }}) +}, {animate: true, animation: { movementSpeed: 10, duration: 0, easing: "linear" }}) ``` -| Param | Type | Default | -| --- | --- | --- | -| speed | number | `10` | -| duration | number | `0` | -| ease | string | `linear` | +| Param | Type | Default | +|---------------| --- | --- | +| movementSpeed | number | `10` | +| duration | number | `0` | +| easing | string | `linear` | If `speed` or `duration` is given in the `animation` parameter, it will override the settings (see [module settings](#module-settings)). If `duration` is provided, `speed` has no impact on the animation, as the token will reach the destination within the given duration. ---- - -### `window.easeFunctions` - -A global object that contains all the ease functions for quick access. - -Use like: `window.easeFunctions["linear"]`, which gives you: - -```js -function linear(x) { - return x; -} -``` - -### List of ease options: - - * `linear` - * `easeInSine` - * `easeOutSine` - * `easeInOutSine` - * `easeInQuad` - * `easeOutQuad` - * `easeInOutQuad` - * `easeInCubic` - * `easeOutCubic` - * `easeInOutCubic` - * `easeInQuart` - * `easeOutQuart` - * `easeInOutQuart` - * `easeInQuint` - * `easeOutQuint` - * `easeInOutQuint` - * `easeInExpo` - * `easeOutExpo` - * `easeInOutExpo` - * `easeInCirc` - * `easeOutCirc` - * `easeInOutCirc` - * `easeInBack` - * `easeOutBack` - * `easeInOutBack` - * `easeInElastic` - * `easeOutElastic` - * `easeInOutElastic` - * `easeInBounce` - * `easeOutBounce` - * `easeInOutBounce` - -## Hooks - -### preTokenAnimate - -Called before a token has started animating. - -You can `return false` to interrupt the movement. - -| Param | Type | -| --- | --- | -| context | Token | -| data | object | - -
- -Example hook - -This hook injects more code into the animate callback (ontick). -The injected code adds a PIXI Graphics circle to the canvas every 100 or so pixels. - -```js -Hooks.once('preTokenAnimate', (token, data) => { - const tokenCenter = token.w/2; - let lastPos = { - x: token.data.x, - y: token.data.y - } - data.ontick = (dt, anim) => { - token._onMovementFrame(dt, anim, data.config); - const currLoc = { - x: token.data.x + tokenCenter, - y: token.data.y + tokenCenter, - } - if ( Math.abs(lastPos.x - token.data.x) > 100 || - Math.abs(lastPos.y - token.data.y) > 100 - ) { - const poopDot = new PIXI.Graphics(); - poopDot.beginFill(0xe74c3c); - poopDot.drawCircle(currLoc.x, currLoc.y, 10); - poopDot.endFill(); - canvas.background.addChild(poopDot); - lastPos = { - x: token.data.x, - y: token.data.y - } - } - } -}); -``` - -This was a test to inject a function into the tokenAnimate callback that is added to the canvas ticker. - -It will just generate a console log message on every tick. - -```js -Hooks.once('preTokenAnimate', (token, data) => { - const myNewVar = "YES!"; - data.ontick = (dt, anim) => { - token._onMovementFrame(dt, anim, data.config); - console.log("Have I added this console.log to the ontick function? ", myNewVar); - } - console.log("preTokenAnimate hook has fired!"); -}); -``` - -This hook will multiply the movement speed by 3. It does this by dividing the duration by 3. - -```js -Hooks.once('preTokenAnimate', (token, data) => { - // This hook will make the token faster than normal. - data.duration /= 3; -}) -``` - -
- -### preTokenChainMove - -Called before a token has started animating along several waypoints. - -You can `return false` to interrupt the movement. - -| Param | Type | -| --- | --- | -| token | Token | -| ruler | Ruler | - -
- -Example hook - -This hook alters the waypoints of a token movement. The intent was to have the token move between waypoints that I would enter in a zig-zag pattern. - -The logic here is very wrong, but it still demonstrates that the waypoints can be changed via the hook. +If you change `easing`, it is recommended you use the ones below. -```js -Hooks.once('preTokenChainMove', (token, ruler) => { - for (let i=1; i < Ruler.waypoints.length - 1; i++) { - ruler.waypoints[i].x = (ruler.waypoints[i].x + ruler.waypoints[i+1].x) / 2; - ruler.waypoints[i].y = (ruler.waypoints[i].y + ruler.waypoints[i+1].y) / 2; - } -}) -``` - -This hook will cause token movement to be cancelled before it begins. This applies to movements entered via waypoints (hold ctrl and click path). The token will emote text in a chat bubble. - -```js -Hooks.once('preTokenChainMove', (token) => { - const bubble = new ChatLog; - bubble.processMessage("Whoa! Do you think I can remember all those waypoints!?"); - return false; -}) -``` - -
- -### tokenAnimationComplete - -Called when a token's animation completed playing entirely. - -| Param | Type | -| --- | --- | -| token | Token | - -
- -Example hook - -This hook will emote text in a chat bubble, when an animation is complete. - -```js -Hooks.once('tokenAnimationComplete', (token) => { - const bubble = new ChatLog; - bubble.processMessage("If you didn't drag me down into these places, I wouldn't have to strain myself running in full armor to get past these ridiculous obstacles you can't seem to avoid...") -}) -``` - -
- -### tokenAnimationTerminated - -Called when a token's animation was terminated early, without having finished. - -| Param | Type | -| --- | --- | -| attributes | array | - -
- -Example hook - -This hook will spit out animation data at time of termination to the console. - -It will also set the token's position at the spot where the animation was terminated. - -Core sets the token position before animation starts, so terminating an animation in core sends the token to the end point. - -```js -Hooks.once('tokenAnimationTerminated', (data) => { - console.log("Token animation terminated early: ", data); - ui.notifications.info("Notification triggered off the new 'tokenAnimationTerminated' hook. See console for animation data!"); - const token = data[0].parent; - const isToX = data.filter(d => d.attribute === "x"); - const isToY = data.filter(d => d.attribute === "y"); - - const wasX = isToX.length ? (isToX[0].to - (isToX[0].delta - isToX[0].done)) : token.data.x; - const wasY = isToY.length ? (isToY[0].to - (isToY[0].delta - isToY[0].done)) : token.data.y; - - token.position.set(wasX, wasY); - - token.document.update({_id: token.id, x: wasX, y: wasY}, {animate: false}); -}) -``` +--- -
+### `CanvasAnimation` + +Token Ease adds multiple easing functions to Foundry's `CanvasAnimation` interface: + +* `linear` +* `easeInSine` +* `easeOutSine` +* `easeInOutSine` +* `easeInQuad` +* `easeOutQuad` +* `easeInOutQuad` +* `easeInCubic` +* `easeOutCubic` +* `easeInOutCubic` +* `easeInQuart` +* `easeOutQuart` +* `easeInOutQuart` +* `easeInQuint` +* `easeOutQuint` +* `easeInOutQuint` +* `easeInExpo` +* `easeOutExpo` +* `easeInOutExpo` +* `easeInCirc` +* `easeOutCirc` +* `easeInOutCirc` +* `easeInBack` +* `easeOutBack` +* `easeInOutBack` +* `easeInElastic` +* `easeOutElastic` +* `easeInOutElastic` +* `easeInBounce` +* `easeOutBounce` +* `easeInOutBounce` \ No newline at end of file diff --git a/images/settings.png b/images/settings.png index 5e28129..19a11f0 100644 Binary files a/images/settings.png and b/images/settings.png differ diff --git a/images/token_settings.png b/images/token_settings.png index e4c6b70..aff70b0 100644 Binary files a/images/token_settings.png and b/images/token_settings.png differ diff --git a/languages/en.json b/languages/en.json index db87168..5c7a3c3 100644 --- a/languages/en.json +++ b/languages/en.json @@ -1,6 +1,6 @@ { "TOKEN-EASE.speed-title": "Token Movement Speed", - "TOKEN-EASE.speed-description": "Sets the default animation speed, in spaces per second (10 is Foundry default).", + "TOKEN-EASE.speed-description": "Sets the default animation speed, in spaces per second (6 is Foundry default).", "TOKEN-EASE.duration-title": "Token Movement Duration", "TOKEN-EASE.duration-description": "Sets the default animation duration for movement (in milliseconds). This overrides Token Speed, and reaches the destination in a set amount of time, regardless of distance. Setting this to 0 will utilize speed instead.", "TOKEN-EASE.ease-title": "Token Movement Ease", diff --git a/module.json b/module.json index eccc1cd..ce793aa 100644 --- a/module.json +++ b/module.json @@ -1,48 +1,45 @@ { - "name": "token-ease", - "title": "Token Ease", - "description": "This module extends native Foundry behavior to introduce easing, custom movement speed, and movement duration to tokens.", - "authors": [ - { - "name": "Wasp", - "url": "https://github.com/Haxxer", - "discord": "Wasp#2005" - }, - { - "name": "Kerrec Snowmane", - "url": "https://github.com/ggagnon76", - "discord": "Kerrec Snowmane#5264" - } - ], - "url": "https://github.com/fantasycalendar/FoundryVTT-TokenEase", - "flags": {}, - "version": "This is auto replaced", - "minimumCoreVersion": "0.8.6", - "compatibleCoreVersion": "9.000", - "scripts": [], - "esmodules": [ - "/scripts/module.js" - ], - "styles": [], - "languages": [ - { - "lang": "en", - "name": "English", - "path": "/languages/en.json" - } - ], - "packs": [], - "system": [], - "dependencies": [ - { - "name": "lib-wrapper", - "type": "module" - } - ], - "socket": false, - "manifest": "https://github.com/fantasycalendar/FoundryVTT-TokenEase/releases/latest/download/module.json", - "download": "https://github.com/fantasycalendar/FoundryVTT-TokenEase/releases/download/1.0.4/module.zip", - "protected": false, - "coreTranslation": false, - "library": "false" + "id": "token-ease", + "title": "Token Ease", + "description": "This module extends native Foundry behavior to introduce easing, custom movement speed, and movement duration to tokens.", + "authors": [ + { + "name": "Wasp", + "url": "https://github.com/Haxxer", + "discord": "Wasp#2005" + }, + { + "name": "Kerrec Snowmane", + "url": "https://github.com/ggagnon76", + "discord": "Kerrec Snowmane#5264" + } + ], + "flags": {}, + "version": "1.2.0", + "minimumCoreVersion": "10", + "compatibility": { + "minimum": "10", + "verified": "10" + }, + "scripts": [], + "esmodules": [ + "/scripts/module.js" + ], + "styles": [], + "languages": [ + { + "lang": "en", + "name": "English", + "path": "/languages/en.json" + } + ], + "packs": [], + "system": [], + "socket": false, + "url": "https://github.com/fantasycalendar/FoundryVTT-TokenEase", + "manifest": "https://github.com/fantasycalendar/FoundryVTT-TokenEase/releases/latest/download/module.json", + "download": "https://github.com/fantasycalendar/FoundryVTT-TokenEase/releases/latest/download/module.zip", + "protected": false, + "coreTranslation": false, + "library": "false" } \ No newline at end of file diff --git a/scripts/lib/libWrapper/shim.js b/scripts/lib/libWrapper/shim.js deleted file mode 100644 index 5d248e4..0000000 --- a/scripts/lib/libWrapper/shim.js +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro - - -'use strict'; - -// A shim for the libWrapper library -export let libWrapper = undefined; - -export const VERSIONS = [1,12,1]; -export const TGT_SPLIT_RE = new RegExp("([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])", 'g'); -export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", 'g'); - -// Main shim code -Hooks.once('init', () => { - // Check if the real module is already loaded - if so, use it - if(globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) { - libWrapper = globalThis.libWrapper; - return; - } - - // Fallback implementation - libWrapper = class { - static get is_fallback() { return true }; - - static get WRAPPER() { return 'WRAPPER' }; - static get MIXED() { return 'MIXED' }; - static get OVERRIDE() { return 'OVERRIDE' }; - - static register(package_id, target, fn, type="MIXED", {chain=undefined, bind=[]}={}) { - const is_setter = target.endsWith('#set'); - target = !is_setter ? target : target.slice(0, -4); - const split = target.match(TGT_SPLIT_RE).map((x)=>x.replace(/\\(.)/g, '$1').replace(TGT_CLEANUP_RE,'')); - const root_nm = split.splice(0,1)[0]; - - let obj, fn_name; - if(split.length == 0) { - obj = globalThis; - fn_name = root_nm; - } - else { - const _eval = eval; - fn_name = split.pop(); - obj = split.reduce((x,y)=>x[y], globalThis[root_nm] ?? _eval(root_nm)); - } - - let iObj = obj; - let descriptor = null; - while(iObj) { - descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name); - if(descriptor) break; - iObj = Object.getPrototypeOf(iObj); - } - if(!descriptor || descriptor?.configurable === false) throw new Error(`libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`); - - let original = null; - const wrapper = (chain ?? (type.toUpperCase?.() != 'OVERRIDE' && type != 3)) ? - function(...args) { return fn.call(this, original.bind(this), ...bind, ...args); } : - function(...args) { return fn.call(this, ...bind, ...args); } - ; - - if(!is_setter) { - if(descriptor.value) { - original = descriptor.value; - descriptor.value = wrapper; - } - else { - original = descriptor.get; - descriptor.get = wrapper; - } - } - else { - if(!descriptor.set) throw new Error(`libWrapper Shim: '${target}' does not have a setter`); - original = descriptor.set; - descriptor.set = wrapper; - } - - descriptor.configurable = true; - Object.defineProperty(obj, fn_name, descriptor); - } - } - - //************** USER CUSTOMIZABLE: - // Set up the ready hook that shows the "libWrapper not installed" warning dialog. Remove if undesired. - { - //************** USER CUSTOMIZABLE: - // Package ID & Package Title - by default attempts to auto-detect, but you might want to hardcode your package ID and title here to avoid potential auto-detect issues - const [PACKAGE_ID, PACKAGE_TITLE] = (()=>{ - const match = (import.meta?.url ?? Error().stack)?.match(/\/(worlds|systems|modules)\/(.+)(?=\/)/i); - if(match?.length !== 3) return [null,null]; - const dirs = match[2].split('/'); - if(match[1] === 'worlds') return dirs.find(n => n && game.world.id === n) ? [game.world.id, game.world.title] : [null,null]; - if(match[1] === 'systems') return dirs.find(n => n && game.system.id === n) ? [game.system.id, game.system.data.title] : [null,null]; - const id = dirs.find(n => n && game.modules.has(n)); - return [id, game.modules.get(id)?.data?.title]; - })(); - - if(!PACKAGE_ID || !PACKAGE_TITLE) { - console.error("libWrapper Shim: Could not auto-detect package ID and/or title. The libWrapper fallback warning dialog will be disabled."); - return; - } - - Hooks.once('ready', () => { - //************** USER CUSTOMIZABLE: - // Title and message for the dialog shown when the real libWrapper is not installed. - const FALLBACK_MESSAGE_TITLE = PACKAGE_TITLE; - const FALLBACK_MESSAGE = ` -

'${PACKAGE_TITLE}' depends on the 'libWrapper' module, which is not present.

-

A fallback implementation will be used, which increases the chance of compatibility issues with other modules.

-

'libWrapper' is a library which provides package developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other packages.

-

You can install it from the "Add-on Modules" tab in the Foundry VTT Setup, from the Foundry VTT package repository, or from libWrapper's Github page.

- `; - - // Settings key used for the "Don't remind me again" setting - const DONT_REMIND_AGAIN_KEY = "libwrapper-dont-remind-again"; - - // Dialog code - console.warn(`${PACKAGE_TITLE}: libWrapper not present, using fallback implementation.`); - game.settings.register(PACKAGE_ID, DONT_REMIND_AGAIN_KEY, { name: '', default: false, type: Boolean, scope: 'world', config: false }); - if(game.user.isGM && !game.settings.get(PACKAGE_ID, DONT_REMIND_AGAIN_KEY)) { - new Dialog({ - title: FALLBACK_MESSAGE_TITLE, - content: FALLBACK_MESSAGE, buttons: { - ok: { icon: '', label: 'Understood' }, - dont_remind: { icon: '', label: "Don't remind me again", callback: () => game.settings.set(PACKAGE_ID, DONT_REMIND_AGAIN_KEY, true) } - } - }).render(true); - } - }); - } -}); \ No newline at end of file diff --git a/scripts/module.js b/scripts/module.js index 6c08a1c..e271ddc 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -1,27 +1,24 @@ -import { libWrapper } from "./lib/libWrapper/shim.js"; -import configure_settings, { configure_hotkeys, keyboardState } from "./settings.js"; -import * as Wrapper from "./wrappers.js" +import { configure_settings, configure_hotkeys, keyboardState, run_migrations } from "./settings.js"; import CONSTANTS from "./constants.js"; import TokenEaseConfig from "./token-ease-config-app.js"; +import { easeFunctions } from "./lib/ease.js"; Hooks.once('init', async function() { - - Wrapper.coreAnimateMovement(); - Wrapper.coreAnimateFrame(); - Wrapper.coreAnimatePromise(); - Wrapper.coreTerminateAnimation(); - Wrapper.coreTokenAnimateLinear(); - Wrapper.coreRulerMoveToken(); - - console.log("Token Ease | Patched core functions"); configure_settings(); configure_hotkeys(); console.log("Token Ease | Ready to (pl)ease!"); }); +Hooks.once("ready", async function () { + run_migrations(); + for(const [name, func] of Object.entries(easeFunctions)){ + CanvasAnimation[name] = func; + } +}) + -Hooks.on('preUpdateToken', (token, changes, data) => { +Hooks.on('preUpdateToken', (token, changes, data, ...args) => { // If position hasn't changed, or animate is false, don't change anything. if(!(changes.x || changes.y) || data.animate === false) return; @@ -35,17 +32,17 @@ Hooks.on('preUpdateToken', (token, changes, data) => { if(!game.settings.get("token-ease", "animation-on-movement-keys")) { const ray = new Ray( - { x: token.data.x, y: token.data.y }, - { x: changes?.x ?? token.data.x, y: changes?.y ?? token.data.y } + { x: token.x, y: token.y }, + { x: changes?.x ?? token.x, y: changes?.y ?? token.y } ); // If movement distance <= grid size, and play animation on movement keys isn't enabled, revert to foundry default let smallMovement = Math.max(Math.abs(ray.dx), Math.abs(ray.dy)) <= canvas.grid.size; if (smallMovement && !data?.animation?.speed && !data?.animation?.duration) { setProperty(data, `animation`, { - speed: 10, + movementSpeed: 10, duration: 0, - ease: "linear" + easing: "linear" }); } } @@ -54,11 +51,13 @@ Hooks.on('preUpdateToken', (token, changes, data) => { const tokenFlags = token.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.MOVEMENT_FLAG); // Set a temporary flag for this animation's data - setProperty(changes, `flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.ANIMATION_FLAG}`, { - speed: data?.animation?.speed ?? tokenFlags?.speed ?? game.settings.get("token-ease", "default-speed"), - duration: data?.animation?.duration ?? tokenFlags?.duration ?? game.settings.get("token-ease", "default-duration"), - ease: data?.animation?.ease ?? tokenFlags?.ease ?? game.settings.get("token-ease", "default-ease") + setProperty(data, `animation`, { + movementSpeed: data?.animation?.speed ?? tokenFlags?.speed ?? game.settings.get("token-ease", "default-speed") ?? 6, + duration: data?.animation?.duration ?? tokenFlags?.duration ?? game.settings.get("token-ease", "default-duration") ?? 0, + easing: data?.animation?.ease ?? tokenFlags?.ease ?? game.settings.get("token-ease", "default-ease") ?? "linear" }); + + console.log(data); } }); diff --git a/scripts/settings.js b/scripts/settings.js index 31e616c..86b6ceb 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -16,16 +16,23 @@ const debouncedDoubleCheckEase = debounce(() => { game.settings.set("token-ease", "default-ease", ease); }, 100) -export default function configure_settings(){ +export function configure_settings(){ window.easeFunctions = easeFunctions; + game.settings.register("token-ease", "module-version", { + scope: "world", + config: false, + default: "", + type: String + }); + game.settings.register("token-ease", "default-speed", { name: game.i18n.format("TOKEN-EASE.speed-title"), hint: game.i18n.format("TOKEN-EASE.speed-description"), scope: "world", config: true, - default: 10, + default: 6, type: Number }); @@ -77,6 +84,20 @@ export default function configure_settings(){ default: false, type: Boolean }); + +} + +export async function run_migrations(){ + + + if(game.settings.get("token-ease", "module-version") === ""){ + let defaultSpeed = game.settings.get("token-ease", "default-speed"); + defaultSpeed = Math.floor(defaultSpeed * 0.6); + await game.settings.set("token-ease", "default-speed", defaultSpeed); + } + + await game.settings.set("token-ease", "module-version", game.modules.get("token-ease").version); + } export const keyboardState = { diff --git a/scripts/wrappers.js b/scripts/wrappers.js deleted file mode 100644 index e976817..0000000 --- a/scripts/wrappers.js +++ /dev/null @@ -1,165 +0,0 @@ -import CONSTANTS from "./constants.js"; -import { libWrapper } from "./lib/libWrapper/shim.js"; - -export function coreAnimateMovement(){ - - libWrapper.register(CONSTANTS.MODULE_NAME, "Token.prototype.animateMovement", async function animateMovement(ray) { - - this._movement = ray; - let animation = this.document.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.ANIMATION_FLAG); - - // Move distance is 10 spaces per second - const speed = canvas.dimensions.size * (animation?.speed || 10); - const duration = animation?.duration || (ray.distance * 1000) / speed; - - // Define attributes - const attributes = [ - { parent: this, attribute: 'x', to: ray.B.x }, - { parent: this, attribute: 'y', to: ray.B.y } - ]; - - // Determine what type of updates should be animated - const emits = this.emitsLight; - const config = { - animate: game.settings.get("core", "visionAnimation"), - source: this._isVisionSource() || emits, - sound: this._controlled || this.observer, - forceUpdateFog: emits && !this._controlled && (canvas.sight.sources.size > 0) - } - - // Dispatch the animation function - await CanvasAnimation.animateLinear(attributes, { - name: this.movementAnimationName, - context: this, - duration: duration, - ontick: (dt, anim) => this._onMovementFrame(dt, anim, config) - }); - - // Once animation is complete perform a final refresh - if ( !config.animate ) this._animatePerceptionFrame({source: config.source, sound: config.sound}); - this._movement = null; - }, - "OVERRIDE" - ); -} - -export function coreTerminateAnimation() { - libWrapper.register(CONSTANTS.MODULE_NAME, 'CanvasAnimation.terminateAnimation', function terminateAnimation(wrapper, ...args) { - const [name] = args; - - if (!name.includes("Token")) return wrapper(...args) - - let animation = this.animations[name]; - if (animation) animation.terminate = true; - }, 'MIXED') -} - -export function coreRulerMoveToken() { - libWrapper.register(CONSTANTS.MODULE_NAME, 'Ruler.prototype.moveToken', async function preRulerMove(wrapper) { - const token = this._getMovementToken(); - if (!token) return wrapper(); - - const allowed = Hooks.call('preTokenChainMove', token, this); - if (!allowed) { - console.log("Token movement prevented by 'preTokenChainMove' hook."); - this.destination = null; - this._endMeasurement(); - } - - return wrapper(); - }, 'WRAPPER'); -} - - -export function coreTokenAnimateLinear() { - libWrapper.register(CONSTANTS.MODULE_NAME, 'CanvasAnimation.animateLinear', function preAnimateLinearHook(wrapper, ...args) { - const [attributes, fnData] = args; - let {context, name, duration, ontick} = fnData; - - if (!(context instanceof Token)) return wrapper(...args); - - let data = { - duration: duration, - config: { - animate: game.settings.get("core", "visionAnimation"), - source: context._isVisionSource() || context.emitsLight, - sound: context._controlled || context.observer, - forceUpdateFog: context.emitsLight && !context._controlled && (canvas.sight.sources.size > 0) - }, - ontick: null - } - data.ontick = (dt, anim) => context._onMovementFrame(dt, anim, data.config) - - Hooks.call('preTokenAnimate', context, data) - - return wrapper(attributes, { - context: context, - name: name, - duration: data.duration, - ontick: data.ontick - }); - }, 'WRAPPER'); -} - -export function coreAnimatePromise() { - libWrapper.register(CONSTANTS.MODULE_NAME, 'CanvasAnimation._animatePromise', async function animatePromise(wrapper, ...args) { - const attributes = args[3]; - const token = attributes[0]?.parent; - await wrapper(...args); - if ((token instanceof Token)) Hooks.callAll('tokenAnimationComplete', token); - }, 'WRAPPER'); -} - -export function coreAnimateFrame() { - libWrapper.register(CONSTANTS.MODULE_NAME, 'CanvasAnimation._animateFrame', function animateFrame(...args) { - - let [deltaTime, resolve, reject, attributes, duration, ontick] = args; - - let ease = window.easeFunctions["linear"]; - - const target = attributes[0]?.parent; - if (target instanceof Token) { - const animationName = target.movementAnimationName; - if (CanvasAnimation.animations[animationName]?.terminate) { - Hooks.callAll('tokenAnimationTerminated', attributes); - return resolve(true); - } - - if(target.document) { - let tokenEase = target.document.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.ANIMATION_FLAG)?.ease; - if(window.easeFunctions[tokenEase]){ - ease = window.easeFunctions[tokenEase]; - } - } - } - - let complete = attributes.length === 0; - let dt = (duration * PIXI.settings.TARGET_FPMS) / deltaTime; - - // Update each attribute - try { - for (let a of attributes) { - let da = a.delta / dt; - a.d = da; - if (a.remaining < (Math.abs(da) * 1.25)) { - a.parent[a.attribute] = a.to; - a.done = a.delta; - a.remaining = 0; - complete = true; - } else { - a.parent[a.attribute] += da; - a.done += da; - a.remaining = Math.abs(a.delta) - Math.abs(a.done); - a.parent[a.attribute] = (a.to - a.delta) + ease(a.done / a.delta) * a.delta; - } - } - if (ontick) ontick(dt, attributes); - } catch (err) { - reject(err); - } - - // Resolve the original promise once the animation is complete - if (complete) resolve(true); - - }, 'OVERRIDE'); -} \ No newline at end of file