diff --git a/README.md b/README.md index c8344c2..a357ebc 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Allows users to use the damage tray for selected tokens that they own, and for t - **Legacy Targeting for Effects**: Apply effects to target with right click, rather than the target source control (off by default). - **Damage Target**: Allow users to damage targets (that they don't own) with the damage tray (off by default). - **Delete Instead of Refresh**: Attempting to transfer an effect to an actor that has it already will delete it rather than refreshing its duration (off by default). -- **Flag Effects with Level**: Adds a flag to active effects applied via the tray on spell messages indicating the level at which the spell was cast, with the scope `ActiveEffect#flags.dnd5e.spellLevel`. This will be provided by the system in 3.2, so it will be on by default (this setting will be removed in 3.2 and enabled for all installs). - **Filtering** based on actor type, permissions, and token disposition. This prevents users from seeing and interacting with effects of certain origins, depending on GM preference (no filtering is performed by default). - **Use Default Trays**: Adds settings (off by default) to use the default effects and damage trays. Only the features *below* this setting will function if a given tray is in its default mode. - **Expand Effects Tray**: The effect tray on chat messages starts in its expanded position when the message is created (on by default). @@ -20,6 +19,90 @@ Allows users to use the damage tray for selected tokens that they own, and for t - **Scroll on Expand**: Scroll chat to bottom when expanding a tray that is at the bottom (experimental, on by default). - **Remove 'Apply Effect to Actor'**: On the time of creation (i.e. drag & drop), remove 'Apply Effect to Actor' from effects on items that have a duration to allow for normal use of the timer (on by default). - **Multiple Effects with Concentration**: Allow multiple effects to be applied from spells with concentration (off by default). + +## API +Now includes three helper functions exposed in the global scope under `effectiv`, `effectiv.applyEffect`, `effectiv.applyDamage` and `effective.partitionTargets`. These functions *have not been heavily tested* and are included now in the hopes that, if someone wants to use them, they will also test them for issues. + +`applyEffect`, a helper function to allow users to apply effects. It allows users to apply effects via macro (or other module), and can take a variety of types of data when doing so, allowing effect data to be passed as the full ActiveEffect document, an effect Uuid, or an object (note that passing it as an object will not interact with refreshing duration or deleting effects of same origin, as determined by module setting, because creating an effect from an object will have its own unique origin). Similarly, this function allows target data to be passed as an array of Uuids, a single Uuid, an array of Tokens, or a Set, as `game.user.targets`. + +This helper also allows the use of the other things the effects tray does, primarily flagging effects with spellLevel, or any other arbitrary flags (via effectData), and making an effect dependent on a concentration effect (so it will be deleted when the concentration effect is). If concentration is used, because it passed as an ID, you must also pass the Uuid or Actor document of the actor the concentration effect is on. + +```js +/** + * Helper function to allow for macros or other applications to apply effects to owned and unowned targets. + * @param {string|object|ActiveEffect5e} effect The effect to apply. + * @param {Set|Token5e[]|string[]|string} targets Targeted tokens. + * @param {object} [options] + * @param {object} [options.effectData] A generic data object, which typically handles the level the originating spell was cast at, + * if it originated from a spell, if any. Use flags like { "flags.dnd5e.spellLevel": 1 }. + * @param {string} [options.concentration] The ID (not Uuid) of the concentration effect this effect is dependent on, if any. + * @param {string|Actor5e} [options.caster] The Uuid or Actor5e document of the actor that cast the spell that requires concentration, if any. + */ +async function applyEffect( + effect, + targets, + { effectData: null, concentration: null, caster: null } = {} +) +``` +```js +/* in use...*/ +effectiv.applyEffect( + effect, + targets, + { effectData: null, concentration: null, caster: null } = {} +) +``` + +`applyDamage`, helper function to allow users to apply damage. This function has been tested not at all and is included as a courtesy. Personally I find the way damage information must be structured to respect resistances, etc is too much of a mess to test this even a single time, but if someone really wants to do this over a socket but didn't write their own socket handler for it...here you go. Full, extensive documentation of the array that must be created is in scripts/api.mjs, copied directly from `dnd5e`, with the exception that socket transmission requires the sets to be arrays. Unlike `applyEffect`, this applies no damage via the requesting client, and so is basically only meant for damaging unowned targets. Users wishing to apply damage to owned targets should simply use the system's `Actor#applyDamage`. +```js +/** + * Helper function to allow for macros or other applications to apply damage via socket request. + * @param {array} damage Array of damage objects; see above. + * @param {array} opts Object of options (which may inlude arrays); see above. + * @param {string} id Uuid of the target. + */ +async function applyDamage(damage = [], opts = {}, id) +``` +```js +/* in use...*/ +effectiv.applyDamage(damage = [], opts = {}, id) +``` + +`partitionTargets`, a function similar to foundry's `Array#partition` but specifically designed to handle `game.user.targets`, a set, or an array of tokens. It sorts them into two arrays, the first array containing tokens that the user owns, and the second array containing those token's `document.uuid`s. This can be useful for determining what information needs to be sent over sockets. I have no idea why anyone would use this function, but here it is. +```js +/** + * Sort tokens into owned and unowned categories. + * @param {Set|array} targets The set or array of tokens to be sorted. + * @returns {array} An Array of length two, the elements of which are the partitioned pieces of the original. + */ +function partitionTargets(targets) +``` +```js +/* in use...*/ +effectiv.partitionTargets(targets) +``` + +## Hooks +Now includes two hooks, `effectiv.preApplyEffect` and `effectiv.applyEffect`. The former allows the data to be modified and explicitly returning `false` will prevent the effect from being applied. The later passes the same (modified in the case of effectData), information in its final state upon application. + +These hooks have not been extensively tested. +```js +/** + * Hook called before the effect is completed and applied. + * @param {Actor5e} actor The actor to create the effect on. + * @param {ActiveEffect5e} effect The effect to create. + * @param {object} effectData A generic data object that contains spellLevel in a `dnd5e` scoped flag, and whatever else. + * @param {ActiveEffect5e} concentration The concentration effect on which `effect` is dependent, if it requires concentration. + */ +Hooks.call("effectiv.preApplyEffect", actor, effect, { effectData, concentration }); +``` +```js +/** + * Hook called before the effect is completed and applied. Same as abvove except for effectData + * @param {object} effectData The packaged effect immediately before application. + */ +Hooks.callAll("effectiv.applyEffect", actor, effect, { effectData, concentration }); +``` ___ ###### **Technical Details** diff --git a/lang/en.json b/lang/en.json index 36a0cc2..4ddafd4 100644 --- a/lang/en.json +++ b/lang/en.json @@ -15,8 +15,6 @@ "EFFECTIVETRAY.ExpandDamageSettingHint": "The damage tray on chat messages starts in its expanded position when the message is created.", "EFFECTIVETRAY.ExpandEffectSettingName": "Expand Effects Tray", "EFFECTIVETRAY.ExpandEffectSettingHint": "The effect tray on chat messages starts in its expanded position when the message is created.", - "EFFECTIVETRAY.FlagLevelSettingName": "Flag Effects with Level", - "EFFECTIVETRAY.FlagLevelSettingHint": "Adds a flag to active effects applied via the tray on spell messages indicating the level at which the spell was cast.", "EFFECTIVETRAY.FilterDispositionSettingName": "Filter by Disposition", "EFFECTIVETRAY.FilterDispositionSettingHint": "Hide effects from users if they originate from tokens with the selected (or worse) disposition.", "EFFECTIVETRAY.FilterPermissionSettingName": "Filter by Permission", diff --git a/module.json b/module.json index 981c3b6..297b058 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "effectivetray", "title": "Effective Tray", - "version": "1.1.17", + "version": "1.1.18", "compatibility": { "minimum": "11", "verified": "11" diff --git a/scripts/api.mjs b/scripts/api.mjs new file mode 100644 index 0000000..e2e8257 --- /dev/null +++ b/scripts/api.mjs @@ -0,0 +1,154 @@ +import { MODULE, socketID } from "./const.mjs"; +import { _applyEffects, partitionTargets} from "./effective-tray.mjs"; + +export class API { + + static init() { + globalThis.effectiv = { + applyEffect: API.applyEffect, + applyDamage: API.applyDamage, + partitionTargets: partitionTargets + }; + }; + + /* -------------------------------------------- */ + /* Effect Application Helper */ + /* -------------------------------------------- */ + + /** + * Helper function to allow for macros or other applications to apply effects to owned and unowned targets. + * @param {string|object|ActiveEffect5e} effect The effect to apply. + * @param {Set|Token5e[]|string[]|string} targets Targeted tokens. + * @param {object} [options] + * @param {object} [options.effectData] A generic data object, which typically handles the level the originating spell was cast at, + * if it originated from a spell, if any. Use flags like { "flags.dnd5e.spellLevel": 1 }. + * @param {string} [options.concentration] The ID (not Uuid) of the concentration effect this effect is dependent on, if any. + * @param {string|Actor5e} [options.caster] The Uuid or Actor5e document of the actor that cast the spell that requires concentration, if any. + */ + static async applyEffect(effect, targets, {effectData = null, concentration = null, caster = null} = {}) { + + // Effect handling + let toApply; + const effectType = foundry.utils.getType(effect); + if (effect instanceof ActiveEffect) { + toApply = effect.uuid; + } + else if (effectType === "string") { + toApply = effect; + effect = await fromUuid(effect); + } + else if (effectType === "Object") { + toApply = effect; + } + + // Target handling + let owned; + let toTarget; + if (targets instanceof Set) { + [owned, toTarget] = partitionTargets(Array.from(targets)); + } + else if (foundry.utils.getType(targets) === "string") { + const t = await fromUuid(targets); + t.document.isOwner ? owned = t : toTarget = Array.from(t); + } + else if (targets.at(0) instanceof Token) { + partitionTargets(targets); + } + else { + owned = []; + toTarget = []; + for (const target of targets) { + const t = await fromUuid(target); + if (t.isOwned) owned.push(t); + else toTarget.push(t); + }; + } + + // Caster handling + let spellCaster; + if (caster instanceof Actor) spellCaster = caster?.uuid; + else if (foundry.utils.getType(caster) === "string") spellCaster = caster; + + + // Apply what effects you can apply yourself + const actors = new Set(); + for (const token of owned) if (token.actor) actors.add(token.actor); + for (const actor of actors) await _applyEffects(actor, effect, {effectData, concentration}); + + // Ask the GM client to apply the rest + if (!game.users.activeGM) return ui.notifications.warn(game.i18n.localize("EFFECTIVETRAY.NOTIFICATION.NoActiveGMEffect")); + + await game.socket.emit(socketID, { + type: "effect", + data: { + origin: toApply, + targets: toTarget, + effectData: effectData, + con: concentration, + caster: spellCaster + } + }); + }; + + /* -------------------------------------------- */ + /* Damage Application Helper */ + /* */ + /* below is documentation for the system's */ + /* applyDamage() function which this calls, */ + /* with the change that all Sets are Arrays */ + /* -------------------------------------------- */ + + /** + * Description of a source of damage. + * + * @typedef {object} DamageDescription + * @property {number} value Amount of damage. + * @property {string} type Type of damage. + * @property {Array} properties Physical properties that affect damage application. + * @property {object} [active] + * @property {number} [active.multiplier] Final calculated multiplier. + * @property {boolean} [active.modifications] Did modification affect this description? + * @property {boolean} [active.resistance] Did resistance affect this description? + * @property {boolean} [active.vulnerability] Did vulnerability affect this description? + * @property {boolean} [active.immunity] Did immunity affect this description? + */ + + /** + * Options for damage application. + * + * @typedef {object} DamageApplicationOptions + * @property {boolean|Array} [downgrade] Should this actor's resistances and immunities be downgraded by one + * step? A Array of damage types to be downgraded or `true` to downgrade + * all damage types. + * @property {number} [multiplier=1] Amount by which to multiply all damage. + * @property {object|boolean} [ignore] Array to `true` to ignore all damage modifiers. If Array to an object, then + * values can either be `true` to indicate that the all modifications of + * that type should be ignored, or a Array of specific damage types for which + * it should be ignored. + * @property {boolean|Array} [ignore.immunity] Should this actor's damage immunity be ignored? + * @property {boolean|Array} [ignore.resistance] Should this actor's damage resistance be ignored? + * @property {boolean|Array} [ignore.vulnerability] Should this actor's damage vulnerability be ignored? + * @property {boolean|Array} [ignore.modification] Should this actor's damage modification be ignored? + * @property {boolean} [invertHealing=true] Automatically invert healing types to it heals, rather than damages. + * @property {"damage"|"healing"} [only] Apply only damage or healing parts. Untyped rolls will always be applied. + */ + + /** + * Apply a certain amount of damage or healing to the health pool for Actor + * @param {DamageDescription[]|number} damages Damages to apply. + * @param {DamageApplicationOptions} [options={}] Damage application options. + * @returns {Promise} A Promise which resolves once the damage has been applied. + */ + + /** + * Helper function to allow for macros or other applications to apply damage via socket request. + * @param {array} damage Array of damage objects; see above. + * @param {array} opts Object of options (which may inlude arrays); see above. + * @param {string} id Uuid of the target. + */ + static async applyDamage(damage = [], opts = {}, id) { + if (!game.users.activeGM) return ui.notifications.warn(game.i18n.localize("EFFECTIVETRAY.NOTIFICATION.NoActiveGMDamage")); + await game.socket.emit(socketID, { type: "damage", data: { id, opts, damage } }); + }; + +}; \ No newline at end of file diff --git a/scripts/effective-tray.mjs b/scripts/effective-tray.mjs index 427e8c5..61d6f22 100644 --- a/scripts/effective-tray.mjs +++ b/scripts/effective-tray.mjs @@ -45,7 +45,7 @@ export class effectiveTray { /** * Make the effects tray effective * @param {ChatMessage5e} message The message on which the tray resides. - * @param {HTMLElement} html HTML contents of the message. + * @param {HTMLElement} html HTML contents of the message. * Methods lacking documentation below share these parameters. */ static async _effectTray(message, html) { @@ -81,10 +81,13 @@ export class effectiveTray { "EFFECTIVETRAY.TOOLTIP.EffectsApplyTokensLegacy" : "EFFECTIVETRAY.TOOLTIP.EffectsApplyTokens" ) : "DND5E.EffectsApplyTokens"; - const lvl = message.flags?.dnd5e?.use?.spellLevel; + let spellLevel; + if (!message.flags?.dnd5e?.use?.spellLevel) spellLevel = 0; + else spellLevel = parseInt(message.flags?.dnd5e?.use?.spellLevel) || null; + const effectData = { "flags.dnd5e.spellLevel": spellLevel }; + const concentration = actor.effects.get(message.getFlag("dnd5e", "use.concentrationId")); + const caster = actor.uuid; for (const effect of effects) { - const concentration = actor.effects.get(message.getFlag("dnd5e", "use.concentrationId")); - const caster = actor.uuid; const label = effect.duration.duration ? effect.duration.label : ""; const contents = `
  • @@ -101,29 +104,26 @@ export class effectiveTray { tray.querySelector('ul.effects.unlist').insertAdjacentHTML("beforeend", contents); // Handle click events - tray.querySelector(`li[data-uuid="${uuid}.ActiveEffect.${effect.id}"]`)?.querySelector("button").addEventListener('click', async() => { + tray.querySelector(`li[data-uuid="${uuid}.ActiveEffect.${effect.id}"]`)?.querySelector("button").addEventListener('click', async () => { const mode = tray.querySelector(`[aria-pressed="true"]`)?.dataset?.mode; if (!mode || mode === "selected") { const actors = new Set(); for (const token of canvas.tokens.controlled) if (token.actor) actors.add(token.actor); + _checkTray(tray); for (const actor of actors) { - await _applyEffects(actor, effect, lvl, concentration); - if (!game.settings.get(MODULE, "dontCloseOnPress")) { - tray.classList.add("collapsed"); - tray.classList.remove("et-uncollapsed"); - }; + await _applyEffects(actor, effect, { effectData, concentration }); }; } else { - _effectApplicationHandler(tray, effect, lvl, concentration, caster); + _effectApplicationHandler(tray, effect, { effectData, concentration, caster }); }; }); // Handle legacy targeting mode if (game.settings.get(MODULE, "allowTarget") && game.settings.get(MODULE, "contextTarget")) { - tray.querySelector(`li[data-uuid="${uuid}.ActiveEffect.${effect.id}"]`)?.querySelector("button").addEventListener('contextmenu', async function(event) { + tray.querySelector(`li[data-uuid="${uuid}.ActiveEffect.${effect.id}"]`)?.querySelector("button").addEventListener('contextmenu', async function (event) { event.stopPropagation(); event.preventDefault(); - _effectApplicationHandler(tray, effect, lvl, concentration, caster); + _effectApplicationHandler(tray, effect, { effectData, concentration, caster }); }); }; }; @@ -141,7 +141,7 @@ export class effectiveTray { event.preventDefault(); if (html.querySelector(".et-uncollapsed")) { tray.classList.remove("et-uncollapsed"); - }; + }; if (!html.querySelector(".effects-tray.collapsed")) { tray.classList.add("collapsed"); } else if (html.querySelector(".effects-tray.collapsed")) tray.classList.remove("collapsed"); @@ -177,7 +177,7 @@ export class effectiveTray { if (game.settings.get(MODULE, "dontCloseOnPress")) { const buttons = tray.querySelectorAll("button"); for (const button of buttons) { - button.addEventListener('click', async() => { + button.addEventListener('click', async () => { if (!tray.querySelector(".et-uncollapsed")) { await tray.classList.add("et-uncollapsed"); await new Promise(r => setTimeout(r, 108)); @@ -211,7 +211,7 @@ export class effectiveTray { const tray = html.querySelector('.effects-tray'); if (!tray) return; const mid = message.id; - await tray.classList.add("et-uncollapsed"); + if (game.settings.get(MODULE, "dontCloseOnPress")) tray.classList.add("et-uncollapsed"); await new Promise(r => setTimeout(r, 108)); await tray.classList.remove("collapsed"); if (game.settings.get(MODULE, "scrollOnExpand")) _scroll(mid); @@ -246,7 +246,7 @@ export class effectiveDamage { /** * Make the damage tray effective * @param {ChatMessage5e} message The message on which the tray resides. - * @param {HTMLElement} html HTML contents of the message. + * @param {HTMLElement} html HTML contents of the message. * Methods lacking documentation below share these parameters. */ static _damageTray(message, html) { @@ -355,10 +355,11 @@ export class effectiveSocket { async function _effectSocket(request) { if (game.user !== game.users.activeGM) return; const targets = request.data.targets; - const effect = await fromUuid(request.data.origin); + const origin = request.data.origin; + const effect = foundry.utils.getType(origin) === "string" ? await fromUuid(origin) : origin; const c = await fromUuid(request.data.caster); const concentration = c?.effects?.get(request.data.con); - const lvl = request.data.lvl; + const effectData = request.data.effectData; const actors = new Set(); for (const target of targets) { const token = await fromUuid(target); @@ -366,7 +367,7 @@ async function _effectSocket(request) { if (target) actors.add(targetActor); }; for (const actor of actors) { - await _applyEffects(actor, effect, lvl, concentration); + await _applyEffects(actor, effect, { effectData, concentration }); }; }; @@ -396,87 +397,90 @@ async function _damageSocket(request) { /** * Handle applying effects to targets: handle it if you can handle it, else make a request via socket - * @param {HTMLElement} tray HTML element that composes the collapsible tray. - * @param {ActiveEffect5e} effect The effect to create. - * @param {number} lvl The spellLevel of the spell the effect is on, if it is on a spell. + * @param {HTMLElement} tray HTML element that composes the collapsible tray. + * @param {ActiveEffect5e} effect The effect to create. + * @param {object} effectData A generic data object that contains spellLevel in a `dnd5e` scoped flag, and whatever else. * @param {ActiveEffect5e} concentration The concentration effect on which `effect` is dependent, if it requires concentration. - * @param {string} caster The Uuid of the actor which originally cast the spell requiring concentration. + * @param {string} caster The Uuid of the actor which originally cast the spell requiring concentration. */ -async function _effectApplicationHandler(tray, effect, lvl, concentration, caster) { +async function _effectApplicationHandler(tray, effect, { effectData, concentration, caster }) { if (!game.user.targets.size) return ui.notifications.info(game.i18n.localize("EFFECTIVETRAY.NOTIFICATION.NoTarget")); - const targets = Array.from(game.user.targets).map(i => i.document.uuid) if (game.user.isGM) { const actors = new Set(); for (const token of game.user.targets) if (token.actor) actors.add(token.actor); + _checkTray(tray); for (const actor of actors) { - await _applyEffects(actor, effect, lvl, concentration); - if (!game.settings.get(MODULE, "dontCloseOnPress")) { - tray.classList.add("collapsed"); - tray.classList.remove("et-uncollapsed"); - }; + await _applyEffects(actor, effect, { effectData, concentration }); }; } else { + const [owned, targets] = partitionTargets(game.user.targets); + const actors = new Set(); + for (const token of owned) if (token.actor) actors.add(token.actor); + _checkTray(tray); + for (const actor of actors) { + await _applyEffects(actor, effect, { effectData, concentration }); + }; + if (!targets.length) return; if (!game.users.activeGM) return ui.notifications.warn(game.i18n.localize("EFFECTIVETRAY.NOTIFICATION.NoActiveGMEffect")); const origin = effect.uuid; const con = concentration?.id; - await game.socket.emit(socketID, { type: "effect", data: { origin, targets, lvl, con, caster } }); - if (!game.settings.get(MODULE, "dontCloseOnPress")) { - tray.classList.add("collapsed"); - tray.classList.remove("et-uncollapsed"); - }; + await game.socket.emit(socketID, { type: "effect", data: { origin, targets, effectData, con, caster } }); }; }; /** * Apply effect, or refresh its duration (and level) if it exists - * @param {Actor5e} actor The actor to create the effect on. - * @param {ActiveEffect5e} effect The effect to create. - * @param {number} lvl The spellLevel of the spell the effect is on, if it is on a spell. + * @param {Actor5e} actor The actor to create the effect on. + * @param {ActiveEffect5e} effect The effect to create. + * @param {object} effectData A generic data object that contains spellLevel in a `dnd5e` scoped flag, and whatever else. * @param {ActiveEffect5e} concentration The concentration effect on which `effect` is dependent, if it requires concentration. */ -async function _applyEffects(actor, effect, lvl, concentration) { +export async function _applyEffects(actor, effect, { effectData, concentration }) { + + // Call the pre effect hook; returning `false` will terminate the function + const preCallback = Hooks.call("effectiv.preApplyEffect", actor, effect, { effectData, concentration }); + if (!preCallback) return; + + // Enable an existing effect on the target if it originated from this effect const origin = game.settings.get(MODULE, "multipleConcentrationEffects") ? effect : concentration ?? effect; const existingEffect = game.settings.get(MODULE, "multipleConcentrationEffects") ? actor.effects.find(e => e.origin === effect.uuid) : actor.effects.find(e => e.origin === origin.uuid); - let flags; - if (game.settings.get(MODULE, "flagLevel") && effect?.parent?.type === "spell") { - flags = foundry.utils.deepClone(effect.flags); - foundry.utils.mergeObject(flags, { - dnd5e: { - spellLevel: lvl - } - }); - } else flags = effect.flags; + if (existingEffect) { if (!game.settings.get(MODULE, "deleteInstead")) { - return existingEffect.update({ + return existingEffect.update(foundry.utils.mergeObject({ ...effect.constructor.getInitialDuration(), - disabled: false, - flags: flags - }); + disabled: false + }, effectData)); + + // Or delete it instead } else existingEffect.delete(); - if ( !game.user.isGM && concentration && !concentration.actor?.isOwner ) { - throw new Error(game.i18n.localize("DND5E.EffectApplyWarningConcentration")); - } } else { - const effectData = foundry.utils.mergeObject(effect.toObject(), { + + // Otherwise, create a new effect on the target + effect instanceof ActiveEffect ? effect = effect.toObject() : effect; + effectData = foundry.utils.mergeObject({ + ...effect, disabled: false, transfer: false, - origin: origin.uuid, - flags: flags - }); + origin: origin.uuid + }, effectData); const applied = await ActiveEffect.implementation.create(effectData, { parent: actor }); if (concentration) await concentration.addDependent(applied); + + // Call the effect hook + Hooks.callAll("effectiv.applyEffect", actor, effect, { effectData, concentration }); + return applied; }; }; /** * Apply damage - * @param {string} id The id of the actor to apply damage to. + * @param {string} id The id of the actor to apply damage to. * @param {object} options The options provided by the tray, primarily the multiplier. - * @param {array} damage An array of objects with the damage type and value that also contain Sets with damage properties. + * @param {array} damage An array of objects with the damage type and value that also contain Sets with damage properties. */ async function _applyTargetDamage(id, options, damage) { const actor = fromUuidSync(id); @@ -497,4 +501,29 @@ async function _scroll(mid) { await new Promise(r => setTimeout(r, 256)); window.ui.sidebar.popouts.chat.scrollBottom(); }; +}; + +/** + * Sort tokens into owned and unowned categories. + * @param {Set|array} targets The set or array of tokens to be sorted. + * @returns {array} An Array of length two whose elements are the partitioned pieces of the original + */ +export function partitionTargets(targets) { + const result = targets.reduce((acc, t) => { + if (t.isOwner) acc[0].push(t); + else acc[1].push(t.document.uuid); + return acc; + }, [[], []]); + return result; +}; + +/** + * Sort tokens into owned and unowned categories. + * @param {HTMLElement} tray The tray to be collapsed or not collapsed + */ +function _checkTray(tray) { + if (!game.settings.get(MODULE, "dontCloseOnPress")) { + tray.classList.add("collapsed"); + tray.classList.remove("et-uncollapsed"); + }; }; \ No newline at end of file diff --git a/scripts/settings.mjs b/scripts/settings.mjs index 2612de6..249b24f 100644 --- a/scripts/settings.mjs +++ b/scripts/settings.mjs @@ -47,17 +47,6 @@ export class moduleSettings { config: true, type: Boolean, default: false, - requiresReload: true, - onChange: false - }); - - game.settings.register(MODULE, "flagLevel", { - name: "EFFECTIVETRAY.FlagLevelSettingName", - hint: "EFFECTIVETRAY.FlagLevelSettingHint", - scope: "world", - config: true, - type: Boolean, - default: true, requiresReload: false, onChange: false }); diff --git a/scripts/setup.mjs b/scripts/setup.mjs index 2ef63a3..84afdbe 100644 --- a/scripts/setup.mjs +++ b/scripts/setup.mjs @@ -1,5 +1,6 @@ import { moduleSettings } from "./settings.mjs"; import { effectiveSocket, effectiveTray, effectiveDamage } from "./effective-tray.mjs"; +import { API } from "./api.mjs"; import EffectiveDAE from "./damage-application.mjs"; window.customElements.define("effective-damage-application", EffectiveDAE); @@ -8,5 +9,6 @@ Hooks.once("init", effectiveSocket.init); Hooks.once("init", moduleSettings.init); Hooks.once("init", effectiveTray.init); Hooks.once("init", effectiveDamage.init); +Hooks.once("init", API.init); export { EffectiveDAE }; \ No newline at end of file