Merged with v14-Dev

This commit is contained in:
WBHarry 2026-03-14 00:31:12 +01:00
commit 4332a1ba7a
613 changed files with 4618 additions and 2996 deletions

View file

@ -33,6 +33,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
handleResourceDice: CharacterSheet.#handleResourceDice,
advanceResourceDie: CharacterSheet.#advanceResourceDie,
cancelBeastform: CharacterSheet.#cancelBeastform,
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
useDowntime: this.useDowntime,
viewParty: CharacterSheet.#viewParty,
toggleArmorMangement: CharacterSheet.#toggleArmorManagement
@ -227,6 +228,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
switch (partId) {
case 'header':
await this._prepareHeaderContext(context, options);
break;
case 'loadout':
await this._prepareLoadoutContext(context, options);
break;
@ -241,6 +245,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
return context;
}
async _prepareHeaderContext(context, _options) {
context.hasExtraResources = Object.keys(CONFIG.DH.RESOURCE.character.all).some(
key => !CONFIG.DH.RESOURCE.character.base[key]
);
}
/**
* Prepare render context for the Loadout part.
* @param {ApplicationRenderContext} context
@ -1057,6 +1067,77 @@ export default class CharacterSheet extends DHBaseActorSheet {
await effect.update({ 'system.changes': newChanges });
}
static async #toggleResourceManagement(event, button) {
event.stopPropagation();
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
if (existingTooltip) {
game.tooltip.dismissLockedTooltips();
return;
}
const extraResources = Object.values(CONFIG.DH.RESOURCE.character.all).reduce((acc, resource) => {
if (CONFIG.DH.RESOURCE.character.base[resource.id]) return acc;
const resourceData = this.document.system.resources[resource.id];
acc[resource.id] = {
id: resource.id,
label: game.i18n.localize(resource.label),
value: resourceData.value,
max: resourceData.max,
fullIcon: resource.images?.full ?? { value: 'fa-solid fa-circle', isIcon: true },
emptyIcon: resource.images?.empty ?? { value: 'fa-regular fa-circle', isIcon: true }
};
return acc;
}, {});
const html = document.createElement('div');
html.innerHTML = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/resourceManagement.hbs`,
{
resources: extraResources
}
);
const target = button.closest('.resource-section');
game.tooltip.dismissLockedTooltips();
game.tooltip.activate(target, {
html,
locked: true,
cssClass: 'bordered-tooltip',
direction: 'DOWN',
noOffset: true
});
const resourceManager = target.querySelector('.resource-manager');
resourceManager.classList.toggle('inverted');
Hooks.once(CONFIG.DH.HOOKS.hooksConfig.lockedTooltipDismissed, () => {
resourceManager.classList.toggle('inverted');
});
for (const element of html.querySelectorAll('.resource-value'))
element.addEventListener('click', this.onUpdateResource.bind(this));
}
async onUpdateResource(event) {
const target = event.target.closest('.resource-value');
const { resource, value: textValue } = target.dataset;
const inputValue = Number.parseInt(textValue);
const decreasing = inputValue <= this.document.system.resources[resource].value;
const value = decreasing ? inputValue - 1 : inputValue;
await this.document.update({ [`system.resources.${resource}.value`]: value }, { render: false });
/* Update resource symbols */
const section = target.closest('.resource-section');
for (const element of section.querySelectorAll('.resource-value')) {
const showFull = Number.parseInt(element.dataset.value) <= value;
element.querySelector('.full').classList.toggle('hidden', !showFull);
element.querySelector('.empty').classList.toggle('hidden', showFull);
}
}
/**
* Open the downtime application.
* @type {ApplicationClickAction}

View file

@ -44,8 +44,32 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee
const context = await super._prepareContext(options);
context.isNPC = this.actor.isNPC;
if (context.systemFields.attack)
if (context.systemFields.attack) {
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
}
// Create fake fields for actor configurable max resource value.
const resourceConfig = CONFIG.DH.RESOURCE[this.actor.type]?.all;
if (resourceConfig) {
const relevant = ['hitPoints', 'stress'].filter(r => r in resourceConfig);
context.resources = relevant.map(key => {
const data = this.actor._source.system.resources[key];
const config = resourceConfig[key];
return {
label: config.label,
name: `system.resources.${key}.max`,
value: data.max ?? config.max,
tooltip: key === 'hitPoints' ? game.i18n.localize('DAGGERHEART.UI.Tooltip.maxHPClassBound') : null,
field: new foundry.data.fields.NumberField({
initial: config.max,
integer: true,
label: game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', {
thing: game.i18n.localize(config.label)
})
})
};
});
}
return context;
}

View file

@ -74,6 +74,15 @@ export default function DHApplicationMixin(Base) {
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
#nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard'];
/**
* @param {DHSheetV2Configuration} [options={}]
*/
constructor(options = {}) {
super(options);
this._setupDragDrop();
}
/**
* The default options for the sheet.
* @type {DHSheetV2Configuration}
@ -165,7 +174,9 @@ export default function DHApplicationMixin(Base) {
/**@inheritdoc */
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
// this._dragDrop.forEach(d => d.bind(htmlElement));
/* Core dragDrop from ActorDocument is always only 1. Possible we could refactor our own */
if (Array.isArray(this._dragDrop)) this._dragDrop.forEach(d => d.bind(htmlElement));
// Handle delta inputs
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
@ -338,6 +349,26 @@ export default function DHApplicationMixin(Base) {
/* Drag and Drop */
/* -------------------------------------------- */
/**
* Creates drag-drop handlers from the configured options.
* @returns {foundry.applications.ux.DragDrop[]}
* @private
*/
_setupDragDrop() {
if (this._dragDrop) {
this._dragDrop.callbacks.dragStart = this._onDragStart;
this._dragDrop.callback.drop = this._onDrop;
} else {
this._dragDrop = this.options.dragDrop.map(d => {
d.callbacks = {
dragstart: this._onDragStart.bind(this),
drop: this._onDrop.bind(this)
};
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
}
/**
* Handle dragStart event.
* @param {DragEvent} event
@ -472,7 +503,10 @@ export default function DHApplicationMixin(Base) {
icon: 'fa-solid fa-explosion',
condition: target => {
const doc = getDocFromElementSync(target);
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
return (
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
!foundry.utils.isEmpty(doc?.damage?.parts)
);
},
callback: async (target, event) => {
const doc = await getDocFromElement(target),
@ -664,6 +698,9 @@ export default function DHApplicationMixin(Base) {
case 'weapon':
presets.folder = 'equipments.folders.weapons';
break;
case 'feature':
presets.folder = 'features';
break;
case 'domainCard':
presets.folder = 'domains';
presets.filter = {