Merge branch 'hotfix' into fix/864-chat-targeting

This commit is contained in:
Dapoolp 2025-08-13 15:50:35 +02:00
commit e5452f1c2a
13 changed files with 208 additions and 55 deletions

View file

@ -1939,6 +1939,7 @@
"itemResource": "Item Resource", "itemResource": "Item Resource",
"label": "Label", "label": "Label",
"level": "Level", "level": "Level",
"levelShort": "Lv",
"levelUp": "Level Up", "levelUp": "Level Up",
"loadout": "Loadout", "loadout": "Loadout",
"max": "Max", "max": "Max",
@ -2086,7 +2087,12 @@
"FIELDS": { "FIELDS": {
"displayFear": { "label": "Fear Display" }, "displayFear": { "label": "Fear Display" },
"dualityColorScheme": { "label": "Chat Style" }, "dualityColorScheme": { "label": "Chat Style" },
"showGenericStatusEffects": { "label": "Show Foundry Status Effects" } "showGenericStatusEffects": { "label": "Show Foundry Status Effects" },
"expandedTitle": "Auto-expand Descriptions",
"extendCharacterDescriptions": { "label": "Characters" },
"extendAdversaryDescriptions": { "label": "Adversaries" },
"extendEnvironmentDescriptions": { "label": "Environments" },
"extendItemDescriptions": { "label": "Items" }
}, },
"fearDisplay": { "fearDisplay": {
"token": "Tokens", "token": "Tokens",

View file

@ -638,15 +638,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
ability: abilityLabel ability: abilityLabel
}) })
}); });
setTimeout(() => { this.consumeResource(result?.costs);
this.consumeResource(result?.costs);
}, 50);
} }
// Remove when Action Refactor part #2 done
async consumeResource(costs) { async consumeResource(costs) {
if (!costs?.length) return; if (!costs?.length) return;
const usefulResources = foundry.utils.deepClone(this.actor.system.resources); const usefulResources = {
...foundry.utils.deepClone(this.actor.system.resources),
fear: {
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
reversed: false
}
};
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => { const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
const resource = usefulResources[c.key]; const resource = usefulResources[c.key];
return { return {

View file

@ -2,6 +2,23 @@ const { HandlebarsApplicationMixin } = foundry.applications.api;
import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs'; import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs';
import { ItemBrowser } from '../../ui/itemBrowser.mjs'; import { ItemBrowser } from '../../ui/itemBrowser.mjs';
const typeSettingsMap = {
character: 'extendCharacterDescriptions',
adversary: 'extendAdversaryDescriptions',
environment: 'extendEnvironmentDescriptions',
ancestry: 'extendItemDescriptions',
community: 'extendItemDescriptions',
class: 'extendItemDescriptions',
subclass: 'extendItemDescriptions',
feature: 'extendItemDescriptions',
domainCard: 'extendItemDescriptions',
loot: 'extendItemDescriptions',
consumable: 'extendItemDescriptions',
weapon: 'extendItemDescriptions',
armor: 'extendItemDescriptions',
beastform: 'extendItemDescriptions'
};
/** /**
* @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction * @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction
*/ */
@ -137,6 +154,8 @@ export default function DHApplicationMixin(Base) {
docs.filter(doc => doc).forEach(doc => (doc.apps[this.id] = this)); docs.filter(doc => doc).forEach(doc => (doc.apps[this.id] = this));
if (!!this.options.contextMenus.length) this._createContextMenus(); if (!!this.options.contextMenus.length) this._createContextMenus();
this.#autoExtendDescriptions(context);
} }
/** @inheritDoc */ /** @inheritDoc */
@ -149,6 +168,7 @@ export default function DHApplicationMixin(Base) {
async _onRender(context, options) { async _onRender(context, options) {
await super._onRender(context, options); await super._onRender(context, options);
this._createTagifyElements(this.options.tagifyConfigs); this._createTagifyElements(this.options.tagifyConfigs);
await this.#prepareInventoryDescription(context);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -162,13 +182,7 @@ export default function DHApplicationMixin(Base) {
const { actionId, itemUuid } = el.parentElement.dataset; const { actionId, itemUuid } = el.parentElement.dataset;
const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`; const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
const newExtensible = newElement.querySelector(selector); const newExtensible = newElement.querySelector(selector);
newExtensible?.classList.add('extended');
if (!newExtensible) continue;
newExtensible.classList.add('extended');
const descriptionElement = newExtensible.querySelector('.invetory-description');
if (descriptionElement) {
this.#prepareInventoryDescription(newExtensible, descriptionElement);
}
} }
} }
@ -395,6 +409,7 @@ export default function DHApplicationMixin(Base) {
context.source = this.document; context.source = this.document;
context.fields = this.document.schema.fields; context.fields = this.document.schema.fields;
context.systemFields = this.document.system.schema.fields; context.systemFields = this.document.system.schema.fields;
context.settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
return context; return context;
} }
@ -404,32 +419,69 @@ export default function DHApplicationMixin(Base) {
/** /**
* Prepares and enriches an inventory item or action description for display. * Prepares and enriches an inventory item or action description for display.
* @param {HTMLElement} extensibleElement - The parent element containing the description.
* @param {HTMLElement} descriptionElement - The element where the enriched description will be rendered.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async #prepareInventoryDescription(extensibleElement, descriptionElement) { async #prepareInventoryDescription(context) {
const parent = extensibleElement.closest('[data-item-uuid], [data-action-id]'); // Get all inventory item elements with a data-item-uuid attribute
const { actionId, itemUuid } = parent?.dataset || {}; const inventoryItems = this.element.querySelectorAll('.inventory-item[data-item-uuid]');
if (!actionId && !itemUuid) return; for (const el of inventoryItems) {
// Get the doc uuid from the element
const { itemUuid } = el?.dataset || {};
if (!itemUuid) continue;
const doc = itemUuid //get doc by uuid
? await getDocFromElement(extensibleElement) const doc = await fromUuid(itemUuid);
: this.document.system.attack?.id === actionId
? this.document.system.attack
: this.document.system.actions?.get(actionId);
if (!doc) return;
const description = game.i18n.localize(doc.system?.description ?? doc.description); //get inventory-item description element
const isAction = !!actionId; const descriptionElement = el.querySelector('.invetory-description');
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML( if (!doc || !descriptionElement) continue;
description,
{ // localize the description (idk if it's still necessary)
relativeTo: isAction ? doc.parent : doc, const description = game.i18n.localize(doc.system?.description ?? doc.description);
rollData: doc.getRollData?.(),
secrets: isAction ? doc.parent.isOwner : doc.isOwner // Enrich the description and attach it;
const isAction = doc.documentName === 'Action';
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
description,
{
relativeTo: isAction ? doc.parent : doc,
rollData: doc.getRollData?.(),
secrets: isAction ? doc.parent.isOwner : doc.isOwner
}
);
}
}
/* -------------------------------------------- */
/* Extend Descriptions by Settings */
/* -------------------------------------------- */
/**
* Extend inventory description when enabled in settings.
* @returns {Promise<void>}
*/
async #autoExtendDescriptions(context) {
const inventoryItems = this.element.querySelectorAll('.inventory-item[data-item-uuid]');
for (const el of inventoryItems) {
// Get the doc uuid from the element
const { itemUuid } = el?.dataset || {};
if (!itemUuid) continue;
//get doc by uuid
const doc = await fromUuid(itemUuid);
//check the type of the document
const actorType =
doc?.type === 'adversary' && context.document?.type === 'environment'
? typeSettingsMap[doc?.type]
: doc.actor?.type;
// If the actor type is defined and the setting is enabled, extend the description
if (typeSettingsMap[actorType]) {
const settingKey = typeSettingsMap[actorType];
if (context.settings[settingKey]) this.#activeExtended(el);
} }
); }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -437,8 +489,6 @@ export default function DHApplicationMixin(Base) {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async #addNewItem(event, target) { static async #addNewItem(event, target) {
const { type } = target.dataset;
const createChoice = await foundry.applications.api.DialogV2.wait({ const createChoice = await foundry.applications.api.DialogV2.wait({
classes: ['dh-style', 'two-big-buttons'], classes: ['dh-style', 'two-big-buttons'],
buttons: [ buttons: [
@ -606,10 +656,12 @@ export default function DHApplicationMixin(Base) {
static async #toggleExtended(_, target) { static async #toggleExtended(_, target) {
const container = target.closest('.inventory-item'); const container = target.closest('.inventory-item');
const extensible = container?.querySelector('.extensible'); const extensible = container?.querySelector('.extensible');
const t = extensible?.classList.toggle('extended'); extensible?.classList.toggle('extended');
}
const descriptionElement = extensible?.querySelector('.invetory-description'); async #activeExtended(element) {
if (t && !!descriptionElement) await this.#prepareInventoryDescription(extensible, descriptionElement); const extensible = element?.querySelector('.extensible');
extensible?.classList.add('extended');
} }
} }

View file

@ -15,7 +15,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
acc.push(effect); acc.push(effect);
const currentStatusActiveEffects = acc.filter( const currentStatusActiveEffects = acc.filter(
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first()).name) x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
); );
for (var status of effect.statuses) { for (var status of effect.statuses) {
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) { if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {

View file

@ -77,6 +77,7 @@ export default class DHDomainCard extends BaseDataItem {
const tags = [ const tags = [
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`), game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
this.domainLabel, this.domainLabel,
`${game.i18n.localize('DAGGERHEART.GENERAL.levelShort')}: ${this.level}`,
`${game.i18n.localize('DAGGERHEART.ITEMS.DomainCard.recallCost')}: ${this.recallCost}` `${game.i18n.localize('DAGGERHEART.ITEMS.DomainCard.recallCost')}: ${this.recallCost}`
]; ];

View file

@ -55,6 +55,22 @@ export default class DhAppearance extends foundry.abstract.DataModel {
showGenericStatusEffects: new fields.BooleanField({ showGenericStatusEffects: new fields.BooleanField({
initial: true, initial: true,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label' label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label'
}),
extendCharacterDescriptions: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendCharacterDescriptions.label'
}),
extendAdversaryDescriptions: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendAdversaryDescriptions.label'
}),
extendEnvironmentDescriptions: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendEnvironmentDescriptions.label'
}),
extendItemDescriptions: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendItemDescriptions.label'
}) })
}; };
} }

View file

@ -222,26 +222,27 @@ export const registerRollDiceHooks = () => {
) )
return; return;
const actor = await fromUuid(config.source.actor), const actor = await fromUuid(config.source.actor);
updates = []; let updates = [];
if (!actor) return; if (!actor) return;
if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ key: 'hope', value: 1 }); if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1 }); if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1 }); if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
if (config.rerolledRoll) { if (config.rerolledRoll) {
if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1) if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1)
updates.push({ key: 'hope', value: -1 }); updates.push({ key: 'hope', value: -1, total: 1, enabled: true });
if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: 1 }); if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: 1, total: -1, enabled: true });
if (config.rerolledRoll.result.duality === -1) updates.push({ key: 'fear', value: -1 }); if (config.rerolledRoll.result.duality === -1) updates.push({ key: 'fear', value: -1, total: 1, enabled: true });
} }
if (updates.length) { if (updates.length) {
const target = actor.system.partner ?? actor; const target = actor.system.partner ?? actor;
if (!['dead', 'unconscious'].some(x => actor.statuses.has(x))) { if (!['dead', 'unconscious'].some(x => actor.statuses.has(x))) {
setTimeout(() => { if(config.rerolledRoll)
target.modifyResource(updates); target.modifyResource(updates);
}, 50); else
config.costs = [...(config.costs ?? []), ...updates];
} }
} }
@ -254,5 +255,7 @@ export const registerRollDiceHooks = () => {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId); const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id); if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
} }
return;
}); });
}; };

View file

@ -72,7 +72,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
if(!game.user.isGM) { if(!game.user.isGM) {
const applyButtons = html.querySelector(".apply-buttons"); const applyButtons = html.querySelector(".apply-buttons");
applyButtons.remove(); applyButtons?.remove();
if(!this.isAuthor && !this.speakerActor?.isOwner) { if(!this.isAuthor && !this.speakerActor?.isOwner) {
const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button"); const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button");
buttons.forEach(b => b.remove()); buttons.forEach(b => b.remove());

View file

@ -35,6 +35,37 @@
} }
} }
} }
&:hover {
.inventory-item-header .item-label .item-name .expanded-icon {
margin-left: 10px;
display: inline-block;
}
&:has(.inventory-item-content.extensible) {
.inventory-item-header,
.inventory-item-content {
background: light-dark(@dark-blue-40, @golden-40);
}
}
&:has(.inventory-item-content.extended) {
.inventory-item-header .item-label .item-name .expanded-icon {
display: none;
}
}
}
&:has(.inventory-item-content.extensible) {
.inventory-item-header {
border-radius: 5px 5px 0 0;
}
.inventory-item-content {
border-radius: 0 0 5px 5px;
}
}
&:not(:has(.inventory-item-content.extensible)) .inventory-item-header {
border-radius: 5px;
}
} }
.inventory-item-header { .inventory-item-header {
@ -42,6 +73,7 @@
align-items: center; align-items: center;
gap: 10px; gap: 10px;
cursor: pointer; cursor: pointer;
border-radius: 3px;
.img-portait { .img-portait {
flex: 0 0 40px; flex: 0 0 40px;
@ -75,6 +107,10 @@
.item-name { .item-name {
font-size: 14px; font-size: 14px;
.expanded-icon {
display: none;
}
} }
.item-tags, .item-tags,
@ -118,6 +154,11 @@
justify-content: end; justify-content: end;
gap: 8px; gap: 8px;
a {
width: 15px;
text-align: center;
}
.unequipped { .unequipped {
opacity: 0.5; opacity: 0.5;
} }

View file

@ -1,3 +1,16 @@
.theme-light .application.daggerheart.dh-style.dialog {
.tab.details {
.traits-inner-container .trait-container {
background: url('../assets/svg/trait-shield-light.svg') no-repeat;
div {
filter: drop-shadow(0 0 3px @beige);
text-shadow: 0 0 3px @beige;
}
}
}
}
.application.daggerheart.dh-style.dialog { .application.daggerheart.dh-style.dialog {
.tab.details { .tab.details {
.traits-inner-container { .traits-inner-container {

View file

@ -28,7 +28,10 @@
"name": "jacobwojoski" "name": "jacobwojoski"
}, },
{ {
"name": "moliloo" "name": "moliloo",
"url": "https://github.com/moliloo",
"email": "molilofl@gmail.com",
"discord": "molilo"
}, },
{ {
"name": "Mysteryusy" "name": "Mysteryusy"

View file

@ -2,8 +2,20 @@
<header class="dialog-header"> <header class="dialog-header">
<h1>{{localize 'DAGGERHEART.SETTINGS.Menu.appearance.name'}}</h1> <h1>{{localize 'DAGGERHEART.SETTINGS.Menu.appearance.name'}}</h1>
</header> </header>
{{formGroup settingFields.schema.fields.displayFear value=settingFields._source.displayFear localize=true}}
{{formGroup settingFields.schema.fields.showGenericStatusEffects value=settingFields._source.showGenericStatusEffects localize=true}} <fieldset>
<legend>{{localize 'DAGGERHEART.GENERAL.fear'}}</legend>
{{formGroup settingFields.schema.fields.displayFear value=settingFields._source.displayFear localize=true}}
{{formGroup settingFields.schema.fields.showGenericStatusEffects value=settingFields._source.showGenericStatusEffects localize=true}}
</fieldset>
<fieldset>
<legend>{{localize 'DAGGERHEART.SETTINGS.Appearance.FIELDS.expandedTitle'}}</legend>
{{formGroup settingFields.schema.fields.extendCharacterDescriptions value=settingFields._source.extendCharacterDescriptions localize=true}}
{{formGroup settingFields.schema.fields.extendAdversaryDescriptions value=settingFields._source.extendAdversaryDescriptions localize=true}}
{{formGroup settingFields.schema.fields.extendEnvironmentDescriptions value=settingFields._source.extendEnvironmentDescriptions localize=true}}
{{formGroup settingFields.schema.fields.extendItemDescriptions value=settingFields._source.extendItemDescriptions localize=true}}
</fieldset>
{{#if showDiceSoNice}} {{#if showDiceSoNice}}
<fieldset> <fieldset>

View file

@ -30,7 +30,7 @@ Parameters:
<div class="item-label"> <div class="item-label">
{{!-- Item Name --}} {{!-- Item Name --}}
<div class="item-name">{{localize item.name}}</div> <div class="item-name">{{localize item.name}} {{#unless noExtensible}}<span class="expanded-icon"><i class="fa-solid fa-expand"></i></span>{{/unless}}</div>
{{!-- Tags Start --}} {{!-- Tags Start --}}
{{#with item}} {{#with item}}