add reloading feature

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
This commit is contained in:
Gergely Brautigam 2026-07-14 20:24:19 +02:00
parent e9e8d2bd59
commit 9e4d8d637a
No known key found for this signature in database
GPG key ID: 3763870D61A6E2CA
8 changed files with 91 additions and 16 deletions

View file

@ -144,6 +144,10 @@
"resultBased": { "resultBased": {
"label": "Formula based on Hope/Fear result." "label": "Formula based on Hope/Fear result."
}, },
"fullRestore": {
"label": "Fully restore the resource.",
"inChatRoll": "Full"
},
"applyTo": { "applyTo": {
"label": "Targeted Resource" "label": "Targeted Resource"
}, },
@ -1279,6 +1283,11 @@
"name": "Fear", "name": "Fear",
"abbreviation": "FR", "abbreviation": "FR",
"inChatRoll": "Fear" "inChatRoll": "Fear"
},
"weaponResource": {
"name": "Weapon Resource",
"abbreviation": "WR",
"inChatRoll": "Reload"
} }
}, },
"ItemResourceProgression": { "ItemResourceProgression": {
@ -3090,6 +3099,8 @@
"applyHealing": "Apply Healing" "applyHealing": "Apply Healing"
}, },
"markResource": "Mark {quantity} {resource}", "markResource": "Mark {quantity} {resource}",
"reloadWeapon": "Reload {weapon}",
"restoreWeaponResource": "Restore {quantity} on {weapon}",
"refreshMessage": { "refreshMessage": {
"title": "Feature Refresh", "title": "Feature Refresh",
"header": "Refreshed" "header": "Refreshed"

View file

@ -191,6 +191,11 @@ export const healingTypes = {
id: 'fear', id: 'fear',
label: 'DAGGERHEART.CONFIG.HealingType.fear.name', label: 'DAGGERHEART.CONFIG.HealingType.fear.name',
abbreviation: 'DAGGERHEART.CONFIG.HealingType.fear.abbreviation' abbreviation: 'DAGGERHEART.CONFIG.HealingType.fear.abbreviation'
},
weaponResource: {
id: 'weaponResource',
label: 'DAGGERHEART.CONFIG.HealingType.weaponResource.name',
abbreviation: 'DAGGERHEART.CONFIG.HealingType.weaponResource.abbreviation'
} }
}; };

View file

@ -197,11 +197,29 @@ export default class CostField extends fields.ArrayField {
static getItemIdCostUpdate(r) { static getItemIdCostUpdate(r) {
switch (r.key) { switch (r.key) {
case CONFIG.DH.GENERAL.itemAbilityCosts.resource.id: case CONFIG.DH.GENERAL.itemAbilityCosts.resource.id: {
const resource = r.target.system.resource;
let max = Number(resource.max);
if (!Number.isFinite(max) && resource.max) {
try {
max = Roll.safeEval(Roll.replaceFormulaData(resource.max, r.target.getRollData()));
} catch {
max = null;
}
}
const hasMax = Number.isFinite(max) && max > 0;
if (r.clear) {
return { return {
path: 'system.resource.value', path: 'system.resource.value',
value: r.target.system.resource.value + r.value value: hasMax ? max : resource.value
}; };
}
const newValue = resource.value + r.value;
return {
path: 'system.resource.value',
value: Math.max(hasMax ? Math.min(newValue, max) : newValue, 0)
};
}
case CONFIG.DH.GENERAL.itemAbilityCosts.quantity.id: case CONFIG.DH.GENERAL.itemAbilityCosts.quantity.id:
return { return {
path: 'system.quantity', path: 'system.quantity',

View file

@ -45,9 +45,10 @@ export default class DamageField extends fields.SchemaField {
return; return;
let formulas = this.damage.parts.map(p => ({ let formulas = this.damage.parts.map(p => ({
formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor), formula: p.fullRestore ? '0' : DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor),
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type, damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
applyTo: p.applyTo applyTo: p.applyTo,
fullRestore: p.fullRestore
})); }));
if (!formulas.length) return false; if (!formulas.length) return false;
@ -67,6 +68,7 @@ export default class DamageField extends fields.SchemaField {
if (DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) if (DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id)
damageConfig.dialog.configure = false; damageConfig.dialog.configure = false;
if (formulas.every(f => f.fullRestore)) damageConfig.dialog.configure = false;
if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod; if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod;
damageConfig.source.message = messageId; damageConfig.source.message = messageId;
@ -297,6 +299,10 @@ export class DHResourceData extends foundry.abstract.DataModel {
initial: false, initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label' label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
}), }),
fullRestore: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.fullRestore.label'
}),
value: new fields.EmbeddedDataField(DHActionDiceData), value: new fields.EmbeddedDataField(DHActionDiceData),
valueAlt: new fields.EmbeddedDataField(DHActionDiceData) valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
}; };

View file

@ -766,17 +766,21 @@ export default class DhpActor extends Actor {
Object.entries(healings).forEach(([key, healing]) => { Object.entries(healings).forEach(([key, healing]) => {
healing.parts.forEach(part => { healing.parts.forEach(part => {
const update = updates.find(u => u.key === key); const update = updates.find(u => u.key === key);
if (update) update.value += part.total; if (update) {
else updates.push({ value: part.total, key }); update.value += part.total;
update.clear ||= !!part.fullRestore;
} else updates.push({ value: part.total, key, clear: !!part.fullRestore });
}); });
}); });
updates.forEach( updates.forEach(u => {
u => if (u.key === CONFIG.DH.GENERAL.healingTypes.weaponResource.id) return;
(u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false) u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false)
? u.value * -1 ? u.value * -1
: u.value) : u.value;
); });
this.convertResourceHealingToReload(updates);
await this.modifyResource(updates); await this.modifyResource(updates);
@ -802,7 +806,7 @@ export default class DhpActor extends Actor {
resources.forEach(r => { resources.forEach(r => {
if (r.itemId) { if (r.itemId) {
const { path, value } = game.system.api.fields.ActionFields.CostField.getItemIdCostUpdate(r); const { path, value } = game.system.api.fields.ActionFields.CostField.getItemIdCostUpdate(r);
updates.items[r.key] = { updates.items[`${r.itemId}-${r.key}`] = {
target: r.target, target: r.target,
resources: { [path]: value } resources: { [path]: value }
}; };
@ -873,6 +877,22 @@ export default class DhpActor extends Actor {
return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1; return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1;
} }
convertResourceHealingToReload(updates) {
const resourceIndex = updates.findIndex(u => u.key === CONFIG.DH.GENERAL.healingTypes.weaponResource.id);
if (resourceIndex === -1) return;
const [reload] = updates.splice(resourceIndex, 1);
const weapons = this.items.filter(i => i.type === 'weapon' && i.system.equipped && i.system.resource);
for (const weapon of weapons) {
updates.push({
key: CONFIG.DH.GENERAL.itemAbilityCosts.resource.id,
value: reload.value,
clear: reload.clear,
itemId: weapon.id,
target: weapon
});
}
}
convertStressDamageToHP(resources) { convertStressDamageToHP(resources) {
const stressDamage = resources.find(r => r.key === 'stress'), const stressDamage = resources.find(r => r.key === 'stress'),
newValue = this.system.resources.stress.value + stressDamage.value; newValue = this.system.resources.stress.value + stressDamage.value;

View file

@ -31,6 +31,10 @@
{{/unless}} {{/unless}}
</legend> </legend>
{{#if (eq dmg.applyTo 'weaponResource')}}
{{formField ../fields.fullRestore value=dmg.fullRestore name=(concat ../path "damage.parts." dmg.applyTo ".fullRestore") localize=true classes="checkbox"}}
{{/if}}
{{#unless dmg.fullRestore}}
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}}
{{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}} {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}}
{{/if}} {{/if}}
@ -48,6 +52,7 @@
{{else}} {{else}}
{{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}}
{{/if}} {{/if}}
{{/unless}}
{{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}} {{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}}
{{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." dmg.applyTo ".type") localize=true}} {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." dmg.applyTo ".type") localize=true}}

View file

@ -8,7 +8,15 @@
<ul class="damage-container"> <ul class="damage-container">
{{#each this.updates}} {{#each this.updates}}
<li class="damage-row"> <li class="damage-row">
{{#if (gte this.value 0)}} {{#if this.itemId}}
<span>
{{#if this.clear}}
{{localize "DAGGERHEART.UI.Chat.reloadWeapon" weapon=this.target.name}}
{{else}}
{{localize "DAGGERHEART.UI.Chat.restoreWeaponResource" quantity=this.value weapon=this.target.name}}
{{/if}}
</span>
{{else if (gte this.value 0)}}
<span> <span>
{{ {{
localize "DAGGERHEART.UI.Chat.markResource" localize "DAGGERHEART.UI.Chat.markResource"

View file

@ -11,7 +11,7 @@
<div class="roll-part-extra on-reduced"> <div class="roll-part-extra on-reduced">
<div class="wrapper"> <div class="wrapper">
{{#each damage as | roll index | }} {{#each damage as | roll index | }}
<div class="roll-formula">{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}: {{total}}</div> <div class="roll-formula">{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}: {{#if parts.[0].fullRestore}}{{localize "DAGGERHEART.ACTIONS.Settings.fullRestore.inChatRoll"}}{{else}}{{total}}{{/if}}</div>
{{/each}} {{/each}}
</div> </div>
</div> </div>
@ -21,7 +21,7 @@
{{#each damage as | roll index | }} {{#each damage as | roll index | }}
<fieldset> <fieldset>
<legend> <legend>
{{#if ../hasHealing}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}{{else}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}{{/if}} <div class="roll-formula">{{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}</div>{{#if (and (eq index "hitPoints") ../isDirect)}} <div class="roll-formula">{{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}</div>{{/if}} {{#if ../hasHealing}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}{{else}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}{{/if}} <div class="roll-formula">{{localize "DAGGERHEART.GENERAL.total"}}: {{#if roll.parts.[0].fullRestore}}{{localize "DAGGERHEART.ACTIONS.Settings.fullRestore.inChatRoll"}}{{else}}{{roll.total}}{{/if}}</div>{{#if (and (eq index "hitPoints") ../isDirect)}} <div class="roll-formula">{{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}</div>{{/if}}
</legend> </legend>
{{#each roll.parts}} {{#each roll.parts}}
{{#if (and (not @root.hasHealing) damageTypes.length)}} {{#if (and (not @root.hasHealing) damageTypes.length)}}
@ -32,6 +32,7 @@
{{/each}} {{/each}}
<div class="roll-formula">{{total}}</div></span></label> <div class="roll-formula">{{total}}</div></span></label>
{{/if}} {{/if}}
{{#unless fullRestore}}
<div class="roll-dice"> <div class="roll-dice">
{{#if dice.length}} {{#if dice.length}}
{{#each dice}} {{#each dice}}
@ -60,6 +61,7 @@
</div> </div>
{{/if}} {{/if}}
</div> </div>
{{/unless}}
{{/each}} {{/each}}
</fieldset> </fieldset>
{{/each}} {{/each}}