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.

314 lines
9.2 KiB

1 year ago
  1. import {getPixelsFromGridPosition} from "./foundry_fixes.js";
  2. import {findVertexSnapPoint} from "./hex_support.js";
  3. import {disableSnap, moveWithoutAnimation, togglePathfinding} from "./keybindings.js";
  4. import {settingsKey} from "./settings.js";
  5. export function* zip(it1, it2) {
  6. for (let i = 0; i < Math.min(it1.length, it2.length); i++) {
  7. yield [it1[i], it2[i]];
  8. }
  9. }
  10. export function* enumeratedZip(it1, it2) {
  11. let i = 0;
  12. for (const [v1, v2] of zip(it1, it2)) {
  13. yield [i, v1, v2];
  14. i++;
  15. }
  16. }
  17. export function* iterPairs(l) {
  18. for (let i = 1; i < l.length; i++) {
  19. yield [l[i - 1], l[i]];
  20. }
  21. }
  22. export function sum(arr) {
  23. return arr.reduce((a, b) => a + b, 0);
  24. }
  25. // A copy of this function lives in the routinglib module
  26. export function getHexTokenSize(token) {
  27. const size = token.document.width;
  28. if (token.document.height !== size) {
  29. return 1;
  30. }
  31. return size;
  32. }
  33. export function getEntityCenter(token) {
  34. if (token instanceof Token && canvas.grid.isHex) {
  35. const center = token.center;
  36. const size = getHexTokenSize(token);
  37. if (size % 2 === 0) {
  38. let offset;
  39. if (canvas.grid.grid.columnar) {
  40. offset = canvas.grid.grid.w - canvas.grid.grid.h;
  41. } else {
  42. offset = canvas.grid.grid.h - canvas.grid.grid.w;
  43. }
  44. if (getAltOrientationFlagForToken(token, size)) {
  45. offset *= -1;
  46. }
  47. if (canvas.grid.grid.columnar) {
  48. center.x -= offset;
  49. return center;
  50. } else {
  51. center.y -= offset;
  52. return center;
  53. }
  54. }
  55. }
  56. return token.center;
  57. }
  58. // A copy of this function lives in the routinglib module
  59. export function getAltOrientationFlagForToken(token, size) {
  60. const hexSizeSupport = game.modules.get("hex-size-support")?.api;
  61. if (hexSizeSupport) {
  62. return hexSizeSupport.isAltOrientation(token);
  63. }
  64. // In native foundry, tokens of size 2 are oriented like the "alt orientation" from hex-size-support
  65. // Tokens of size 4 are oriented like alt orientation wasn't set
  66. return size === 2;
  67. }
  68. // A copy of this function lives in the librouting module
  69. export function getSnapPointForToken(x, y, token) {
  70. if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
  71. return {x, y};
  72. }
  73. if (canvas.grid.isHex) {
  74. const size = getHexTokenSize(token);
  75. if (size % 2 === 0) {
  76. return findVertexSnapPoint(x, y, getAltOrientationFlagForToken(token, size));
  77. }
  78. const [snapX, snapY] = canvas.grid.getCenter(x, y);
  79. return {x: snapX, y: snapY};
  80. }
  81. const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
  82. let cellX, cellY;
  83. if (token.document.width % 2 === 0) cellX = x - canvas.grid.h / 2;
  84. else cellX = x;
  85. if (token.document.height % 2 === 0) cellY = y - canvas.grid.h / 2;
  86. else cellY = y;
  87. const [centerX, centerY] = canvas.grid.getCenter(cellX, cellY);
  88. let snapX, snapY;
  89. // Tiny tokens can snap to the cells corners
  90. if (token.document.width <= 0.5) {
  91. const offsetX = x - topLeftX;
  92. const subGridWidth = Math.floor(canvas.grid.w / 2);
  93. const subGridPosX = Math.floor(offsetX / subGridWidth);
  94. snapX = topLeftX + (subGridPosX + 0.5) * subGridWidth;
  95. }
  96. // Tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
  97. else if (Math.round(token.document.width) % 2 === 1 || token.document.width < 1) {
  98. snapX = centerX;
  99. }
  100. // All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
  101. else {
  102. snapX = centerX + canvas.grid.w / 2;
  103. }
  104. if (token.document.height <= 0.5) {
  105. const offsetY = y - topLeftY;
  106. const subGridHeight = Math.floor(canvas.grid.h / 2);
  107. const subGridPosY = Math.floor(offsetY / subGridHeight);
  108. snapY = topLeftY + (subGridPosY + 0.5) * subGridHeight;
  109. } else if (Math.round(token.document.height) % 2 === 1 || token.document.height < 1) {
  110. snapY = centerY;
  111. } else {
  112. snapY = centerY + canvas.grid.h / 2;
  113. }
  114. return {x: snapX, y: snapY};
  115. }
  116. export function getSnapPointForTokenObj(pos, token) {
  117. return getSnapPointForToken(pos.x, pos.y, token);
  118. }
  119. export function getSnapPointForMeasuredTemplate(x, y) {
  120. if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
  121. return new PIXI.Point(x, y);
  122. }
  123. return canvas.grid.grid.getSnappedPosition(x, y, canvas.templates.gridPrecision);
  124. }
  125. export function getSnapPointForEntity(x, y, entity) {
  126. const isToken = entity instanceof Token;
  127. if (isToken) return getSnapPointForToken(x, y, entity);
  128. else return getSnapPointForMeasuredTemplate(x, y);
  129. }
  130. export function highlightTokenShape(position, shape, color, alpha) {
  131. const layer = canvas.grid.highlightLayers[this.name];
  132. if (!layer) return false;
  133. const area = getAreaFromPositionAndShape(position, shape);
  134. for (const space of area) {
  135. const [x, y] = getPixelsFromGridPosition(space.x, space.y);
  136. canvas.grid.grid.highlightGridPosition(layer, {x, y, color, alpha: 0.25 * alpha});
  137. }
  138. }
  139. export function getAreaFromPositionAndShape(position, shape) {
  140. return shape.map(space => {
  141. let x = position.x + space.x;
  142. let y = position.y + space.y;
  143. if (canvas.grid.isHex) {
  144. let shiftedRow;
  145. if (canvas.grid.grid.options.even) shiftedRow = 1;
  146. else shiftedRow = 0;
  147. if (canvas.grid.grid.columnar) {
  148. if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
  149. y += 1;
  150. }
  151. } else {
  152. if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) {
  153. x += 1;
  154. }
  155. }
  156. }
  157. return {x, y};
  158. });
  159. }
  160. // A copy of this function lives in the routinglib module
  161. export function getTokenShape(token) {
  162. let scene = canvas.scene;
  163. if (scene.grid.type === CONST.GRID_TYPES.GRIDLESS) {
  164. return [{x: 0, y: 0}];
  165. } else if (scene.grid.type === CONST.GRID_TYPES.SQUARE) {
  166. const topOffset = -Math.floor(token.document.height / 2);
  167. const leftOffset = -Math.floor(token.document.width / 2);
  168. const shape = [];
  169. for (let y = 0; y < token.document.height; y++) {
  170. for (let x = 0; x < token.document.width; x++) {
  171. shape.push({x: x + leftOffset, y: y + topOffset});
  172. }
  173. }
  174. return shape;
  175. } else {
  176. // Hex grids
  177. const size = getHexTokenSize(token);
  178. let shape = [{x: 0, y: 0}];
  179. if (size >= 2)
  180. shape = shape.concat([
  181. {x: 0, y: -1},
  182. {x: -1, y: -1},
  183. ]);
  184. if (size >= 3)
  185. shape = shape.concat([
  186. {x: 0, y: 1},
  187. {x: -1, y: 1},
  188. {x: -1, y: 0},
  189. {x: 1, y: 0},
  190. ]);
  191. if (size >= 4)
  192. shape = shape.concat([
  193. {x: -2, y: -1},
  194. {x: 1, y: -1},
  195. {x: -1, y: -2},
  196. {x: 0, y: -2},
  197. {x: 1, y: -2},
  198. ]);
  199. if (getAltOrientationFlagForToken(token, size)) {
  200. shape.forEach(space => (space.y *= -1));
  201. }
  202. if (canvas.grid.grid.columnar)
  203. shape = shape.map(space => {
  204. return {x: space.y, y: space.x};
  205. });
  206. return shape;
  207. }
  208. }
  209. export function getTokenSize(token) {
  210. let w, h;
  211. if (canvas.grid.isHex) {
  212. w = h = getHexTokenSize(token);
  213. } else {
  214. w = token.document.width;
  215. h = token.document.height;
  216. }
  217. return {w, h};
  218. }
  219. // Tokens that have a size divisible by two (2x2, 4x4, 2x1) have their ruler at the edge of a cell.
  220. // This function applies an offset to to the waypoints that will move the ruler from the edge to the center of the cell
  221. export function applyTokenSizeOffset(waypoints, token) {
  222. if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
  223. return waypoints;
  224. }
  225. const tokenSize = getTokenSize(token);
  226. const waypointOffset = {x: 0, y: 0};
  227. if (canvas.grid.isHex) {
  228. const isAltOrientation = getAltOrientationFlagForToken(token, getHexTokenSize(token));
  229. if (canvas.grid.grid.columnar) {
  230. if (tokenSize.w % 2 === 0) {
  231. waypointOffset.x = canvas.grid.w / 2;
  232. if (isAltOrientation) waypointOffset.x *= -1;
  233. }
  234. } else {
  235. if (tokenSize.h % 2 === 0) {
  236. waypointOffset.y = canvas.grid.h / 2;
  237. if (isAltOrientation) waypointOffset.y *= -1;
  238. }
  239. }
  240. // If hex size support isn't active leave the waypoints like they are
  241. } else {
  242. if (tokenSize.w % 2 === 0) {
  243. waypointOffset.x = canvas.grid.w / 2;
  244. }
  245. if (tokenSize.h % 2 === 0) {
  246. waypointOffset.y = canvas.grid.h / 2;
  247. }
  248. }
  249. return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y));
  250. }
  251. export function setSnapParameterOnOptions(sourceObject, options) {
  252. // Allow outside modules to override snapping
  253. if (sourceObject.snapOverride?.active) {
  254. options.snapOverrideActive = true;
  255. options.snap = sourceObject.snapOverride.snap;
  256. sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
  257. } else {
  258. options.snap = !disableSnap;
  259. }
  260. }
  261. export function isClose(a, b, delta) {
  262. return Math.abs(a - b) <= delta;
  263. }
  264. export function getMeasurePosition() {
  265. const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(
  266. canvas.tokens,
  267. );
  268. const rulerOffset = canvas.controls.ruler.rulerOffset;
  269. const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
  270. return measurePosition;
  271. }
  272. // isGM function for use during loading when game.user isn't available yet
  273. export function early_isGM() {
  274. const level = game.data.users.find(u => u._id == game.data.userId).role;
  275. const gmLevel = CONST.USER_ROLES.ASSISTANT;
  276. return level >= gmLevel;
  277. }
  278. export function isModuleActive(moduleName) {
  279. return game.modules.get(moduleName)?.active;
  280. }
  281. export function isPathfindingEnabled() {
  282. if (!window.routinglib) return false;
  283. if (this.user !== game.user) return false;
  284. if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding")) return false;
  285. if (moveWithoutAnimation) return false;
  286. return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
  287. }