Added so that beastform actions can have trait bonuses to be selected while transforming

This commit is contained in:
WBHarry 2026-03-25 22:22:49 +01:00
parent a4fff56461
commit 9ca1c85dad
8 changed files with 177 additions and 17 deletions

View file

@ -87,9 +87,14 @@
},
"Config": {
"beastform": {
"exact": "Beastform Max Tier",
"exactHint": "The Character's Tier is used if empty",
"label": "Beastform"
"exact": { "label": "Beastform Max Tier", "hint": "The Character's Tier is used if empty" },
"modifications": {
"traitBonuses": {
"label": { "single": "Trait Bonus", "plural": "Trait Bonuses" },
"hint": "Pick bonuses you apply to freely chosen traits at the time of transforming",
"bonus": "Bonus Amount"
}
}
},
"countdown": {
"defaultOwnership": "Default Ownership",

View file

@ -10,6 +10,12 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
this.selected = null;
this.evolved = { form: null };
this.hybrid = { forms: {}, advantages: {}, features: {} };
this.modifications = {
traitBonuses: configData.modifications.traitBonuses.map(x => ({
trait: null,
bonus: x.bonus
}))
};
this._dragDrop = this._createDragDropHandlers();
}
@ -28,6 +34,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
selectBeastform: this.selectBeastform,
toggleHybridFeature: this.toggleHybridFeature,
toggleHybridAdvantage: this.toggleHybridAdvantage,
toggleTraitBonus: this.toggleTraitBonus,
submitBeastform: this.submitBeastform
},
form: {
@ -48,6 +55,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
modifications: { template: 'systems/daggerheart/templates/dialogs/beastform/modifications.hbs' },
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
};
@ -146,6 +154,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
{}
);
context.modifications = this.modifications;
context.traits = CONFIG.DH.ACTOR.abilities;
context.tier = beastformTiers[this.tabGroups.primary];
context.tierKey = this.tabGroups.primary;
@ -156,11 +167,14 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
canSubmit() {
if (this.selected) {
const modificationsFinished =
!this.modifications.traitBonuses.length || this.modifications.traitBonuses.every(x => x.trait);
switch (this.selected.system.beastformType) {
case 'normal':
return true;
return modificationsFinished;
case 'evolved':
return this.evolved.form;
return modificationsFinished && this.evolved.form;
case 'hybrid':
const selectedAdvantages = Object.values(this.hybrid.advantages).reduce(
(acc, form) => acc + Object.values(form).length,
@ -173,7 +187,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
const advantagesSelected = selectedAdvantages === this.selected.system.hybrid.advantages;
const featuresSelected = selectedFeatures === this.selected.system.hybrid.features;
return advantagesSelected && featuresSelected;
return modificationsFinished && advantagesSelected && featuresSelected;
}
}
@ -261,6 +275,13 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
this.render();
}
static toggleTraitBonus(_, button) {
const { index, trait } = button.dataset;
this.modifications.traitBonuses[index].trait =
this.modifications.traitBonuses[index].trait === trait ? null : trait;
this.render();
}
static async submitBeastform() {
await this.close({ submitted: true });
}
@ -292,6 +313,24 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
}
}
const beastformEffect = selected.effects.find(x => x.type === 'beastform');
while (app.modifications.traitBonuses.length) {
const traitBonus = app.modifications.traitBonuses.pop();
const existingChange = beastformEffect.changes.find(
x => x.key === `system.traits.${traitBonus.trait}.value`
);
if (existingChange) {
existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus;
} else {
beastformEffect.changes.push({
key: `system.traits.${traitBonus.trait}.value`,
mode: 2,
priority: null,
value: traitBonus.bonus
});
}
}
resolve({
selected: selected,
evolved: { ...app.evolved, form: evolved },

View file

@ -35,7 +35,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
editDoc: this.editDoc,
addTrigger: this.addTrigger,
removeTrigger: this.removeTrigger,
expandTrigger: this.expandTrigger
expandTrigger: this.expandTrigger,
addBeastformTraitBonus: this.addBeastformTraitBonus,
removeBeastformTraitBonus: this.removeBeastformTraitBonus
},
form: {
handler: this.updateForm,
@ -360,6 +362,21 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
}
}
static async addBeastformTraitBonus() {
const data = this.action.toObject();
data.beastform.modifications.traitBonuses = [
...data.beastform.modifications.traitBonuses,
this.action.schema.fields.beastform.fields.modifications.fields.traitBonuses.element.getInitialValue()
];
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static async removeBeastformTraitBonus(_event, button) {
const data = this.action.toObject();
data.beastform.modifications.traitBonuses.splice(button.dataset.index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
updateSummonCount(event) {
event.stopPropagation();
const wrapper = event.target.closest('.summon-count-wrapper');

View file

@ -28,8 +28,21 @@ export default class BeastformField extends fields.SchemaField {
{ 1: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') }
);
},
hint: 'DAGGERHEART.ACTIONS.Config.beastform.exactHint'
label: 'DAGGERHEART.ACTIONS.Config.beastform.exact.label',
hint: 'DAGGERHEART.ACTIONS.Config.beastform.exact.hint'
})
}),
modifications: new fields.SchemaField({
traitBonuses: new fields.ArrayField(
new fields.SchemaField({
bonus: new fields.NumberField({
integer: true,
initial: 1,
min: 1,
label: 'DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.bonus'
})
})
)
})
};
super(beastformFields, options, context);
@ -66,15 +79,9 @@ export default class BeastformField extends fields.SchemaField {
) ?? 1;
config.tierLimit = this.beastform.tierAccess.exact ?? actorTier;
config.modifications = this.beastform.modifications;
}
/**
* TODO by Harry
* @param {*} selectedForm
* @param {*} evolvedData
* @param {*} hybridData
* @returns
*/
static async transform(selectedForm, evolvedData, hybridData) {
const formData = evolvedData?.form ?? selectedForm;
const beastformEffect = formData.effects.find(x => x.type === 'beastform');

View file

@ -204,6 +204,44 @@
}
}
.modifications-container {
display: flex;
flex-direction: column;
gap: 16px;
.trait-bonuses-container {
display: flex;
flex-direction: column;
gap: 8px;
.bonus-separator {
background: light-dark(@dark-blue, @golden);
mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%);
height: 2px;
width: calc(100% - 10px);
}
.trait-bonus-container {
display: flex;
gap: 4px;
.trait-card {
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
padding: 2px;
opacity: 0.4;
flex: 1;
white-space: nowrap;
text-align: center;
&.selected {
opacity: 1;
}
}
}
}
}
footer {
margin-top: 8px;
display: flex;

View file

@ -133,4 +133,18 @@
height: 300px;
}
}
.deletable-row {
display: flex;
align-items: end;
gap: 8px;
input {
flex: 1;
}
a {
padding-bottom: 7px;
}
}
}

View file

@ -1,4 +1,16 @@
{{formGroup fields.tierAccess.fields.exact value=source.tierAccess.exact name="beastform.tierAccess.exact" labelAttr="label" valueAttr="key" localize=true blank=""}}
<fieldset>
<legend>{{localize "DAGGERHEART.ACTIONS.Config.beastform.label"}}</legend>
{{formGroup fields.tierAccess.fields.exact value=source.tierAccess.exact name="beastform.tierAccess.exact" labelAttr="label" valueAttr="key" localize=true blank=""}}
<legend>{{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.plural"}} <a data-action="addBeastformTraitBonus"><i class="fa-solid fa-plus"></i></a></legend>
{{#if source.modifications.traitBonuses.length}}
{{#each source.modifications.traitBonuses as |traitBonus index|}}
<div class="deletable-row">
{{formGroup ../fields.modifications.fields.traitBonuses.element.fields.bonus value=traitBonus.bonus name=(concat "beastform.modifications.traitBonuses." index ".bonus") localize=true}}
<a data-action="removeBeastformTraitBonus" data-index="{{index}}"><i class="fa-solid fa-trash"></i></a>
</div>
{{/each}}
{{else}}
<span class="hint">{{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.hint"}}</span>
{{/if}}
</fieldset>

View file

@ -0,0 +1,28 @@
<div class="modifications-container">
{{#if modifications.traitBonuses.length}}
<fieldset>
<legend>
{{#if (gt modifications.traitBonuses.length 1)}}
{{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.plural"}}
{{else}}
{{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.single"}}
{{/if}}
</legend>
<div class="trait-bonuses-container">
{{#each modifications.traitBonuses as |traitBonus index|}}
<div class="trait-bonus-container">
{{#each @root.traits as |trait|}}
<a class="trait-card {{#if (eq trait.id traitBonus.trait)}}selected{{/if}}"
data-action="toggleTraitBonus" data-index="{{index}}" data-trait="{{trait.id}}"
>
{{localize trait.label}} +{{traitBonus.bonus}}
</a>
{{/each}}
</div>
{{#unless @last}}<div class="bonus-separator"></div>{{/unless}}
{{/each}}
</div>
</fieldset>
{{/if}}
</div>