|
import { getSetting } from './settings.js';
|
|
const rad = Math.PI * 2, baseRotation = Math.PI / 4;
|
|
function repositionToken(token, rotation, offset, pos = 0) {
|
|
const size = token.scene.dimensions.size, x = Math.sin(rotation * pos + baseRotation) * offset * token.document.width * size, y = Math.cos(rotation * pos + baseRotation) * offset * token.document.height * size;
|
|
token.border.x = token.document.x - x;
|
|
token.border.y = token.document.y - y;
|
|
token.hitArea.x = token.effects.x = token.bars.x = token.target.x = -x;
|
|
token.hitArea.y = token.effects.y = token.bars.y = token.target.y = -y;
|
|
token.nameplate.x = token.w / 2 - x;
|
|
token.nameplate.y = token.h + 2 - y;
|
|
token.tooltip.x = token.w / 2 - x;
|
|
token.tooltip.y = -y - 2;
|
|
const gridOffset = size / 2;
|
|
token.mesh.x = token.border.x + gridOffset * token.document.width;
|
|
token.mesh.y = token.border.y + gridOffset * token.document.height;
|
|
}
|
|
let SNAPPED_TOKENS = [];
|
|
function findGroup(token) {
|
|
for (const group of SNAPPED_TOKENS) {
|
|
for (const t of group) {
|
|
if (token === t)
|
|
return group;
|
|
}
|
|
}
|
|
}
|
|
function sameGroup(oldGroup, newGroup) {
|
|
if (oldGroup.length !== newGroup.length)
|
|
return false;
|
|
for (const t of oldGroup) {
|
|
if (!newGroup.includes(t))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
export function refreshAll(groups = SNAPPED_TOKENS) {
|
|
for (const t of SNAPPED_TOKENS.flat()) {
|
|
t.object?.refresh();
|
|
}
|
|
}
|
|
function snapToken(token, options) {
|
|
if (token.isAnimating)
|
|
return;
|
|
if (!getSetting('snapTokens')) {
|
|
token.hitArea.x = token.effects.x = token.bars.x = token.target.x = 0;
|
|
token.hitArea.y = token.effects.y = token.bars.y = token.target.y = 0;
|
|
return;
|
|
}
|
|
const oldGroup = findGroup(token.document);
|
|
const x = token.document.x, y = token.document.y, height = token.document.height, width = token.document.width;
|
|
const ignoreDead = getSetting('ignoreDead');
|
|
const tokens = token.scene.tokens.contents.filter((token) => !token.object?.destroyed &&
|
|
token.x === x &&
|
|
token.y === y &&
|
|
token.height === height &&
|
|
token.width === width &&
|
|
!(ignoreDead && checkStatus(token, ['dead', 'dying', 'unconscious'])) &&
|
|
token.object.visible);
|
|
if (tokens.length < 2) {
|
|
token.hitArea.x = token.effects.x = token.bars.x = token.target.x = 0;
|
|
token.hitArea.y = token.effects.y = token.bars.y = token.target.y = 0;
|
|
if (oldGroup) {
|
|
if (oldGroup.length > 1) {
|
|
const idx = oldGroup.indexOf(token.document);
|
|
oldGroup.splice(idx, 1);
|
|
refreshAll(oldGroup);
|
|
}
|
|
else {
|
|
const idx = SNAPPED_TOKENS.indexOf(oldGroup);
|
|
SNAPPED_TOKENS.splice(idx, 1);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (oldGroup && !sameGroup(oldGroup, tokens)) {
|
|
const idx = oldGroup.indexOf(token.document);
|
|
oldGroup.splice(idx, 1);
|
|
if (oldGroup.length)
|
|
refreshAll(oldGroup);
|
|
else {
|
|
const idx = SNAPPED_TOKENS.indexOf(oldGroup);
|
|
SNAPPED_TOKENS.splice(idx, 1);
|
|
}
|
|
}
|
|
const newGroup = findGroup(tokens.find((t) => t !== token.document));
|
|
if (newGroup) {
|
|
const idx = SNAPPED_TOKENS.indexOf(newGroup);
|
|
SNAPPED_TOKENS.splice(idx, 1);
|
|
}
|
|
SNAPPED_TOKENS.push(tokens);
|
|
const angle = rad / tokens.length;
|
|
const offset = getSetting('scatter');
|
|
for (let i = 0; i < tokens.length; i++)
|
|
repositionToken(tokens[i].object, angle, offset, i);
|
|
}
|
|
function checkStatus(token, status) {
|
|
return status.some((s) => token.hasStatusEffect(s));
|
|
}
|
|
Hooks.on('refreshToken', snapToken);
|
|
Hooks.on('canvasTearDown', () => (SNAPPED_TOKENS = []));
|