From aec5c06da76acc9bce9e7c59f366e915d44aef0d Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 25 Nov 2025 23:16:30 +0100 Subject: [PATCH] . --- lang/en.json | 4 ++- module/applications/hud/tokenHUD.mjs | 22 +++++++++++-- module/applications/ui/effectsDisplay.mjs | 8 +++-- module/canvas/placeables/token.mjs | 24 +------------- module/documents/activeEffect.mjs | 15 +++++++++ module/documents/actor.mjs | 33 ++++++++++++++++++++ module/documents/tooltipManager.mjs | 21 ++++++++++++- module/systemRegistration/socket.mjs | 3 +- styles/less/hud/token-hud/token-hud.less | 9 ++++++ styles/less/ui/effects-display/sheet.less | 18 ++++++++--- styles/less/ux/tooltip/bordered-tooltip.less | 9 ++++++ templates/hud/tokenHUD.hbs | 16 ++++++---- templates/ui/effects-display.hbs | 11 +++++-- templates/ui/tooltip/effect-display.hbs | 9 +++++- 14 files changed, 156 insertions(+), 46 deletions(-) diff --git a/lang/en.json b/lang/en.json index 1999f75d..8dc34bf7 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2032,6 +2032,7 @@ "basics": "Basics", "bonus": "Bonus", "burden": "Burden", + "condition": "Condition", "continue": "Continue", "criticalSuccess": "Critical Success", "criticalShort": "Critical", @@ -2586,7 +2587,8 @@ "increasingLoop": "Increasing Looping" }, "EffectsDisplay": { - "title": "" + "removeThing": "[Right Click] Remove {thing}", + "appliedBy": "Applied By: {by}" }, "ItemBrowser": { "title": "Daggerheart Compendium Browser", diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs index 5fa40a4c..6333579a 100644 --- a/module/applications/hud/tokenHUD.mjs +++ b/module/applications/hud/tokenHUD.mjs @@ -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 + }); + } } diff --git a/module/applications/ui/effectsDisplay.mjs b/module/applications/ui/effectsDisplay.mjs index 7ac3d964..50eecabd 100644 --- a/module/applications/ui/effectsDisplay.mjs +++ b/module/applications/ui/effectsDisplay.mjs @@ -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); } diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 367e7682..64ec3fa9 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -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 diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 1724ec13..fcf1d590 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -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 */ /* -------------------------------------------- */ diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 8faf1350..ee963dc1 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -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; + }, []); + } } diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index fd95d61e..25cf19b5 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -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`, { diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index ac61238f..27bf48c5 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -36,7 +36,8 @@ export const GMUpdateEvent = { export const RefreshType = { Countdown: 'DhCoundownRefresh', - TagTeamRoll: 'DhTagTeamRollRefresh' + TagTeamRoll: 'DhTagTeamRollRefresh', + EffectsDisplay: 'DhEffectsDisplayRefresh' }; export const registerSocketHooks = () => { diff --git a/styles/less/hud/token-hud/token-hud.less b/styles/less/hud/token-hud/token-hud.less index ec67b524..ac269172 100644 --- a/styles/less/hud/token-hud/token-hud.less +++ b/styles/less/hud/token-hud/token-hud.less @@ -52,6 +52,15 @@ align-items: center; justify-content: center; } + + .effect-locked { + position: absolute; + bottom: 2px; + right: 2px; + font-size: 12px; + color: @golden; + filter: drop-shadow(0 0 3px black); + } } } } diff --git a/styles/less/ui/effects-display/sheet.less b/styles/less/ui/effects-display/sheet.less index d5dea9fa..8fe9fcfb 100644 --- a/styles/less/ui/effects-display/sheet.less +++ b/styles/less/ui/effects-display/sheet.less @@ -2,8 +2,9 @@ position: absolute; right: 0; width: 68px; - max-height: 300px; + max-height: 600px; overflow: hidden; + border: 0; .window-content { > div { @@ -14,13 +15,20 @@ .effects-display-container { display: flex; flex-direction: column; - gap: 8px; + gap: 4px; .effect-container { - cursor: pointer; + position: relative; + pointer-events: all; + border: 2px solid light-dark(@dark-blue, @golden); - img { - pointer-events: all; + .effect-locked { + position: absolute; + bottom: 4px; + right: 4px; + font-size: 24px; + color: @golden; + filter: drop-shadow(0 0 3px black); } } } diff --git a/styles/less/ux/tooltip/bordered-tooltip.less b/styles/less/ux/tooltip/bordered-tooltip.less index 34c978d4..ef2c92ec 100644 --- a/styles/less/ux/tooltip/bordered-tooltip.less +++ b/styles/less/ux/tooltip/bordered-tooltip.less @@ -15,5 +15,14 @@ font-style: italic; } } + + .tooltip-footer { + display: flex; + justify-content: center; + font-size: 12px; + width: 100%; + font-style: italic; + font-weight: bold; + } } } diff --git a/templates/hud/tokenHUD.hbs b/templates/hud/tokenHUD.hbs index aaf2f5f9..20090729 100644 --- a/templates/hud/tokenHUD.hbs +++ b/templates/hud/tokenHUD.hbs @@ -46,18 +46,22 @@
{{#each systemStatusEffects as |status|}} -
- +
+ {{#if status.disabled}} / {{/if}} + {{#if status.locked}}{{/if}}
{{/each}} {{#if genericStatusEffects}} - - {{#each genericStatusEffects as |status|}} - - {{/each}} +
> + + {{#each genericStatusEffects as |status|}} + + {{/each}} + {{#if status.locked}}{{/if}} +
{{/if}}
{{/if}} diff --git a/templates/ui/effects-display.hbs b/templates/ui/effects-display.hbs index 6da7f9db..da37d8eb 100644 --- a/templates/ui/effects-display.hbs +++ b/templates/ui/effects-display.hbs @@ -1,9 +1,14 @@
{{#each effects as | effect |}} -
- -
+ + + + + {{#if effect.condition}}{{/if}} + {{/each}}
\ No newline at end of file diff --git a/templates/ui/tooltip/effect-display.hbs b/templates/ui/tooltip/effect-display.hbs index 31117257..1213e5cb 100644 --- a/templates/ui/tooltip/effect-display.hbs +++ b/templates/ui/tooltip/effect-display.hbs @@ -1,9 +1,16 @@

{{effect.name}}

-
{{localize "[Right Click] Remove Effect"}}
+
+ {{#unless effect.isLockedCondition}} + {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} + {{/unless}} +
{{#if effect.description}}{{{effect.description}}}{{else}}{{{effect.parent.system.description}}}{{/if}}
+ {{#if effect.appliedBy}} + + {{/if}}
\ No newline at end of file