Skip to content

Commit

Permalink
Merge pull request #11 from fantasycalendar/Hooking_Tokens
Browse files Browse the repository at this point in the history
Hooking tokens
  • Loading branch information
Haxxer authored Feb 17, 2022
2 parents 6f77c8f + 0af6586 commit 87f7cff
Show file tree
Hide file tree
Showing 11 changed files with 627 additions and 253 deletions.
190 changes: 187 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ No more will you suffer the lethargic slog of transporting tokens across the can

## Credits

* Kerrec Snowmane for his implementation of the hooks
* League of Extraordinary FoundryVTT Developers
* [Easing Functions Cheat Sheet](https://easings.net/) ([GitHub](https://github.com/ai/easings.net)) - Copyright © 2020 Andrey Sitnik and Ivan Solovev

## Download Here:

`https://github.com/FantasyCalendar/FoundryVTT-TokenEase/releases/latest/download/module.json`
`https://github.com/fantasycalendar/FoundryVTT-TokenEase/releases/latest/download/module.json`


## Module Settings

Expand Down Expand Up @@ -80,9 +82,15 @@ If enabled, this will make moving tokens with the movement keys (arrow keys, etc

We do not recommend enabling this, as the movement distance is so short when using movement keys.

## Token Settings

Each token can override the module's default movement animation settings.

![Token settings](images/token_settings.png)

## API

### Changes to `Token.Document.Update`
### Changes to `TokenDocument#update`

This accepts an additional optional parameter in its second parameter:

Expand Down Expand Up @@ -148,4 +156,180 @@ function linear(x) {
* `easeInOutElastic`
* `easeInBounce`
* `easeOutBounce`
* `easeInOutBounce`
* `easeInOutBounce`

## Hooks

### preTokenAnimate

Called before a token has started animating.

You can `return false` to interrupt the movement.

| Param | Type |
| --- | --- |
| context | <code>Token</code> |
| data | <code>object</code> |

<details>

<summary>Example hook</summary>

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;
})
```

</details>

### preTokenChainMove

Called before a token has started animating along several waypoints.

You can `return false` to interrupt the movement.

| Param | Type |
| --- | --- |
| token | <code>Token</code> |
| ruler | <code>Ruler</code> |

<details>

<summary>Example hook</summary>

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.

```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;
})
```

</details>

### tokenAnimationComplete

Called when a token's animation completed playing entirely.

| Param | Type |
| --- | --- |
| token | <code>Token</code> |

<details>

<summary>Example hook</summary>

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...")
})
```

</details>

### tokenAnimationTerminated

Called when a token's animation was terminated early, without having finished.

| Param | Type |
| --- | --- |
| attributes | <code>array</code> |

<details>

<summary>Example hook</summary>

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});
})
```

</details>
Binary file added images/token_settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 6 additions & 4 deletions languages/en.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
{
"TOKEN-EASE.speed-title": "Token Movement Speed",
"TOKEN-EASE.speed-description": "Sets the default animation speed of moving tokens, in spaces per second (10 is Foundry default).",
"TOKEN-EASE.speed-description": "Sets the default animation speed, in spaces per second (10 is Foundry default).",
"TOKEN-EASE.duration-title": "Token Movement Duration",
"TOKEN-EASE.duration-description": "Sets the default animation duration for token movement (in milliseconds). This overrides Token Speed, and causes tokens to reach their destination in a set amount of time, regardless of distance.",
"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",
"TOKEN-EASE.ease-description": "Sets the type of ease used by the movement animation on tokens.",
"TOKEN-EASE.ease-description": "Sets the type of ease used by the movement animation.",
"TOKEN-EASE.ease-type-title": "Ease In/Out",
"TOKEN-EASE.ease-type-description": "Sets the type of easing to use at the start/end of the animation. If Token Movement Ease is set to Linear, this has no impact.",
"TOKEN-EASE.movement-keys-title": "Play animation on keypad movement",
"TOKEN-EASE.movement-keys-description": "If enabled, this will make moving tokens with the movement keys (arrow keys, etc) to play the animations configured above. We do not recommend enabling this, as the movement distance is so short when using movement keys.",

"TOKEN-EASE.InstantMovement": "Instant Movement Hotkey (hold)"
"TOKEN-EASE.override": "Override Token-Ease default settings for this token",

"TOKEN-EASE.instant-movement": "Instant Movement Hotkey (hold)"
}
82 changes: 46 additions & 36 deletions module.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,48 @@
{
"name": "token-ease",
"title": "Token Ease",
"description": "This module extends native Foundry behavior to introduce easing, custom movement speed, and movement duration to tokens.",
"author": "Wasp (Wasp#2005)",
"authors": [],
"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"
"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"
}
7 changes: 7 additions & 0 deletions scripts/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const CONSTANTS = {
MODULE_NAME: "token-ease",
MOVEMENT_FLAG: "movement",
ANIMATION_FLAG: "animation"
}

export default CONSTANTS;
13 changes: 8 additions & 5 deletions scripts/lib/libWrapper/shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// A shim for the libWrapper library
export let libWrapper = undefined;

export const VERSIONS = [1,11,0];
export const VERSIONS = [1,12,1];
export const TGT_SPLIT_RE = new RegExp("([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])", 'g');
export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", 'g');

Expand All @@ -27,7 +27,7 @@ Hooks.once('init', () => {
static get MIXED() { return 'MIXED' };
static get OVERRIDE() { return 'OVERRIDE' };

static register(package_id, target, fn, type="MIXED", {chain=undefined}={}) {
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,''));
Expand All @@ -51,10 +51,13 @@ Hooks.once('init', () => {
if(descriptor) break;
iObj = Object.getPrototypeOf(iObj);
}
if(!descriptor || descriptor?.configurable === false) throw `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`;
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() { return fn.call(this, original.bind(this), ...arguments); } : function() { return fn.apply(this, arguments); };
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) {
Expand All @@ -67,7 +70,7 @@ Hooks.once('init', () => {
}
}
else {
if(!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`;
if(!descriptor.set) throw new Error(`libWrapper Shim: '${target}' does not have a setter`);
original = descriptor.set;
descriptor.set = wrapper;
}
Expand Down
Loading

0 comments on commit 87f7cff

Please sign in to comment.