152 - Improve Rest Options (#154)

* Fixed up downtime dialogs and data model

* Added homebrew settings without action handling for now

* Added NrChoices to homebrew
This commit is contained in:
WBHarry 2025-06-20 16:36:09 +02:00 committed by GitHub
parent f6e077b290
commit 3464717958
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 799 additions and 123 deletions

View file

@ -1,3 +1,5 @@
import { actionsTypes } from '../data/_module.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV2) {
@ -5,25 +7,25 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
super({});
this.actor = actor;
this.selectedActivity = null;
this.shortrest = shortrest;
this.customActivity = SYSTEM.GENERAL.downtime.custom;
const options = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew).restMoves;
this.moveData = shortrest ? options.shortRest : options.longRest;
}
get title() {
return `${this.actor.name} - ${this.shortrest ? 'Short Rest' : 'Long Rest'}`;
return '';
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'views', 'downtime'],
position: { width: 800, height: 'auto' },
position: { width: 680, height: 'auto' },
actions: {
selectActivity: this.selectActivity,
selectMove: this.selectMove,
takeDowntime: this.takeDowntime
},
form: { handler: this.updateData, submitOnChange: true }
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
};
static PARTS = {
@ -33,51 +35,63 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
}
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement
.querySelectorAll('.activity-image')
.forEach(element => element.addEventListener('contextmenu', this.deselectMove.bind(this)));
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.selectedActivity = this.selectedActivity;
context.options = this.shortrest ? SYSTEM.GENERAL.downtime.shortRest : SYSTEM.GENERAL.downtime.longRest;
context.customActivity = this.customActivity;
context.disabledDowntime =
!this.selectedActivity ||
(this.selectedActivity.id === this.customActivity.id &&
(!this.customActivity.name || !this.customActivity.description));
context.moveData = this.moveData;
context.nrCurrentChoices = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
context.disabledDowntime = context.nrCurrentChoices < context.moveData.nrChoices;
return context;
}
static selectActivity(_, button) {
const activity = button.dataset.activity;
this.selectedActivity =
activity === this.customActivity.id
? this.customActivity
: this.shortrest
? SYSTEM.GENERAL.downtime.shortRest[activity]
: SYSTEM.GENERAL.downtime.longRest[activity];
static selectMove(_, button) {
const nrSelected = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
if (nrSelected === this.moveData.nrChoices) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.Downtime.Notifications.NoMoreMoves'));
return;
}
const move = button.dataset.move;
this.moveData.moves[move].selected = this.moveData.moves[move].selected
? this.moveData.moves[move].selected + 1
: 1;
this.render();
}
deselectMove(event) {
const move = event.currentTarget.dataset.move;
this.moveData.moves[move].selected = this.moveData.moves[move].selected
? this.moveData.moves[move].selected - 1
: 0;
this.render();
}
static async takeDowntime() {
const refreshedFeatures = this.shortrest
? this.actor.system.refreshableFeatures.shortRest
: [...this.actor.system.refreshableFeatures.shortRest, ...this.actor.system.refreshableFeatures.longRest];
for (var feature of refreshedFeatures) {
await feature.system.refresh();
}
const moves = Object.values(this.moveData.moves).filter(x => x.selected);
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
system: {
moves: moves,
actor: this.actor.uuid
},
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/downtime.hbs',
{
player: this.actor.name,
title: game.i18n.localize(this.selectedActivity.name),
img: this.selectedActivity.img,
description: game.i18n.localize(this.selectedActivity.description),
refreshedFeatures: refreshedFeatures
title: `${this.actor.name} - ${game.i18n.localize(`DAGGERHEART.Downtime.${this.shortRest ? 'ShortRest' : 'LongRest'}.title`)}`,
moves: moves
}
)
});

View file

@ -0,0 +1,144 @@
import { actionsTypes } from '../../../data/_module.mjs';
import DHActionConfig from '../../config/Action.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhSettingsActionView extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(resolve, reject, title, name, img, description, actions) {
super({});
this.resolve = resolve;
this.reject = reject;
this.viewTitle = title;
this.name = name;
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: '400', 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.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, img, description } = foundry.utils.expandObject(formData.object);
this.name = name;
this.description = description;
this.render();
}
static async saveForm(event) {
this.resolve({
name: this.name,
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 FilePicker({
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/views/actionType.hbs',
{ types: SYSTEM.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(SYSTEM.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,8 +1,9 @@
import { DhHomebrew } from '../../data/settings/_module.mjs';
import DhSettingsActionView from './components/settingsActionsView.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhAutomationSettings extends HandlebarsApplicationMixin(ApplicationV2) {
export default class DhHomebrewSettings extends HandlebarsApplicationMixin(ApplicationV2) {
constructor() {
super({});
@ -19,7 +20,10 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
classes: ['daggerheart', 'setting', 'dh-style'],
position: { width: '600', height: 'auto' },
actions: {
reset: this.reset,
addItem: this.addItem,
editItem: this.editItem,
removeItem: this.removeItem,
resetMoves: this.resetMoves,
save: this.save
},
form: { handler: this.updateData, submitOnChange: true }
@ -48,8 +52,101 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
this.render();
}
static async reset() {
this.settings = new DhHomebrew();
static async addItem(_, target) {
await this.settings.updateSource({
[`restMoves.${target.dataset.type}.moves.${foundry.utils.randomID()}`]: {
name: game.i18n.localize('DAGGERHEART.Settings.Homebrew.NewDowntimeMove'),
img: 'icons/magic/life/cross-worn-green.webp',
description: '',
actions: []
}
});
this.render();
}
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.img,
move.description,
move.actions
).render(true);
}).then(data => this.updateAction.bind(this)(data, target.dataset.type, target.dataset.id));
}
async updateAction(data, type, id) {
await this.settings.updateSource({
[`restMoves.${type}.moves.${id}`]: {
name: data.name,
img: data.img,
description: data.description
}
});
this.render();
}
static async removeItem(_, target) {
await this.settings.updateSource({
[`restMoves.${target.dataset.type}.moves.-=${target.dataset.id}`]: null
});
this.render();
}
static async resetMoves(_, target) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.Settings.Homebrew.ResetMovesTitle', {
type: game.i18n.localize(
`DAGGERHEART.Downtime.${target.dataset.type === 'shortRest' ? 'ShortRest' : 'LongRest'}.title`
)
})
},
content: game.i18n.localize('DAGGERHEART.Settings.Homebrew.ResetMovesText')
});
if (!confirmed) return;
const fields = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew).schema.fields;
const removeUpdate = Object.keys(this.settings.restMoves[target.dataset.type].moves).reduce((acc, key) => {
acc[`-=${key}`] = null;
return acc;
}, {});
const updateBase =
target.dataset.type === 'shortRest'
? fields.restMoves.fields.shortRest.fields
: fields.restMoves.fields.longRest.fields;
const update = {
nrChoices: updateBase.nrChoices.initial,
moves: Object.keys(updateBase.moves.initial).reduce((acc, key) => {
const move = updateBase.moves.initial[key];
acc[key] = {
...move,
name: game.i18n.localize(move.name),
description: game.i18n.localize(move.description)
};
return acc;
}, {})
};
await this.settings.updateSource({
[`restMoves.${target.dataset.type}`]: {
...update,
moves: {
...removeUpdate,
...update.moves
}
}
});
this.render();
}