[Feature] EffectsDisplay (#1340)

* Merged with main

* Added the display

* .

* Removed unused override function

* Fixed layout for generic effects

* feat: add basic style to effects tooltip

* Corrected distancing

* Use CSS based solution for shifting the countdowns

* Centered tooltip header

---------

Co-authored-by: moliloo <dev.murilobrito@gmail.com>
Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
This commit is contained in:
WBHarry 2025-11-30 18:30:57 +01:00 committed by GitHub
parent f41f0b20b7
commit 165068a9ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 399 additions and 41 deletions

View file

@ -61,6 +61,7 @@ CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
CONFIG.ui.combat = applications.ui.DhCombatTracker; CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.chat = applications.ui.DhChatLog; CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
CONFIG.ui.hotbar = applications.ui.DhHotbar; CONFIG.ui.hotbar = applications.ui.DhHotbar;
CONFIG.ui.sidebar = applications.sidebar.DhSidebar; CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
CONFIG.ui.actors = applications.sidebar.DhActorDirectory; CONFIG.ui.actors = applications.sidebar.DhActorDirectory;
@ -168,6 +169,9 @@ Hooks.on('ready', async () => {
ui.countdowns.render({ force: true }); ui.countdowns.render({ force: true });
} }
ui.effectsDisplay = new CONFIG.ui.effectsDisplay();
ui.effectsDisplay.render({ force: true });
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser)) if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
ui.compendiumBrowser = new applications.ui.ItemBrowser(); ui.compendiumBrowser = new applications.ui.ItemBrowser();

View file

@ -2032,6 +2032,7 @@
"basics": "Basics", "basics": "Basics",
"bonus": "Bonus", "bonus": "Bonus",
"burden": "Burden", "burden": "Burden",
"condition": "Condition",
"continue": "Continue", "continue": "Continue",
"criticalSuccess": "Critical Success", "criticalSuccess": "Critical Success",
"criticalShort": "Critical", "criticalShort": "Critical",
@ -2585,6 +2586,10 @@
"decreasingLoop": "Decreasing Looping", "decreasingLoop": "Decreasing Looping",
"increasingLoop": "Increasing Looping" "increasingLoop": "Increasing Looping"
}, },
"EffectsDisplay": {
"removeThing": "[Right Click] Remove {thing}",
"appliedBy": "Applied By: {by}"
},
"ItemBrowser": { "ItemBrowser": {
"title": "Daggerheart Compendium Browser", "title": "Daggerheart Compendium Browser",
"hint": "Select a Folder in sidebar to start browsing through the compendium", "hint": "Select a Folder in sidebar to start browsing through the compendium",

View file

@ -21,7 +21,6 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
context.partyOnCanvas = context.partyOnCanvas =
this.actor.type === 'party' && this.actor.type === 'party' &&
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0); this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
@ -31,6 +30,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type) context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
? false ? false
: context.canToggleCombat; : context.canToggleCombat;
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => { context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
const effect = context.statusEffects[key]; const effect = context.statusEffects[key];
if (effect.systemEffect) { if (effect.systemEffect) {
@ -193,16 +193,18 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
} }
// Update the status of effects which are active for the token actor // 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 effect of activeEffects) {
for (const statusId of effect.statuses) { for (const statusId of effect.statuses) {
const status = choices[statusId]; const status = choices[statusId];
status.instances = 1 + (status.instances ?? 0);
status.locked = status.locked || effect.condition || status.instances > 1;
if (!status) continue; if (!status) continue;
if (status._id) { if (status._id) {
if (status._id !== effect.id) continue; if (status._id !== effect.id) continue;
} }
status.isActive = true; status.isActive = true;
if (effect.getFlag('core', 'overlay')) status.isOverlay = true; if (effect.getFlag?.('core', 'overlay')) status.isOverlay = true;
} }
} }

View file

@ -2,6 +2,7 @@ export { default as CountdownEdit } from './countdownEdit.mjs';
export { default as DhCountdowns } from './countdowns.mjs'; export { default as DhCountdowns } from './countdowns.mjs';
export { default as DhChatLog } from './chatLog.mjs'; export { default as DhChatLog } from './chatLog.mjs';
export { default as DhCombatTracker } from './combatTracker.mjs'; export { default as DhCombatTracker } from './combatTracker.mjs';
export { default as DhEffectsDisplay } from './effectsDisplay.mjs';
export { default as DhFearTracker } from './fearTracker.mjs'; export { default as DhFearTracker } from './fearTracker.mjs';
export { default as DhHotbar } from './hotbar.mjs'; export { default as DhHotbar } from './hotbar.mjs';
export { ItemBrowser } from './itemBrowser.mjs'; export { ItemBrowser } from './itemBrowser.mjs';

View file

@ -14,7 +14,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
constructor(options = {}) { constructor(options = {}) {
super(options); super(options);
this.sidebarCollapsed = true;
this.setupHooks(); this.setupHooks();
} }
@ -98,11 +97,10 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
context.isGM = game.user.isGM; context.isGM = game.user.isGM;
context.sidebarCollapsed = this.sidebarCollapsed;
context.iconOnly = context.iconOnly =
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) === game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
CONFIG.DH.GENERAL.countdownAppMode.iconOnly; CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
const playersWithAccess = game.users.reduce((acc, user) => { const playersWithAccess = game.users.reduce((acc, user) => {

View file

@ -0,0 +1,117 @@
import { RefreshType } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
/**
* A UI element which displays the Active Effects on a selected token.
*
* @extends ApplicationV2
* @mixes HandlebarsApplication
*/
export default class DhEffectsDisplay extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(options = {}) {
super(options);
this.setupHooks();
}
/** @inheritDoc */
static DEFAULT_OPTIONS = {
id: 'effects-display',
tag: 'div',
classes: ['daggerheart', 'dh-style', 'effects-display'],
window: {
frame: false,
positioned: false,
resizable: false,
minimizable: false
},
actions: {}
};
/** @override */
static PARTS = {
resources: {
root: true,
template: 'systems/daggerheart/templates/ui/effects-display.hbs'
}
};
get element() {
return document.body.querySelector('.daggerheart.dh-style.effects-display');
}
get hidden() {
return this.element.classList.contains('hidden');
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
if (this.element) {
this.element.querySelectorAll('.effect-container a').forEach(element => {
element.addEventListener('contextmenu', this.removeEffect.bind(this));
});
}
}
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.effects = DhEffectsDisplay.getTokenEffects();
return context;
}
static getTokenEffects = token => {
const actor = token
? token.actor
: canvas.tokens.controlled.length === 0
? !game.user.isGM
? game.user.character
: null
: canvas.tokens.controlled[0].actor;
return actor?.getActiveEffects() ?? [];
};
toggleHidden(token, focused) {
const effects = DhEffectsDisplay.getTokenEffects(focused ? token : null);
this.element.hidden = effects.length === 0;
Hooks.callAll(CONFIG.DH.HOOKS.effectDisplayToggle, this.element.hidden, token);
this.render();
}
async removeEffect(event) {
const element = event.target.closest('.effect-container');
const effects = DhEffectsDisplay.getTokenEffects();
const effect = effects.find(x => x.id === element.id);
await effect.delete();
this.render();
}
setupHooks() {
Hooks.on('controlToken', this.toggleHidden.bind(this));
Hooks.on(RefreshType.EffectsDisplay, this.toggleHidden.bind(this));
}
async close(options) {
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
if (options.closeKey) return;
Hooks.off('controlToken', this.toggleHidden);
Hooks.off(RefreshType.EffectsDisplay, this.toggleHidden);
return super.close(options);
}
async _onRender(context, options) {
await super._onRender(context, options);
this.element.hidden = context.effects.length === 0;
if (options?.force) {
document.getElementById('ui-right-column-1')?.appendChild(this.element);
}
}
}

View file

@ -10,29 +10,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
this.effects.overlay = null; this.effects.overlay = null;
// Categorize effects // Categorize effects
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status])); const activeEffects = this.actor?.getActiveEffects() ?? [];
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 overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay')); const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
// Draw effects // Draw effects

View file

@ -4,6 +4,7 @@ export * as domainConfig from './domainConfig.mjs';
export * as effectConfig from './effectConfig.mjs'; export * as effectConfig from './effectConfig.mjs';
export * as flagsConfig from './flagsConfig.mjs'; export * as flagsConfig from './flagsConfig.mjs';
export * as generalConfig from './generalConfig.mjs'; export * as generalConfig from './generalConfig.mjs';
export * as hooksConfig from './hooksConfig.mjs';
export * as itemConfig from './itemConfig.mjs'; export * as itemConfig from './itemConfig.mjs';
export * as settingsConfig from './settingsConfig.mjs'; export * as settingsConfig from './settingsConfig.mjs';
export * as systemConfig from './system.mjs'; export * as systemConfig from './system.mjs';

View file

@ -0,0 +1,5 @@
const hooksConfig = {
effectDisplayToggle: 'DHEffectDisplayToggle'
};
export default hooksConfig;

View file

@ -6,7 +6,8 @@ import * as SETTINGS from './settingsConfig.mjs';
import * as EFFECTS from './effectConfig.mjs'; import * as EFFECTS from './effectConfig.mjs';
import * as ACTIONS from './actionConfig.mjs'; import * as ACTIONS from './actionConfig.mjs';
import * as FLAGS from './flagsConfig.mjs'; import * as FLAGS from './flagsConfig.mjs';
import * as ITEMBROWSER from './itemBrowserConfig.mjs' import HOOKS from './hooksConfig.mjs';
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
export const SYSTEM_ID = 'daggerheart'; export const SYSTEM_ID = 'daggerheart';
@ -20,5 +21,6 @@ export const SYSTEM = {
EFFECTS, EFFECTS,
ACTIONS, ACTIONS,
FLAGS, FLAGS,
HOOKS,
ITEMBROWSER ITEMBROWSER
}; };

View file

@ -1,4 +1,5 @@
import { itemAbleRollParse } from '../helpers/utils.mjs'; import { itemAbleRollParse } from '../helpers/utils.mjs';
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
export default class DhActiveEffect extends foundry.documents.ActiveEffect { 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); 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 */ /* Methods */
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -844,4 +844,37 @@ export default class DhpActor extends Actor {
if (this.system._getTags) tags.push(...this.system._getTags()); if (this.system._getTags) tags.push(...this.system._getTags());
return tags; 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

@ -1,8 +1,45 @@
export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager { export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager {
#bordered = false;
async activate(element, options = {}) { async activate(element, options = {}) {
const { TextEditor } = foundry.applications.ux; const { TextEditor } = foundry.applications.ux;
let html = options.html; let html = options.html;
if (element.dataset.tooltip === '#effect-display#') {
this.#bordered = true;
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`,
{
effect
}
);
this.tooltip.innerHTML = html;
options.direction = this._determineItemTooltipDirection(element);
} else {
this.#bordered = false;
}
if (element.dataset.tooltip?.startsWith('#item#')) { if (element.dataset.tooltip?.startsWith('#item#')) {
const itemUuid = element.dataset.tooltip.slice(6); const itemUuid = element.dataset.tooltip.slice(6);
const item = await foundry.utils.fromUuid(itemUuid); const item = await foundry.utils.fromUuid(itemUuid);
@ -112,6 +149,14 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
super.activate(element, { ...options, html: html }); super.activate(element, { ...options, html: html });
} }
_setStyle(position = {}) {
super._setStyle(position);
if (this.#bordered) {
this.tooltip.classList.add('bordered-tooltip');
}
}
_determineItemTooltipDirection(element, prefered = this.constructor.TOOLTIP_DIRECTIONS.LEFT) { _determineItemTooltipDirection(element, prefered = this.constructor.TOOLTIP_DIRECTIONS.LEFT) {
const pos = element.getBoundingClientRect(); const pos = element.getBoundingClientRect();
const dirs = this.constructor.TOOLTIP_DIRECTIONS; const dirs = this.constructor.TOOLTIP_DIRECTIONS;

View file

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

View file

@ -52,6 +52,15 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.effect-locked {
position: absolute;
bottom: 2px;
right: 2px;
font-size: 12px;
color: @golden;
filter: drop-shadow(0 0 3px black);
}
} }
} }
} }

View file

@ -12,18 +12,23 @@
} }
.daggerheart.dh-style.countdowns { .daggerheart.dh-style.countdowns {
position: initial; position: relative;
border: 0; border: 0;
border-radius: 4px; border-radius: 4px;
box-shadow: none; box-shadow: none;
width: 300px; width: 300px;
pointer-events: all; pointer-events: all;
align-self: flex-end; align-self: flex-end;
transition: 0.3s right ease-in-out;
.window-title { .window-title {
font-family: @font-body; font-family: @font-body;
} }
#ui-right:has(#effects-display .effect-container) & {
right: 62px;
}
&.icon-only { &.icon-only {
width: 180px; width: 180px;
min-width: 180px; min-width: 180px;

View file

@ -0,0 +1,40 @@
.daggerheart.dh-style.effects-display {
position: absolute;
right: 0;
width: 46px;
max-height: 600px;
overflow: hidden;
border: 0;
.window-content {
> div {
height: 100%;
}
}
.effects-display-container {
display: flex;
flex-direction: column;
gap: 4px;
.effect-container {
position: relative;
pointer-events: all;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 3px;
img {
border-radius: 3px;
}
.effect-locked {
position: absolute;
bottom: 4px;
right: 4px;
font-size: 16px;
color: @golden;
filter: drop-shadow(0 0 3px black);
}
}
}
}

View file

@ -31,3 +31,5 @@
@import './sidebar/daggerheartMenu.less'; @import './sidebar/daggerheartMenu.less';
@import './scene-config/scene-config.less'; @import './scene-config/scene-config.less';
@import './effects-display/sheet.less';

View file

@ -4,12 +4,18 @@
} }
} }
#interface #ui-right #sidebar { #interface #ui-right {
menu li button { #ui-right-column-1 {
img { position: relative;
width: 22px; }
max-width: unset;
filter: @beige-filter; #sidebar {
menu li button {
img {
width: 22px;
max-width: unset;
filter: @beige-filter;
}
} }
} }
} }

View file

@ -28,6 +28,8 @@
@dark-golden-40: #2b1d0340; @dark-golden-40: #2b1d0340;
@dark-golden-80: #2b1d0380; @dark-golden-80: #2b1d0380;
@rustic-brown-80: #4c340780;
@red: #e54e4e; @red: #e54e4e;
@red-10: #e54e4e10; @red-10: #e54e4e10;
@red-40: #e54e4e40; @red-40: #e54e4e40;

View file

@ -1,2 +1,3 @@
@import './tooltip/tooltip.less'; @import './tooltip/tooltip.less';
@import './tooltip/bordered-tooltip.less';
@import './autocomplete/autocomplete.less'; @import './autocomplete/autocomplete.less';

View file

@ -0,0 +1,44 @@
#tooltip.bordered-tooltip {
border: 1px solid @golden;
background-image: url('../assets/parchments/dh-parchment-dark.png');
.daggerheart.dh-style.tooltip {
display: flex;
flex-direction: column;
text-align: start;
width: 100%;
gap: 5px;
padding: 0px;
border-radius: 3px;
.tooltip-header {
display: flex;
flex-direction: column;
text-align: start;
padding: 5px;
gap: 0px;
h2 {
display: flex;
justify-content: start;
font-size: var(--font-size-20);
color: @golden;
font-weight: 700;
margin: 0;
padding: 0;
}
.subtitle {
font-size: 12px;
}
}
.close-hint {
border-radius: 3px;
padding: 3px;
background: @rustic-brown-80;
color: @golden;
font-size: 12px;
}
}
}

View file

@ -46,17 +46,21 @@
</button> </button>
<div class="palette status-effects" data-palette="effects"> <div class="palette status-effects" data-palette="effects">
{{#each systemStatusEffects as |status|}} {{#each systemStatusEffects as |status|}}
<div class="effect-control-container" {{#if status.disabled}}data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.immune"}}"{{/if}}> <div class="effect-control-container" {{#if status.disabled}}data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.immune"}}"{{else if status.title}}data-tooltip-text="{{status.title}}"{{/if}}>
<img class="effect-control {{status.cssClass}} {{#if status.disabled}}disabled{{/if}}" src="{{status.src}}" data-action="effect" data-status-id="{{status.id}}" {{#if status.title}}data-tooltip-text="{{status.title}}"{{/if}}> <img class="effect-control {{status.cssClass}} {{#if (or status.disabled status.locked)}}disabled{{/if}}" src="{{status.src}}" data-action="effect" data-status-id="{{status.id}}">
{{#if status.disabled}} {{#if status.disabled}}
<span class="effect-control-disabled-marker">/</span> <span class="effect-control-disabled-marker">/</span>
{{/if}} {{/if}}
{{#if status.locked}}<i class="effect-locked fa-solid fa-lock"></i>{{/if}}
</div> </div>
{{/each}} {{/each}}
{{#if genericStatusEffects}} {{#if genericStatusEffects}}
<label class="palette-category-title">{{localize "DAGGERHEART.APPLICATIONS.HUD.tokenHUD.genericEffects"}}</label> <label class="palette-category-title">{{localize "DAGGERHEART.APPLICATIONS.HUD.tokenHUD.genericEffects"}}</label>
{{#each genericStatusEffects as |status|}} {{#each genericStatusEffects as |status|}}
<img class="effect-control {{status.cssClass}}" src="{{status.src}}" data-action="effect" data-status-id="{{status.id}}" {{#if status.title}}data-tooltip-text="{{status.title}}"{{/if}}> <div class="effect-control-container" {{#if status.title}}data-tooltip-text="{{status.title}}"{{/if}}>
<img class="effect-control {{status.cssClass}} {{#if (or status.disabled status.locked)}}disabled{{/if}}" src="{{status.src}}" data-action="effect" data-status-id="{{status.id}}" >
{{#if status.locked}}<i class="effect-locked fa-solid fa-lock"></i>{{/if}}
</div>
{{/each}} {{/each}}
{{/if}} {{/if}}
</div> </div>

View file

@ -0,0 +1,14 @@
<div>
<div class="effects-display-container">
{{#each effects as | effect |}}
<span class="effect-container {{#if effect.condition}}disabled{{/if}}" data-tooltip="#effect-display#" id="{{effect.id}}"
data-applied-by="{{effect.appliedBy}}" {{#if effect.condition}}data-condition="{{effect.condition}}"{{else}}data-uuid="{{effect.uuid}}"{{/if}}
>
<a {{#if effect.condition}}disabled{{/if}}>
<img src="{{effect.img}}" />
</a>
{{#if effect.condition}}<i class="effect-locked fa-solid fa-lock"></i>{{/if}}
</span>
{{/each}}
</div>
</div>

View file

@ -0,0 +1,24 @@
<div class="daggerheart dh-style tooltip">
<div class="tooltip-header">
<h2>{{effect.name}}</h2>
{{#if effect.appliedBy}}
<p class="subtitle">{{localize "DAGGERHEART.UI.EffectsDisplay.appliedBy" by=effect.appliedBy}}</p>
{{/if}}
</div>
{{#if effect.description}}
<div class="description">
{{{effect.description}}}
</div>
{{else if effect.parent.system.description}}
<div class="description">
{{{effect.parent.system.description}}}
</div>
{{/if}}
{{#unless effect.isLockedCondition}}
<p class="close-hint">
<i class="fa-solid fa-computer-mouse"></i> {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}}
</p>
{{/unless}}
</div>