This commit is contained in:
WBHarry 2025-11-25 23:16:30 +01:00
parent 4954e41b02
commit aec5c06da7
14 changed files with 156 additions and 46 deletions

View file

@ -4,6 +4,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
static DEFAULT_OPTIONS = {
classes: ['daggerheart'],
actions: {
effect: { handler: DHTokenHUD.#onToggleEffect, buttons: [0, 2] },
combat: DHTokenHUD.#onToggleCombat,
togglePartyTokens: DHTokenHUD.#togglePartyTokens
}
@ -21,7 +22,6 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.partyOnCanvas =
this.actor.type === 'party' &&
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
@ -31,6 +31,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
? false
: context.canToggleCombat;
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
const effect = context.statusEffects[key];
if (effect.systemEffect) {
@ -193,16 +194,18 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
}
// Update the status of effects which are active for the token actor
const activeEffects = this.actor?.effects || [];
const activeEffects = this.actor?.getActiveEffects() || [];
for (const effect of activeEffects) {
for (const statusId of effect.statuses) {
const status = choices[statusId];
status.instances = 1 + (status.instances ?? 0);
status.locked = status.locked || effect.condition || status.instances > 1;
if (!status) continue;
if (status._id) {
if (status._id !== effect.id) continue;
}
status.isActive = true;
if (effect.getFlag('core', 'overlay')) status.isOverlay = true;
if (effect.getFlag?.('core', 'overlay')) status.isOverlay = true;
}
}
@ -212,4 +215,17 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
}
return choices;
}
static async #onToggleEffect(event, target) {
if (!this.actor) {
ui.notifications.warn('HUD.WarningEffectNoActor', { localize: true });
return;
}
const statusId = target.dataset.statusId;
await this.actor.toggleStatusEffect(statusId, {
active: !target.classList.contains('active'),
overlay: event.button === 2
});
}
}

View file

@ -1,3 +1,5 @@
import { RefreshType } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
/**
@ -48,7 +50,7 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
super._attachPartListeners(partId, htmlElement, options);
if (this.element) {
this.element.querySelectorAll('.effect-container').forEach(element => {
this.element.querySelectorAll('.effect-container a').forEach(element => {
element.addEventListener('contextmenu', this.removeEffect.bind(this));
});
}
@ -70,7 +72,7 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
? game.user.character
: null
: canvas.tokens.controlled[0].actor;
return actor?.effects ? Array.from(actor.effects) : [];
return actor?.getActiveEffects() ?? [];
};
toggleHidden(token, focused) {
@ -92,6 +94,7 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
setupHooks() {
Hooks.on('controlToken', this.toggleHidden.bind(this));
Hooks.on(RefreshType.EffectsDisplay, this.toggleHidden.bind(this));
}
async close(options) {
@ -99,6 +102,7 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
if (options.closeKey) return;
Hooks.off('controlToken', this.toggleHidden);
Hooks.off(RefreshType.EffectsDisplay, this.toggleHidden);
return super.close(options);
}

View file

@ -10,29 +10,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
this.effects.overlay = null;
// Categorize effects
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
const activeEffects = (this.actor ? this.actor.effects.filter(x => !x.disabled) : []).reduce((acc, effect) => {
acc.push(effect);
const currentStatusActiveEffects = acc.filter(
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
);
for (var status of effect.statuses) {
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
const statusData = statusMap.get(status);
if (statusData) {
acc.push({
name: game.i18n.localize(statusData.name),
statuses: [status],
img: statusData.icon ?? statusData.img,
tint: effect.tint
});
}
}
}
return acc;
}, []);
const activeEffects = this.actor?.getActiveEffects() ?? [];
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
// Draw effects

View file

@ -1,4 +1,5 @@
import { itemAbleRollParse } from '../helpers/utils.mjs';
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
export default class DhActiveEffect extends foundry.documents.ActiveEffect {
/* -------------------------------------------- */
@ -85,6 +86,20 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
await super._preCreate(data, options, user);
}
/** @inheritdoc */
_onCreate(data, options, userId) {
super._onCreate(data, options, userId);
Hooks.callAll(RefreshType.EffectsDisplay);
}
/** @inheritdoc */
_onDelete(data, options, userId) {
super._onDelete(data, options, userId);
Hooks.callAll(RefreshType.EffectsDisplay);
}
/* -------------------------------------------- */
/* Methods */
/* -------------------------------------------- */

View file

@ -834,4 +834,37 @@ export default class DhpActor extends Actor {
if (this.system._getTags) tags.push(...this.system._getTags());
return tags;
}
/** Get active effects */
getActiveEffects() {
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
return this.effects
.filter(x => !x.disabled)
.reduce((acc, effect) => {
acc.push(effect);
const currentStatusActiveEffects = acc.filter(
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first()).name)
);
for (var status of effect.statuses) {
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
const statusData = statusMap.get(status);
if (statusData) {
acc.push({
condition: status,
appliedBy: game.i18n.localize(effect.name),
name: game.i18n.localize(statusData.name),
statuses: new Set([status]),
img: statusData.icon ?? statusData.img,
description: game.i18n.localize(statusData.description),
tint: effect.tint
});
}
}
}
return acc;
}, []);
}
}

View file

@ -7,7 +7,26 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
let html = options.html;
if (element.dataset.tooltip === '#effect-display#') {
this.#bordered = true;
const effect = await foundry.utils.fromUuid(element.dataset.uuid);
let effect = {};
if (element.dataset.uuid) {
const effectData = (await foundry.utils.fromUuid(element.dataset.uuid)).toObject();
effect = {
...effectData,
name: game.i18n.localize(effectData.name),
description: game.i18n.localize(effectData.description ?? effectData.parent.system.description)
};
} else {
const conditions = CONFIG.DH.GENERAL.conditions();
const condition = conditions[element.dataset.condition];
effect = {
...condition,
name: game.i18n.localize(condition.name),
description: game.i18n.localize(condition.description),
appliedBy: element.dataset.appliedBy,
isLockedCondition: true
};
}
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/effect-display.hbs`,
{

View file

@ -36,7 +36,8 @@ export const GMUpdateEvent = {
export const RefreshType = {
Countdown: 'DhCoundownRefresh',
TagTeamRoll: 'DhTagTeamRollRefresh'
TagTeamRoll: 'DhTagTeamRollRefresh',
EffectsDisplay: 'DhEffectsDisplayRefresh'
};
export const registerSocketHooks = () => {