All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

95 lines
3.3 KiB

1 year ago
  1. import { getSetting } from './settings.js';
  2. const rad = Math.PI * 2, baseRotation = Math.PI / 4;
  3. function repositionToken(token, rotation, offset, pos = 0) {
  4. 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;
  5. token.border.x = token.document.x - x;
  6. token.border.y = token.document.y - y;
  7. token.hitArea.x = -x;
  8. token.hitArea.y = -y;
  9. const gridOffset = size / 2;
  10. token.mesh.x = token.border.x + gridOffset * token.document.width;
  11. token.mesh.y = token.border.y + gridOffset * token.document.height;
  12. }
  13. let SNAPPED_TOKENS = [];
  14. function findGroup(token) {
  15. for (const group of SNAPPED_TOKENS) {
  16. for (const t of group) {
  17. if (token === t)
  18. return group;
  19. }
  20. }
  21. }
  22. function sameGroup(oldGroup, newGroup) {
  23. if (oldGroup.length !== newGroup.length)
  24. return false;
  25. for (const t of oldGroup) {
  26. if (!newGroup.includes(t))
  27. return false;
  28. }
  29. return true;
  30. }
  31. export function refreshAll(groups = SNAPPED_TOKENS) {
  32. for (const t of SNAPPED_TOKENS.flat()) {
  33. t.object?.refresh();
  34. }
  35. }
  36. function snapToken(token, options) {
  37. if (token.isAnimating)
  38. return;
  39. if (!getSetting('snapTokens')) {
  40. token.hitArea.x = 0;
  41. token.hitArea.y = 0;
  42. return;
  43. }
  44. const oldGroup = findGroup(token.document);
  45. const x = token.document.x, y = token.document.y, height = token.document.height, width = token.document.width;
  46. const ignoreDead = getSetting('ignoreDead');
  47. const tokens = token.scene.tokens.contents.filter((token) => !token.object?.destroyed &&
  48. token.object.x === x &&
  49. token.object.y === y &&
  50. token.height === height &&
  51. token.width === width &&
  52. !(ignoreDead && checkStatus(token, ['dead', 'dying', 'unconscious'])) &&
  53. token.object.visible);
  54. if (tokens.length < 2) {
  55. token.hitArea.x = 0;
  56. token.hitArea.y = 0;
  57. if (oldGroup) {
  58. if (oldGroup.length > 1) {
  59. const idx = oldGroup.indexOf(token.document);
  60. oldGroup.splice(idx, 1);
  61. refreshAll(oldGroup);
  62. }
  63. else {
  64. const idx = SNAPPED_TOKENS.indexOf(oldGroup);
  65. SNAPPED_TOKENS.splice(idx, 1);
  66. }
  67. }
  68. return;
  69. }
  70. if (oldGroup && !sameGroup(oldGroup, tokens)) {
  71. const idx = oldGroup.indexOf(token.document);
  72. oldGroup.splice(idx, 1);
  73. if (oldGroup.length)
  74. refreshAll(oldGroup);
  75. else {
  76. const idx = SNAPPED_TOKENS.indexOf(oldGroup);
  77. SNAPPED_TOKENS.splice(idx, 1);
  78. }
  79. }
  80. const newGroup = findGroup(tokens.find((t) => t !== token.document));
  81. if (newGroup) {
  82. const idx = SNAPPED_TOKENS.indexOf(newGroup);
  83. SNAPPED_TOKENS.splice(idx, 1);
  84. }
  85. SNAPPED_TOKENS.push(tokens);
  86. const angle = rad / tokens.length;
  87. const offset = getSetting('scatter');
  88. for (let i = 0; i < tokens.length; i++)
  89. repositionToken(tokens[i].object, angle, offset, i);
  90. }
  91. function checkStatus(token, status) {
  92. return status.some((s) => token.hasStatusEffect(s));
  93. }
  94. Hooks.on('refreshToken', snapToken);
  95. Hooks.on('canvasTearDown', () => (SNAPPED_TOKENS = []));