diff --git a/module.json b/module.json
index bf7f3e4..2c41f47 100644
--- a/module.json
+++ b/module.json
@@ -1,7 +1,7 @@
{
"id": "effectivetray",
"title": "Effective Tray",
- "version": "1.1.15",
+ "version": "1.1.16",
"compatibility": {
"minimum": "11",
"verified": "11"
diff --git a/scripts/damage-application.mjs b/scripts/damage-application.mjs
index 3e04147..d26d3d7 100644
--- a/scripts/damage-application.mjs
+++ b/scripts/damage-application.mjs
@@ -1,19 +1,101 @@
-import { socketID } from "./const.mjs";
+import { MODULE, socketID } from "./const.mjs";
- /* -------------------------------------------- */
- /* Damage Application Extension (from dnd5e) */
- /* Refer to dnd5e for full documentation */
- /* -------------------------------------------- */
+/* -------------------------------------------- */
+/* Functions */
+/* -------------------------------------------- */
+
+/**
+* Check targets for ownership when determining which target selection mode to use.
+* @param {Array} targets Array of objects with target data, including UUID.
+* @returns {boolean}
+*/
+async function ownershipCheck(targets) {
+ for (const target of targets) {
+ const token = await fromUuid(target.uuid);
+ if (token?.isOwner) return true;
+ else continue;
+ };
+ return false;
+};
+
+
+/* -------------------------------------------- */
+/* Damage Application Extension (from dnd5e) */
+/* Refer to dnd5e for full documentation */
+/* -------------------------------------------- */
const MULTIPLIERS = [[-1, "-1"], [0, "0"], [.25, "¼"], [.5, "½"], [1, "1"], [2, "2"]];
export default class EffectiveDAE extends dnd5e.applications.components.DamageApplicationElement {
+ /**
+ * Determine which target selection mode to use based on damageTarget setting state.
+ */
+ /** @override */
+ async connectedCallback() {
+ // Fetch the associated chat message
+ const messageId = this.closest("[data-message-id]")?.dataset.messageId;
+ this.chatMessage = game.messages.get(messageId);
+ if (!this.chatMessage) return;
+
+ // Build the frame HTML only once (technically, we've built it twice by this point :weary:)
+ if (!this.targetList) {
+ const div = document.createElement("div");
+ div.classList.add("card-tray", "damage-tray", "collapsible", "collapsed");
+ div.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ this.replaceChildren(div);
+ this.applyButton = div.querySelector(".apply-damage");
+ this.applyButton.addEventListener("click", this._onApplyDamage.bind(this));
+ this.targetList = div.querySelector(".targets");
+ this.targetSourceControl = this.querySelector(".target-source-control");
+ this.targetSourceControl.querySelectorAll("button").forEach(b =>
+ b.addEventListener("click", this._onChangeTargetMode.bind(this))
+ );
+ if (!this.chatMessage.getFlag("dnd5e", "targets")?.length) this.targetSourceControl.hidden = true;
+
+ // If damageTarget setting is disabled, check if users own any targeted tokens, otherwise use "selected" mode
+ if (!game.settings.get(MODULE, "damageTarget")) {
+ const targets = this.chatMessage.getFlag("dnd5e", "targets");
+ if (!await ownershipCheck(targets)) this.targetSourceControl.hidden = true;
+ };
+
+ div.querySelector(".collapsible-content").addEventListener("click", event => {
+ event.stopImmediatePropagation();
+ });
+ }
+
+ this.targetingMode = this.targetSourceControl.hidden ? "selected" : "targeted";
+ }
+
/**
* Create a list entry for a single target.
* @param {string} uuid UUID of the token represented by this entry.
* Extends this method to remove checking for token owner.
*/
+ /** @override */
buildTargetListEntry(uuid) {
const token = fromUuidSync(uuid);
@@ -75,11 +157,12 @@ export default class EffectiveDAE extends dnd5e.applications.components.DamageAp
}
/**
- * Handle clicking the apply damage button.
- * @param {PointerEvent} event Triggering click event.
- * Extends this method to emit a request for the active GM client to damage a non-owned actor.
- * Special handling is required for the Set `this.damages.properties`.
- */
+ * Handle clicking the apply damage button.
+ * @param {PointerEvent} event Triggering click event.
+ * Extends this method to emit a request for the active GM client to damage a non-owned actor.
+ * Special handling is required for the Set `this.damages.properties`.
+ */
+ /** @override */
async _onApplyDamage(event) {
event.preventDefault();
for (const target of this.targetList.querySelectorAll("[data-target-uuid]")) {
@@ -90,19 +173,17 @@ export default class EffectiveDAE extends dnd5e.applications.components.DamageAp
await token?.applyDamage(this.damages, options);
}
else {
- const damage = []
- for (const d of this.damages) {
- const damageObject = {
- properties: Array.from(d.properties),
- type: d.type,
- value: d.value
- };
- damage.push(damageObject);
- };
+ const damage = [];
+
+ // Convert damage properties to an Array for socket emission
+ this.damages.forEach(d => {
+ foundry.utils.mergeObject(d, { properties: Array.from(d.properties) });
+ damage.push(d);
+ });
if (!game.users.activeGM) return ui.notifications.warn(game.i18n.localize("EFFECTIVETRAY.NOTIFICATION.NoActiveGMDamage"));
await game.socket.emit(socketID, { type: "damage", data: { id, options, damage } });
};
}
this.querySelector(".collapsible").dispatchEvent(new PointerEvent("click", { bubbles: true, cancelable: true }));
}
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/scripts/effective-tray.mjs b/scripts/effective-tray.mjs
index 8829e6f..fa21687 100644
--- a/scripts/effective-tray.mjs
+++ b/scripts/effective-tray.mjs
@@ -101,7 +101,7 @@ 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 function() {
+ 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();
@@ -177,12 +177,12 @@ export class effectiveTray {
if (game.settings.get(MODULE, "dontCloseOnPress")) {
const buttons = tray.querySelectorAll("button");
for (const button of buttons) {
- button.addEventListener('click', async function() {
+ button.addEventListener('click', async() => {
if (!tray.querySelector(".et-uncollapsed")) {
await tray.classList.add("et-uncollapsed");
await new Promise(r => setTimeout(r, 108));
await tray.classList.remove("collapsed");
- }
+ };
});
};
};
@@ -253,9 +253,7 @@ export class effectiveDamage {
if (message.flags?.dnd5e?.roll?.type === "damage") {
if (!game.user.isGM) {
if (message.whisper.length && !message.whisper.includes(game.user.id)) return;
- const damageApplication = game.settings.get(MODULE, "damageTarget") ?
- document.createElement("effective-damage-application") :
- document.createElement("damage-application");
+ const damageApplication = document.createElement("effective-damage-application");
damageApplication.classList.add("dnd5e2");
damageApplication.damages = dnd5e.dice.aggregateDamageRolls(message.rolls, { respectProperties: true }).map(roll => ({
value: roll.total,
@@ -373,20 +371,17 @@ async function _effectSocket(request) {
};
// Make the GM client apply damage to the requested targets
-// Convert damage properties back from an Array into a Set
async function _damageSocket(request) {
if (game.user !== game.users.activeGM) return;
const id = request.data.id;
const options = request.data.options;
const damage = [];
- for (const d of request.data.damage) {
- const damageObject = {
- properties: new Set(d.properties),
- type: d.type,
- value: d.value
- };
- damage.push(damageObject);
- };
+
+ // Convert damage properties back into a Set for damage application
+ request.data.damage.forEach(d => {
+ foundry.utils.mergeObject(d, { properties: new Set(d.properties) });
+ damage.push(d);
+ });
return await _applyTargetDamage(id, options, damage);
};