diff --git a/README.md b/README.md
index c518104..33d3b46 100644
--- a/README.md
+++ b/README.md
@@ -10,14 +10,14 @@
-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;
-})
-```
-
-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;
-})
-```
-
-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...")
-})
-```
-
-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});
-})
-```
+---
-
'${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