[Feature] Downtime Improvements (#411)

* Initial

* Fixed dialog again

* Fix healing targeted resource in setting

* Removed unused templates

---------

Co-authored-by: Dapoolp <elcatnet@gmail.com>
This commit is contained in:
WBHarry 2025-07-25 21:31:05 +02:00 committed by GitHub
parent 3f95740b7a
commit 62b9a8fbee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 538 additions and 457 deletions

View file

@ -64,8 +64,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
context.selectedActivity = this.selectedActivity;
context.moveData = this.moveData;
const shortRestMovesSelected = this.#nrSelectedMoves('shortRest');
const longRestMovesSelected = this.#nrSelectedMoves('longRest');
const shortRestMovesSelected = this.nrSelectedMoves('shortRest');
const longRestMovesSelected = this.nrSelectedMoves('longRest');
context.nrChoices = {
...this.nrChoices,
shortRest: {
@ -89,7 +89,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
static selectMove(_, target) {
const { category, move } = target.dataset;
const nrSelected = this.#nrSelectedMoves(category);
const nrSelected = this.nrSelectedMoves(category);
if (nrSelected + this.nrChoices[category].taken >= this.nrChoices[category].max) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noMoreMoves'));
@ -176,7 +176,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
this.render();
}
#nrSelectedMoves(category) {
nrSelectedMoves(category) {
return Object.values(this.moveData[category].moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
}
}

View file

@ -36,10 +36,37 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
}
};
/** @inheritdoc */
static TABS = {
diceSoNice: {
tabs: [
{ id: 'hope', label: 'DAGGERHEART.GENERAL.hope' },
{ id: 'fear', label: 'DAGGERHEART.GENERAL.fear' },
{ id: 'advantage', label: 'DAGGERHEART.GENERAL.Advantage.full' },
{ id: 'disadvantage', label: 'DAGGERHEART.GENERAL.Advantage.full' }
],
initial: 'hope'
}
};
changeTab(tab, group, options) {
super.changeTab(tab, group, options);
this.render();
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.settingFields = this.settings;
context.diceSoNiceTextures = game.dice3d?.exports?.TEXTURELIST ?? {};
context.diceSoNiceColorsets = game.dice3d?.exports?.COLORSETS ?? {};
context.diceTab = {
key: this.tabGroups.diceSoNice,
source: this.settings._source.diceSoNice[this.tabGroups.diceSoNice],
fields: this.settings.schema.fields.diceSoNice.fields[this.tabGroups.diceSoNice].fields
};
return context;
}
@ -65,4 +92,13 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
this.close();
}
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
return tabs;
}
}

View file

@ -1,148 +0,0 @@
import { actionsTypes } from '../../../data/action/_module.mjs';
import DHActionConfig from '../../sheets-configs/action-config.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhSettingsActionView extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(resolve, reject, title, name, icon, img, description, actions) {
super({});
this.resolve = resolve;
this.reject = reject;
this.viewTitle = title;
this.name = name;
this.icon = icon;
this.img = img;
this.description = description;
this.actions = actions;
}
get title() {
return this.viewTitle;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'setting', 'dh-style'],
position: { width: 440, height: 'auto' },
actions: {
editImage: this.onEditImage,
addItem: this.addItem,
editItem: this.editItem,
removeItem: this.removeItem,
resetMoves: this.resetMoves,
saveForm: this.saveForm
},
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/settings/components/action-view-header.hbs' },
main: {
template: 'systems/daggerheart/templates/settings/components/action-view.hbs'
},
footer: { template: 'systems/daggerheart/templates/settings/components/action-view-footer.hbs' }
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.name = this.name;
context.icon = this.icon;
context.img = this.img;
context.description = this.description;
context.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(context.description);
context.actions = this.actions;
return context;
}
static async updateData(event, element, formData) {
const { name, icon, description } = foundry.utils.expandObject(formData.object);
this.name = name;
this.icon = icon;
this.description = description;
this.render();
}
static async saveForm(event) {
this.resolve({
name: this.name,
icon: this.icon,
img: this.img,
description: this.description,
actions: this.actions
});
this.close(true);
}
static close(fromSave) {
if (!fromSave) {
this.reject();
}
super.close();
}
static onEditImage() {
const fp = new foundry.applications.apps.FilePicker.implementation({
current: this.img,
type: 'image',
callback: async path => {
this.img = path;
this.render();
},
top: this.position.top + 40,
left: this.position.left + 10
});
return fp.browse();
}
async selectActionType() {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/actionTypes/actionType.hbs',
{ types: CONFIG.DH.ACTIONS.actionTypes }
),
title = 'Select Action Type',
type = 'form',
data = {};
return Dialog.prompt({
title,
label: title,
content,
type,
callback: html => {
const form = html[0].querySelector('form'),
fd = new foundry.applications.ux.FormDataExtended(form);
foundry.utils.mergeObject(data, fd.object, { inplace: true });
return data;
},
rejectClose: false
});
}
static async addItem() {
const actionType = await this.selectActionType();
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls({
_id: foundry.utils.randomID(),
type: actionType.type,
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType.type].name),
...cls.getSourceConfig(this.document)
});
this.actions.push(action);
this.render();
}
static async editItem(_, button) {
await new DHActionConfig(this.actions[button.dataset.id]).render(true);
}
static removeItem(event, button) {
this.actions = this.actions.filter((_, index) => index !== Number.parseInt(button.dataset.id));
this.render();
}
static resetMoves() {}
}

View file

@ -1,6 +1,4 @@
import { DhHomebrew } from '../../data/settings/_module.mjs';
import DhSettingsActionView from './components/settingsActionsView.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhHomebrewSettings extends HandlebarsApplicationMixin(ApplicationV2) {
@ -73,23 +71,21 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
static async editItem(_, target) {
const move = this.settings.restMoves[target.dataset.type].moves[target.dataset.id];
new Promise((resolve, reject) => {
new DhSettingsActionView(
resolve,
reject,
game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves'),
move.name,
move.icon,
move.img,
move.description,
move.actions
).render(true);
}).then(data => this.updateAction.bind(this)(data, target.dataset.type, target.dataset.id));
const path = `restMoves.${target.dataset.type}.moves.${target.dataset.id}`;
const editedMove = await game.system.api.applications.sheetConfigs.DowntimeConfig.configure(
move,
path,
this.settings
);
if (!editedMove) return;
await this.updateAction.bind(this)(editedMove, target.dataset.type, target.dataset.id);
}
async updateAction(data, type, id) {
await this.settings.updateSource({
[`restMoves.${type}.moves.${id}`]: {
actions: data.actions,
name: data.name,
icon: data.icon,
img: data.img,
@ -139,7 +135,11 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
acc[key] = {
...move,
name: game.i18n.localize(move.name),
description: game.i18n.localize(move.description)
description: game.i18n.localize(move.description),
actions: move.actions.map(action => ({
...action,
name: game.i18n.localize(action.name)
}))
};
return acc;

View file

@ -1,6 +1,7 @@
export { default as ActionConfig } from './action-config.mjs';
export { default as AdversarySettings } from './adversary-settings.mjs';
export { default as CompanionSettings } from './companion-settings.mjs';
export { default as DowntimeConfig } from './downtimeConfig.mjs';
export { default as EnvironmentSettings } from './environment-settings.mjs';
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
export { default as DhTokenConfig } from './token-config.mjs';

View file

@ -2,10 +2,11 @@ import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
const { ApplicationV2 } = foundry.applications.api;
export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
constructor(action) {
constructor(action, sheetUpdate) {
super({});
this.action = action;
this.sheetUpdate = sheetUpdate;
this.openSection = null;
}
@ -171,6 +172,8 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
const submitData = this._prepareSubmitData(event, formData),
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
this.action = await this.action.update(data);
this.sheetUpdate?.(this.action);
this.render();
}
@ -195,7 +198,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
if (!this.action.damage.parts) return;
const data = this.action.toObject(),
part = {};
if(this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
data.damage.parts.push(part);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}

View file

@ -0,0 +1,162 @@
import { actionsTypes } from '../../data/action/_module.mjs';
import DHActionConfig from './action-config.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DowntimeConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(move, movePath, settings, options) {
super(options);
this.move = move;
this.movePath = movePath;
this.actionsPath = `${movePath}.actions`;
this.settings = settings;
}
get title() {
return game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves');
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'setting', 'dh-style'],
position: { width: 440, height: 'auto' },
window: {
icon: 'fa-solid fa-gears'
},
actions: {
editImage: this.onEditImage,
addItem: this.addItem,
editItem: this.editItem,
removeItem: this.removeItem,
resetMoves: this.resetMoves,
saveForm: this.saveForm
},
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/settings/downtime-config/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
main: { template: 'systems/daggerheart/templates/settings/downtime-config/main.hbs' },
actions: { template: 'systems/daggerheart/templates/settings/downtime-config/actions.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/downtime-config/footer.hbs' }
};
/** @inheritdoc */
static TABS = {
primary: {
tabs: [{ id: 'main' }, { id: 'actions' }],
initial: 'main',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.move = this.move;
context.move.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(
context.move.description
);
return context;
}
static async updateData(event, element, formData) {
const data = foundry.utils.expandObject(formData.object);
foundry.utils.mergeObject(this.move, data);
this.render();
}
static async saveForm() {
this.close({ submitted: true });
}
static onEditImage() {
const fp = new foundry.applications.apps.FilePicker.implementation({
current: this.img,
type: 'image',
callback: async path => {
this.move.img = path;
this.render();
},
top: this.position.top + 40,
left: this.position.left + 10
});
return fp.browse();
}
async selectActionType() {
return (
(await foundry.applications.api.DialogV2.input({
window: { title: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') },
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/actionTypes/actionType.hbs',
{ types: CONFIG.DH.ACTIONS.actionTypes }
),
ok: {
label: game.i18n.format('DOCUMENT.Create', {
type: game.i18n.localize('DAGGERHEART.GENERAL.Action.single')
})
}
})) ?? {}
);
}
static async addItem() {
const { type: actionType } = await this.selectActionType();
if (!actionType) return;
const cls = actionsTypes[actionType] ?? actionsTypes.attack,
action = new cls(
{
type: actionType,
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
img: 'icons/magic/life/cross-worn-green.webp',
actionType: 'action',
systemPath: this.actionsPath
},
{
parent: this.settings
}
);
await this.settings.updateSource({ [`${this.actionsPath}.${action.id}`]: action });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}
static async editItem(_, target) {
const actionId = target.dataset.id;
const action = this.move.actions.get(actionId);
await new DHActionConfig(action, async updatedMove => {
await this.settings.updateSource({ [`${this.actionsPath}.${actionId}`]: updatedMove });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}).render(true);
}
static async removeItem(_, target) {
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}
static resetMoves() {}
/** @override */
_onClose(options = {}) {
if (!options.submitted) this.move = null;
}
static async configure(move, movePath, settings, options = {}) {
return new Promise(resolve => {
const app = new this(move, movePath, settings, options);
app.addEventListener('close', () => resolve(app.move), { once: true });
app.render({ force: true });
});
}
}