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.

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