Skip to content

Commit

Permalink
Merge pull request #70 from etiquettestartshere/1.1.16
Browse files Browse the repository at this point in the history
1.1.16
  • Loading branch information
etiquettestartshere authored May 13, 2024
2 parents 3242f4a + d86f151 commit af478eb
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 36 deletions.
2 changes: 1 addition & 1 deletion module.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "effectivetray",
"title": "Effective Tray",
"version": "1.1.15",
"version": "1.1.16",
"compatibility": {
"minimum": "11",
"verified": "11"
Expand Down
121 changes: 101 additions & 20 deletions scripts/damage-application.mjs
Original file line number Diff line number Diff line change
@@ -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 = `
<label class="roboto-upper">
<i class="fa-solid fa-heart-crack"></i>
<span>${game.i18n.localize("DND5E.Apply")}</span>
<i class="fa-solid fa-caret-down"></i>
</label>
<div class="collapsible-content">
<div class="wrapper">
<div class="target-source-control">
<button type="button" class="unbutton" data-mode="targeted" aria-pressed="false">
<i class="fa-solid fa-bullseye" inert></i> ${game.i18n.localize("DND5E.Tokens.Targeted")}
</button>
<button type="button" class="unbutton" data-mode="selected" aria-pressed="false">
<i class="fa-solid fa-expand" inert></i> ${game.i18n.localize("DND5E.Tokens.Selected")}
</button>
</div>
<ul class="targets unlist"></ul>
<button class="apply-damage" type="button" data-action="applyDamage">
<i class="fa-solid fa-reply-all fa-flip-horizontal" inert></i>
${game.i18n.localize("DND5E.Apply")}
</button>
</div>
</div>
`;
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);

Expand Down Expand Up @@ -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]")) {
Expand All @@ -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 }));
}
};
}
25 changes: 10 additions & 15 deletions scripts/effective-tray.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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");
}
};
});
};
};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
};

Expand Down

0 comments on commit af478eb

Please sign in to comment.