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.

238 lines
7.7 KiB

  1. import { registerSettings, TVA_CONFIG, exportSettingsToJSON, updateSettings } from './scripts/settings.js';
  2. import { ArtSelect, addToArtSelectQueue } from './applications/artSelect.js';
  3. import {
  4. SEARCH_TYPE,
  5. registerKeybinds,
  6. updateTokenImage,
  7. startBatchUpdater,
  8. userRequiresImageCache,
  9. waitForTokenTexture,
  10. } from './scripts/utils.js';
  11. import { FONT_LOADING, drawOverlays } from './scripts/token/overlay.js';
  12. import {
  13. getTokenEffects,
  14. setOverlayVisibility,
  15. toggleTemplate,
  16. toggleTemplateOnSelected,
  17. updateWithEffectMapping,
  18. } from './scripts/hooks/effectMappingHooks.js';
  19. import { cacheImages, doImageSearch, doRandomSearch, isCaching } from './scripts/search.js';
  20. import { REGISTERED_HOOKS, registerAllHooks, registerHook } from './scripts/hooks/hooks.js';
  21. import { REGISTERED_WRAPPERS, registerAllWrappers } from './scripts/wrappers/wrappers.js';
  22. import {
  23. assignUserSpecificImage,
  24. assignUserSpecificImageToSelected,
  25. unassignUserSpecificImage,
  26. unassignUserSpecificImageFromSelected,
  27. } from './scripts/wrappers/userMappingWrappers.js';
  28. import { toggleTemplateDialog } from './applications/dialogs.js';
  29. // Tracks if module has been initialized
  30. let MODULE_INITIALIZED = false;
  31. export function isInitialized() {
  32. return MODULE_INITIALIZED;
  33. }
  34. let onInit = [];
  35. /**
  36. * Initialize the Token Variants module on Foundry VTT init
  37. */
  38. async function initialize() {
  39. // Initialization should only be performed once
  40. if (MODULE_INITIALIZED) {
  41. return;
  42. }
  43. // Font Awesome need to be loaded manually on FireFox
  44. FONT_LOADING.loading = FontConfig.loadFont('fontAwesome', {
  45. editor: false,
  46. fonts: [{ urls: ['fonts/fontawesome/webfonts/fa-solid-900.ttf'] }],
  47. });
  48. // Want this to be executed once the module has initialized
  49. onInit.push(() => {
  50. // Need to wait for icons do be drawn first however I could not find a way
  51. // to wait until that has occurred. Instead we'll just wait for some static
  52. // amount of time.
  53. new Promise((resolve) => setTimeout(resolve, 500)).then(() => {
  54. for (const tkn of canvas.tokens.placeables) {
  55. drawOverlays(tkn); // Draw Overlays
  56. // Disable effect icons
  57. if (TVA_CONFIG.disableEffectIcons) {
  58. waitForTokenTexture(tkn, (token) => {
  59. token.effects.removeChildren().forEach((c) => c.destroy());
  60. token.effects.bg = token.effects.addChild(new PIXI.Graphics());
  61. token.effects.overlay = null;
  62. });
  63. } else if (TVA_CONFIG.filterEffectIcons) {
  64. waitForTokenTexture(tkn, (token) => {
  65. token.drawEffects();
  66. });
  67. }
  68. }
  69. });
  70. });
  71. if (userRequiresImageCache()) cacheImages();
  72. // Register ALL Hooks
  73. registerAllHooks();
  74. // Startup ticker that will periodically call 'updateEmbeddedDocuments' with all the accrued updates since the last tick
  75. startBatchUpdater();
  76. registerHook('Search', 'renderArtSelect', () => {
  77. ArtSelect.executing = false;
  78. });
  79. // Handle broadcasts
  80. game.socket?.on(`module.token-variants`, (message) => {
  81. if (message.handlerName === 'forgeSearchPaths' && message.type === 'UPDATE') {
  82. // Workaround for forgeSearchPaths setting to be updated by non-GM clients
  83. if (!game.user.isGM) return;
  84. const isResponsibleGM = !game.users
  85. .filter((user) => user.isGM && (user.active || user.isActive))
  86. .some((other) => other.id < game.user.id);
  87. if (!isResponsibleGM) return;
  88. updateSettings({ forgeSearchPaths: message.args });
  89. } else if (message.handlerName === 'drawOverlays' && message.type === 'UPDATE') {
  90. if (message.args.all) {
  91. if (canvas.scene.id !== message.args.sceneId) {
  92. for (const tkn of canvas.tokens.placeables) {
  93. drawOverlays(tkn);
  94. }
  95. }
  96. } else if (message.args.actorId) {
  97. const actor = game.actors.get(message.args.actorId);
  98. if (actor) actor.getActiveTokens(true)?.forEach((tkn) => drawOverlays(tkn));
  99. } else if (message.args.tokenId) {
  100. const tkn = canvas.tokens.get(message.args.tokenId);
  101. if (tkn) drawOverlays(tkn);
  102. }
  103. } else if (message.handlerName === 'effectMappings') {
  104. if (!game.user.isGM) return;
  105. const isResponsibleGM = !game.users
  106. .filter((user) => user.isGM && (user.active || user.isActive))
  107. .some((other) => other.id < game.user.id);
  108. if (!isResponsibleGM) return;
  109. const args = message.args;
  110. const token = game.scenes.get(args.sceneId)?.tokens.get(args.tokenId);
  111. if (token) updateWithEffectMapping(token, { added: args.added, removed: args.removed });
  112. }
  113. });
  114. MODULE_INITIALIZED = true;
  115. for (const cb of onInit) {
  116. cb();
  117. }
  118. onInit = [];
  119. }
  120. /**
  121. * Performs searches and displays the Art Select pop-up with the results
  122. * @param {string} search The text to be used as the search criteria
  123. * @param {object} [options={}] Options which customize the search
  124. * @param {Function[]} [options.callback] Function to be called with the user selected image path
  125. * @param {SEARCH_TYPE|string} [options.searchType] Controls filters applied to the search results
  126. * @param {Token|Actor} [options.object] Token/Actor used when displaying Custom Token Config prompt
  127. * @param {boolean} [options.force] If true will always override the current Art Select window if one exists instead of adding it to the queue
  128. * @param {object} [options.searchOptions] Override search and filter settings
  129. */
  130. export async function showArtSelect(
  131. search,
  132. {
  133. callback = null,
  134. searchType = SEARCH_TYPE.PORTRAIT_AND_TOKEN,
  135. object = null,
  136. force = false,
  137. preventClose = false,
  138. image1 = '',
  139. image2 = '',
  140. displayMode = ArtSelect.IMAGE_DISPLAY.NONE,
  141. multipleSelection = false,
  142. searchOptions = {},
  143. allImages = null,
  144. } = {}
  145. ) {
  146. if (isCaching()) return;
  147. const artSelects = Object.values(ui.windows).filter((app) => app instanceof ArtSelect);
  148. if (ArtSelect.executing || (!force && artSelects.length !== 0)) {
  149. addToArtSelectQueue(search, {
  150. callback,
  151. searchType,
  152. object,
  153. preventClose,
  154. searchOptions,
  155. allImages,
  156. });
  157. return;
  158. }
  159. ArtSelect.executing = true;
  160. if (!allImages)
  161. allImages = await doImageSearch(search, {
  162. searchType: searchType,
  163. searchOptions: searchOptions,
  164. });
  165. new ArtSelect(search, {
  166. allImages: allImages,
  167. searchType: searchType,
  168. callback: callback,
  169. object: object,
  170. preventClose: preventClose,
  171. image1: image1,
  172. image2: image2,
  173. displayMode: displayMode,
  174. multipleSelection: multipleSelection,
  175. searchOptions: searchOptions,
  176. }).render(true);
  177. }
  178. // Initialize module
  179. registerHook('main', 'ready', initialize, { once: true });
  180. // Register API and Keybinds
  181. registerHook('main', 'init', function () {
  182. registerSettings();
  183. registerAllWrappers();
  184. registerKeybinds();
  185. const api = {
  186. cacheImages,
  187. doImageSearch,
  188. doRandomSearch,
  189. getTokenEffects,
  190. showArtSelect,
  191. updateTokenImage,
  192. exportSettingsToJSON,
  193. assignUserSpecificImage,
  194. assignUserSpecificImageToSelected,
  195. unassignUserSpecificImage,
  196. unassignUserSpecificImageFromSelected,
  197. setOverlayVisibility,
  198. toggleTemplateDialog,
  199. toggleTemplate,
  200. toggleTemplateOnSelected,
  201. TVA_CONFIG,
  202. };
  203. Object.defineProperty(api, 'hooks', {
  204. get() {
  205. return deepClone(REGISTERED_HOOKS);
  206. },
  207. configurable: true,
  208. });
  209. Object.defineProperty(api, 'wrappers', {
  210. get() {
  211. return deepClone(REGISTERED_WRAPPERS);
  212. },
  213. configurable: true,
  214. });
  215. game.modules.get('token-variants').api = api;
  216. });