Merged with main

This commit is contained in:
WBHarry 2026-01-26 14:36:58 +01:00
commit 286944d2e6
207 changed files with 4909 additions and 1073 deletions

View file

@ -1,3 +1,4 @@
export * as actors from './actors/_module.mjs';
export * as api from './api/_modules.mjs';
export * as items from './items/_module.mjs';
export * as rollTables from './rollTables/_module.mjs';

View file

@ -31,7 +31,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
dropSelector: null
}
],
]
};
static PARTS = {
@ -185,7 +185,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
super._onDragStart(event);
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */

View file

@ -1,5 +1,5 @@
import DHBaseActorSheet from '../api/base-actor.mjs';
import DhpDeathMove from '../../dialogs/deathMove.mjs';
import DhDeathMove from '../../dialogs/deathMove.mjs';
import { abilities } from '../../../config/actorConfig.mjs';
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
@ -27,6 +27,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
makeDeathMove: CharacterSheet.#makeDeathMove,
levelManagement: CharacterSheet.#levelManagement,
viewLevelups: CharacterSheet.#viewLevelups,
resetCharacter: CharacterSheet.#resetCharacter,
toggleEquipItem: CharacterSheet.#toggleEquipItem,
toggleResourceDice: CharacterSheet.#toggleResourceDice,
handleResourceDice: CharacterSheet.#handleResourceDice,
@ -42,6 +43,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
icon: 'fa-solid fa-angles-up',
label: 'DAGGERHEART.ACTORS.Character.viewLevelups',
action: 'viewLevelups'
},
{
icon: 'fa-solid fa-arrow-rotate-left',
label: 'DAGGERHEART.ACTORS.Character.resetCharacter',
action: 'resetCharacter'
}
]
},
@ -224,13 +230,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
switch (partId) {
case 'header':
const { playerCanEditSheet, levelupAuto } = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Automation
);
context.showSettings = game.user.isGM || !levelupAuto || (levelupAuto && playerCanEditSheet);
break;
case 'loadout':
await this._prepareLoadoutContext(context, options);
break;
@ -679,12 +678,19 @@ export default class CharacterSheet extends DHBaseActorSheet {
new LevelupViewMode(this.document).render({ force: true });
}
/**
* Resets the character data and removes all embedded documents.
*/
static async #resetCharacter() {
new game.system.api.applications.dialogs.CharacterResetDialog(this.document).render({ force: true });
}
/**
* Opens the Death Move interface for the character.
* @type {ApplicationClickAction}
*/
static async #makeDeathMove() {
await new DhpDeathMove(this.document).render({ force: true });
await new DhDeathMove(this.document).render({ force: true });
}
/**
@ -725,8 +731,10 @@ export default class CharacterSheet extends DHBaseActorSheet {
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel
}),
effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document),
roll: {
trait: button.dataset.attribute
trait: button.dataset.attribute,
type: 'trait'
},
hasRoll: true,
actionType: 'action',
@ -736,11 +744,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
})
};
const result = await this.document.diceRoll(config);
if (!result) return;
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
const costResources = result.costs
.filter(x => x.enabled)
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
const costResources =
result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) ||
{};
config.resourceUpdates.addResources(costResources);
await config.resourceUpdates.updateResources();
}
@ -840,7 +849,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
static async #toggleVault(_event, button) {
const doc = await getDocFromElement(button);
const { available } = this.document.system.loadoutSlot;
if (doc.system.inVault && !available) {
if (doc.system.inVault && !available && !doc.system.loadoutIgnore) {
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
}
@ -971,6 +980,18 @@ export default class CharacterSheet extends DHBaseActorSheet {
return this._onSidebarDrop(event, item);
}
const setupCriticalItemTypes = ['class', 'subclass', 'ancestry', 'community'];
if (this.document.system.needsCharacterSetup && setupCriticalItemTypes.includes(item.type)) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.CharacterCreation.setupSkipTitle')
},
content: game.i18n.localize('DAGGERHEART.APPLICATIONS.CharacterCreation.setupSkipContent')
});
if (!confirmed) return;
}
if (this.document.uuid === item.parent?.uuid) {
return super._onDropItem(event, item);
}

View file

@ -38,15 +38,6 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
}
};
/** @inheritDoc */
async _onRender(context, options) {
await super._onRender(context, options);
this.element
.querySelector('.level-value')
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
@ -71,10 +62,10 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
title: `${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}: ${this.actor.name}`,
headerTitle: `Companion ${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}`,
roll: {
trait: partner.system.spellcastModifierTrait?.key
trait: partner.system.spellcastModifierTrait?.key,
companionRoll: true
},
hasRoll: true,
data: partner.getRollData()
hasRoll: true
};
const result = await partner.diceRoll(config);

View file

@ -505,6 +505,10 @@ export default function DHApplicationMixin(Base) {
const doc = await getDocFromElement(target),
action = doc?.system?.attack ?? doc;
const config = action.prepareConfig(event);
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(
this.document,
doc
);
config.hasRoll = false;
return action && action.workflow.get('damage').execute(config, null, true);
}
@ -629,7 +633,7 @@ export default function DHApplicationMixin(Base) {
{
relativeTo: isAction ? doc.parent : doc,
rollData: doc.getRollData?.(),
secrets: isAction ? doc.parent.isOwner : doc.isOwner
secrets: isAction ? doc.parent.parent.isOwner : doc.isOwner
}
);
}

View file

@ -0,0 +1 @@
export { default as RollTableSheet } from './rollTable.mjs';

View file

@ -0,0 +1,191 @@
export default class DhRollTableSheet extends foundry.applications.sheets.RollTableSheet {
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
actions: {
changeMode: DhRollTableSheet.#onChangeMode,
drawResult: DhRollTableSheet.#onDrawResult,
resetResults: DhRollTableSheet.#onResetResults,
addFormula: DhRollTableSheet.#addFormula,
removeFormula: DhRollTableSheet.#removeFormula
}
};
static buildParts() {
const { footer, header, sheet, results, ...parts } = super.PARTS;
return {
sheet: {
...sheet,
template: 'systems/daggerheart/templates/sheets/rollTable/sheet.hbs'
},
header: { template: 'systems/daggerheart/templates/sheets/rollTable/header.hbs' },
...parts,
results: {
template: 'systems/daggerheart/templates/sheets/rollTable/results.hbs',
templates: ['templates/sheets/roll-table/result-details.hbs'],
scrollable: ['table[data-results] tbody']
},
summary: { template: 'systems/daggerheart/templates/sheets/rollTable/summary.hbs' },
footer
};
}
static PARTS = DhRollTableSheet.buildParts();
async _preRender(context, options) {
await super._preRender(context, options);
if (!options.internalRefresh)
this.daggerheartFlag = new game.system.api.data.DhRollTable(this.document.flags.daggerheart);
}
/* root PART has a blank element on _attachPartListeners, so it cannot be used to set the eventListeners for the view mode */
async _onRender(context, options) {
super._onRender(context, options);
for (const element of this.element.querySelectorAll('.system-update-field'))
element.addEventListener('change', this.updateSystemField.bind(this));
}
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
switch (partId) {
case 'sheet':
context.altFormula = this.daggerheartFlag.altFormula;
context.usesAltFormula = Object.keys(this.daggerheartFlag.altFormula).length > 0;
context.altFormulaOptions = {
'': { name: this.daggerheartFlag.formulaName },
...this.daggerheartFlag.altFormula
};
context.activeAltFormula = this.daggerheartFlag.activeAltFormula;
context.selectedFormula = this.daggerheartFlag.getActiveFormula(this.document.formula);
context.results = this.getExtendedResults(context.results);
break;
case 'header':
context.altFormula = this.daggerheartFlag.altFormula;
context.usesAltFormula = Object.keys(this.daggerheartFlag.altFormula).length > 0;
context.altFormulaOptions = {
'': { name: this.daggerheartFlag.formulaName },
...this.daggerheartFlag.altFormula
};
context.activeAltFormula = this.daggerheartFlag.activeAltFormula;
break;
case 'summary':
context.systemFields = this.daggerheartFlag.schema.fields;
context.altFormula = this.daggerheartFlag.altFormula;
context.formulaName = this.daggerheartFlag.formulaName;
break;
case 'results':
context.results = this.getExtendedResults(context.results);
break;
}
return context;
}
getExtendedResults(results) {
const bodyDarkMode = document.body.classList.contains('theme-dark');
const elementLightMode = this.element.classList.contains('theme-light');
const elementDarkMode = this.element.classList.contains('theme-dark');
const isDarkMode = elementDarkMode || (!elementLightMode && bodyDarkMode);
return results.map(x => ({
...x,
displayImg: isDarkMode && x.img === 'icons/svg/d20-black.svg' ? 'icons/svg/d20.svg' : x.img
}));
}
/* -------------------------------------------- */
/* Flag SystemData update methods */
/* -------------------------------------------- */
async updateSystemField(event) {
const { dataset, value } = event.target;
await this.daggerheartFlag.updateSource({ [dataset.path]: value });
this.render({ internalRefresh: true });
}
getSystemFlagUpdate() {
const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce(
(acc, formulaKey) => {
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[`-=${formulaKey}`] = null;
return acc;
},
{ altFormula: {} }
);
return { ['flags.daggerheart']: foundry.utils.mergeObject(this.daggerheartFlag.toObject(), deleteUpdate) };
}
static async #addFormula() {
await this.daggerheartFlag.updateSource({
[`altFormula.${foundry.utils.randomID()}`]: game.system.api.data.DhRollTable.getDefaultFormula()
});
this.render({ internalRefresh: true });
}
static async #removeFormula(_event, target) {
await this.daggerheartFlag.updateSource({
[`altFormula.-=${target.dataset.key}`]: null
});
this.render({ internalRefresh: true });
}
/* -------------------------------------------- */
/* Extended RollTable methods */
/* -------------------------------------------- */
/**
* Alternate between view and edit modes.
* @this {RollTableSheet}
* @type {ApplicationClickAction}
*/
static async #onChangeMode() {
this.mode = this.isEditMode ? 'view' : 'edit';
await this.document.update(this.getSystemFlagUpdate());
await this.render({ internalRefresh: true });
}
/** @inheritdoc */
async _processSubmitData(event, form, submitData, options) {
/* RollTable sends an empty dummy event when swapping from view/edit first time */
if (Object.keys(submitData).length) {
if (!submitData.flags) submitData.flags = { daggerheart: {} };
submitData.flags.daggerheart = this.getSystemFlagUpdate();
}
super._processSubmitData(event, form, submitData, options);
}
/** @inheritdoc */
static async #onResetResults() {
await this.document.update(this.getSystemFlagUpdate());
await this.document.resetResults();
}
/**
* Roll and draw a TableResult.
* @this {RollTableSheet}
* @type {ApplicationClickAction}
*/
static async #onDrawResult(_event, button) {
if (this.form) await this.submit({ operation: { render: false } });
button.disabled = true;
const table = this.document;
await this.document.update(this.getSystemFlagUpdate());
/* Sending in the currently selectd activeFormula to table.roll to use as the formula */
const selectedFormula = this.daggerheartFlag.getActiveFormula(this.document.formula);
const tableRoll = await table.roll({ selectedFormula });
const draws = table.getResultsForRoll(tableRoll.roll.total);
if (draws.length > 0) {
if (game.settings.get('core', 'animateRollTable')) await this._animateRoll(draws);
await table.draw(tableRoll);
}
// Reenable the button if drawing with replacement since the draw won't trigger a sheet re-render
if (table.replacement) button.disabled = false;
}
}