Fixed up downtime dialogs and data model

This commit is contained in:
WBHarry 2025-06-19 17:11:01 +02:00
parent f6e077b290
commit e38ebfab29
10 changed files with 284 additions and 118 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

@ -101,65 +101,99 @@ export const conditions = {
}
};
export const downtime = {
shortRest: {
export const defaultRestOptions = {
shortRest: () => ({
tendToWounds: {
id: 'tendToWounds',
name: 'DAGGERHEART.Downtime.TendToWounds.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.TendToWounds.Name'),
img: 'icons/magic/life/cross-worn-green.webp',
description: 'DAGGERHEART.Downtime.TendToWounds.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.TendToWounds.Description'),
actions: [
{
type: 'healing',
name: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.TendToWounds.Name'),
img: 'icons/magic/life/cross-worn-green.webp',
actionType: 'action',
healing: {
type: 'health',
value: {
custom: {
enabled: true,
formula: '1d4 + 1' // should be 1d4 + {tier}. How to use the roll param?
}
}
}
}
]
},
clearStress: {
id: 'clearStress',
name: 'DAGGERHEART.Downtime.ClearStress.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.ClearStress.Name'),
img: 'icons/magic/perception/eye-ringed-green.webp',
description: 'DAGGERHEART.Downtime.ClearStress.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.ClearStress.Description'),
actions: [
{
type: 'healing',
name: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.ClearStress.Name'),
img: 'icons/magic/perception/eye-ringed-green.webp',
actionType: 'action',
healing: {
type: 'stress',
value: {
custom: {
enabled: true,
formula: '1d4 + 1' // should be 1d4 + {tier}. How to use the roll param?
}
}
}
}
]
},
repairArmor: {
id: 'repairArmor',
name: 'DAGGERHEART.Downtime.RepairArmor.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.RepairArmor.Name'),
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
description: 'DAGGERHEART.Downtime.RepairArmor.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.RepairArmor.Description')
},
prepare: {
id: 'prepare',
name: 'DAGGERHEART.Downtime.Prepare.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.Prepare.Name'),
img: 'icons/skills/trades/academics-merchant-scribe.webp',
description: 'DAGGERHEART.Downtime.Prepare.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.ShortRest.Prepare.Description')
}
},
longRest: {
}),
longRest: () => ({
tendToWounds: {
id: 'tendToWounds',
name: 'DAGGERHEART.Downtime.TendToWounds.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.LongRest.TendToWounds.Name'),
img: 'icons/magic/life/cross-worn-green.webp',
description: 'DAGGERHEART.Downtime.TendToWounds.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.LongRest.TendToWounds.Description')
},
clearStress: {
id: 'clearStress',
name: 'DAGGERHEART.Downtime.ClearStress.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.LongRest.ClearStress.Name'),
img: 'icons/magic/perception/eye-ringed-green.webp',
description: 'DAGGERHEART.Downtime.ClearStress.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.LongRest.ClearStress.Description')
},
repairArmor: {
id: 'repairArmor',
name: 'DAGGERHEART.Downtime.RepairArmor.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.LongRest.RepairArmor.Name'),
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
description: 'DAGGERHEART.Downtime.RepairArmor.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.LongRest.RepairArmor.Description')
},
prepare: {
id: 'prepare',
name: 'DAGGERHEART.Downtime.Prepare.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.LongRest.Prepare.Name'),
img: 'icons/skills/trades/academics-merchant-scribe.webp',
description: 'DAGGERHEART.Downtime.Prepare.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.LongRest.Prepare.Description')
},
workOnAProject: {
id: 'workOnAProject',
name: 'DAGGERHEART.Downtime.WorkOnAProject.Name',
name: game.i18n.localize('DAGGERHEART.Downtime.LongRest.WorkOnAProject.Name'),
img: 'icons/skills/social/thumbsup-approval-like.webp',
description: 'DAGGERHEART.Downtime.WorkOnAProject.Description'
description: game.i18n.localize('DAGGERHEART.Downtime.LongRest.WorkOnAProject.Description')
}
},
}),
custom: {
id: 'customActivity',
name: '',

View file

@ -87,6 +87,14 @@ export default class DhCharacter extends BaseDataActor {
};
}
get tier() {
return this.levelData.level.current === 1
? 1
: Object.values(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers).tiers).find(
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
).tier;
}
get ancestry() {
return this.parent.items.find(x => x.type === 'ancestry') ?? null;
}
@ -134,19 +142,6 @@ export default class DhCharacter extends BaseDataActor {
: null;
}
get refreshableFeatures() {
return this.parent.items.reduce(
(acc, x) => {
if (x.type === 'feature' && x.system.refreshData?.type === 'feature' && x.system.refreshData?.type) {
acc[x.system.refreshData.type].push(x);
}
return acc;
},
{ shortRest: [], longRest: [] }
);
}
static async unequipBeforeEquip(itemToEquip) {
const primary = this.primaryWeapon,
secondary = this.secondaryWeapon;
@ -242,6 +237,14 @@ export default class DhCharacter extends BaseDataActor {
this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus;
this.proficiency.total = this.proficiency.value + this.proficiency.bonus;
}
getRollData() {
const data = super.getRollData();
return {
...data,
tier: this.tier
};
}
}
class DhPCLevelData extends foundry.abstract.DataModel {

View file

@ -1,3 +1,5 @@
import { defaultRestOptions } from '../../config/generalConfig.mjs';
export default class DhHomebrew extends foundry.abstract.DataModel {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Settings.Homebrew']; // Doesn't work for some reason
@ -13,6 +15,40 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
}),
traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), {
initial: () => [2, 1, 1, 0, 0, -1]
}),
restMoves: new fields.SchemaField({
longRest: new fields.SchemaField({
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
moves: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new fields.HTMLField(),
actions: new fields.ArrayField(new fields.ObjectField())
}),
{ initial: defaultRestOptions.longRest() }
)
}),
shortRest: new fields.SchemaField({
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
moves: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new fields.HTMLField(),
actions: new fields.ArrayField(new fields.ObjectField())
}),
{ initial: defaultRestOptions.shortRest() }
)
})
})
};
}

View file

@ -1,3 +1,5 @@
import { actionsTypes } from '../data/_module.mjs';
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
constructor() {
super();
@ -38,6 +40,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', event => this.abilityUseButton.bind(this)(event, data.message))
);
html.querySelectorAll('.action-use-button').forEach(element =>
element.addEventListener('click', event => this.actionUseButton.bind(this)(event, data.message))
);
};
setupHooks() {
@ -137,4 +142,16 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
const actor = game.actors.get(message.system.origin);
await actor.useAction(action);
};
actionUseButton = async (_, message) => {
const parent = await foundry.utils.fromUuid(message.system.actor);
const testAction = Object.values(message.system.moves)[0].actions[0];
const cls = actionsTypes[testAction.type];
const action = new cls(
{ ...testAction, _id: foundry.utils.randomID(), name: game.i18n.localize(testAction.name) },
{ parent: parent }
);
action.use();
};
}