import { registerSettings, TVA_CONFIG, exportSettingsToJSON, updateSettings } from './scripts/settings.js';
|
|
import { ArtSelect, addToArtSelectQueue } from './applications/artSelect.js';
|
|
import {
|
|
SEARCH_TYPE,
|
|
registerKeybinds,
|
|
updateTokenImage,
|
|
startBatchUpdater,
|
|
userRequiresImageCache,
|
|
waitForTokenTexture,
|
|
} from './scripts/utils.js';
|
|
import { FONT_LOADING, drawOverlays } from './scripts/token/overlay.js';
|
|
import {
|
|
getTokenEffects,
|
|
setOverlayVisibility,
|
|
toggleTemplate,
|
|
toggleTemplateOnSelected,
|
|
updateWithEffectMapping,
|
|
} from './scripts/hooks/effectMappingHooks.js';
|
|
import { cacheImages, doImageSearch, doRandomSearch, isCaching } from './scripts/search.js';
|
|
import { REGISTERED_HOOKS, registerAllHooks, registerHook } from './scripts/hooks/hooks.js';
|
|
import { REGISTERED_WRAPPERS, registerAllWrappers } from './scripts/wrappers/wrappers.js';
|
|
import {
|
|
assignUserSpecificImage,
|
|
assignUserSpecificImageToSelected,
|
|
unassignUserSpecificImage,
|
|
unassignUserSpecificImageFromSelected,
|
|
} from './scripts/wrappers/userMappingWrappers.js';
|
|
import { toggleTemplateDialog } from './applications/dialogs.js';
|
|
|
|
// Tracks if module has been initialized
|
|
let MODULE_INITIALIZED = false;
|
|
export function isInitialized() {
|
|
return MODULE_INITIALIZED;
|
|
}
|
|
let onInit = [];
|
|
|
|
/**
|
|
* Initialize the Token Variants module on Foundry VTT init
|
|
*/
|
|
async function initialize() {
|
|
// Initialization should only be performed once
|
|
if (MODULE_INITIALIZED) {
|
|
return;
|
|
}
|
|
|
|
// Font Awesome need to be loaded manually on FireFox
|
|
FONT_LOADING.loading = FontConfig.loadFont('fontAwesome', {
|
|
editor: false,
|
|
fonts: [{ urls: ['fonts/fontawesome/webfonts/fa-solid-900.ttf'] }],
|
|
});
|
|
|
|
// Want this to be executed once the module has initialized
|
|
onInit.push(() => {
|
|
// Need to wait for icons do be drawn first however I could not find a way
|
|
// to wait until that has occurred. Instead we'll just wait for some static
|
|
// amount of time.
|
|
new Promise((resolve) => setTimeout(resolve, 500)).then(() => {
|
|
for (const tkn of canvas.tokens.placeables) {
|
|
drawOverlays(tkn); // Draw Overlays
|
|
|
|
// Disable effect icons
|
|
if (TVA_CONFIG.disableEffectIcons) {
|
|
waitForTokenTexture(tkn, (token) => {
|
|
token.effects.removeChildren().forEach((c) => c.destroy());
|
|
token.effects.bg = token.effects.addChild(new PIXI.Graphics());
|
|
token.effects.overlay = null;
|
|
});
|
|
} else if (TVA_CONFIG.filterEffectIcons) {
|
|
waitForTokenTexture(tkn, (token) => {
|
|
token.drawEffects();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
if (userRequiresImageCache()) cacheImages();
|
|
|
|
// Register ALL Hooks
|
|
registerAllHooks();
|
|
|
|
// Startup ticker that will periodically call 'updateEmbeddedDocuments' with all the accrued updates since the last tick
|
|
startBatchUpdater();
|
|
|
|
registerHook('Search', 'renderArtSelect', () => {
|
|
ArtSelect.executing = false;
|
|
});
|
|
|
|
// Handle broadcasts
|
|
game.socket?.on(`module.token-variants`, (message) => {
|
|
if (message.handlerName === 'forgeSearchPaths' && message.type === 'UPDATE') {
|
|
// Workaround for forgeSearchPaths setting to be updated by non-GM clients
|
|
if (!game.user.isGM) return;
|
|
const isResponsibleGM = !game.users
|
|
.filter((user) => user.isGM && (user.active || user.isActive))
|
|
.some((other) => other.id < game.user.id);
|
|
if (!isResponsibleGM) return;
|
|
updateSettings({ forgeSearchPaths: message.args });
|
|
} else if (message.handlerName === 'drawOverlays' && message.type === 'UPDATE') {
|
|
if (message.args.all) {
|
|
if (canvas.scene.id !== message.args.sceneId) {
|
|
for (const tkn of canvas.tokens.placeables) {
|
|
drawOverlays(tkn);
|
|
}
|
|
}
|
|
} else if (message.args.actorId) {
|
|
const actor = game.actors.get(message.args.actorId);
|
|
if (actor) actor.getActiveTokens(true)?.forEach((tkn) => drawOverlays(tkn));
|
|
} else if (message.args.tokenId) {
|
|
const tkn = canvas.tokens.get(message.args.tokenId);
|
|
if (tkn) drawOverlays(tkn);
|
|
}
|
|
} else if (message.handlerName === 'effectMappings') {
|
|
if (!game.user.isGM) return;
|
|
const isResponsibleGM = !game.users
|
|
.filter((user) => user.isGM && (user.active || user.isActive))
|
|
.some((other) => other.id < game.user.id);
|
|
if (!isResponsibleGM) return;
|
|
const args = message.args;
|
|
const token = game.scenes.get(args.sceneId)?.tokens.get(args.tokenId);
|
|
if (token) updateWithEffectMapping(token, { added: args.added, removed: args.removed });
|
|
}
|
|
});
|
|
|
|
MODULE_INITIALIZED = true;
|
|
for (const cb of onInit) {
|
|
cb();
|
|
}
|
|
onInit = [];
|
|
}
|
|
|
|
/**
|
|
* Performs searches and displays the Art Select pop-up with the results
|
|
* @param {string} search The text to be used as the search criteria
|
|
* @param {object} [options={}] Options which customize the search
|
|
* @param {Function[]} [options.callback] Function to be called with the user selected image path
|
|
* @param {SEARCH_TYPE|string} [options.searchType] Controls filters applied to the search results
|
|
* @param {Token|Actor} [options.object] Token/Actor used when displaying Custom Token Config prompt
|
|
* @param {boolean} [options.force] If true will always override the current Art Select window if one exists instead of adding it to the queue
|
|
* @param {object} [options.searchOptions] Override search and filter settings
|
|
*/
|
|
export async function showArtSelect(
|
|
search,
|
|
{
|
|
callback = null,
|
|
searchType = SEARCH_TYPE.PORTRAIT_AND_TOKEN,
|
|
object = null,
|
|
force = false,
|
|
preventClose = false,
|
|
image1 = '',
|
|
image2 = '',
|
|
displayMode = ArtSelect.IMAGE_DISPLAY.NONE,
|
|
multipleSelection = false,
|
|
searchOptions = {},
|
|
allImages = null,
|
|
} = {}
|
|
) {
|
|
if (isCaching()) return;
|
|
|
|
const artSelects = Object.values(ui.windows).filter((app) => app instanceof ArtSelect);
|
|
if (ArtSelect.executing || (!force && artSelects.length !== 0)) {
|
|
addToArtSelectQueue(search, {
|
|
callback,
|
|
searchType,
|
|
object,
|
|
preventClose,
|
|
searchOptions,
|
|
allImages,
|
|
});
|
|
return;
|
|
}
|
|
|
|
ArtSelect.executing = true;
|
|
if (!allImages)
|
|
allImages = await doImageSearch(search, {
|
|
searchType: searchType,
|
|
searchOptions: searchOptions,
|
|
});
|
|
|
|
new ArtSelect(search, {
|
|
allImages: allImages,
|
|
searchType: searchType,
|
|
callback: callback,
|
|
object: object,
|
|
preventClose: preventClose,
|
|
image1: image1,
|
|
image2: image2,
|
|
displayMode: displayMode,
|
|
multipleSelection: multipleSelection,
|
|
searchOptions: searchOptions,
|
|
}).render(true);
|
|
}
|
|
|
|
// Initialize module
|
|
registerHook('main', 'ready', initialize, { once: true });
|
|
|
|
// Register API and Keybinds
|
|
registerHook('main', 'init', function () {
|
|
registerSettings();
|
|
registerAllWrappers();
|
|
|
|
registerKeybinds();
|
|
|
|
const api = {
|
|
cacheImages,
|
|
doImageSearch,
|
|
doRandomSearch,
|
|
getTokenEffects,
|
|
showArtSelect,
|
|
updateTokenImage,
|
|
exportSettingsToJSON,
|
|
assignUserSpecificImage,
|
|
assignUserSpecificImageToSelected,
|
|
unassignUserSpecificImage,
|
|
unassignUserSpecificImageFromSelected,
|
|
setOverlayVisibility,
|
|
toggleTemplateDialog,
|
|
toggleTemplate,
|
|
toggleTemplateOnSelected,
|
|
TVA_CONFIG,
|
|
};
|
|
|
|
Object.defineProperty(api, 'hooks', {
|
|
get() {
|
|
return deepClone(REGISTERED_HOOKS);
|
|
},
|
|
configurable: true,
|
|
});
|
|
|
|
Object.defineProperty(api, 'wrappers', {
|
|
get() {
|
|
return deepClone(REGISTERED_WRAPPERS);
|
|
},
|
|
configurable: true,
|
|
});
|
|
|
|
game.modules.get('token-variants').api = api;
|
|
});
|