diff --git a/lang/en.json b/lang/en.json index 508a771d..286c3d3a 100755 --- a/lang/en.json +++ b/lang/en.json @@ -144,6 +144,10 @@ "resultBased": { "label": "Formula based on Hope/Fear result." }, + "fullRestore": { + "label": "Fully restore the resource.", + "inChatRoll": "Full" + }, "applyTo": { "label": "Targeted Resource" }, @@ -1277,6 +1281,11 @@ "name": "Fear", "abbreviation": "FR", "inChatRoll": "Fear" + }, + "weaponResource": { + "name": "Weapon Resource", + "abbreviation": "WR", + "inChatRoll": "Reload" } }, "ItemResourceProgression": { @@ -2658,6 +2667,7 @@ }, "Weapon": { "weaponType": "Weapon Type", + "configureAttack": "Configure Attack", "primaryWeapon": { "full": "Primary Weapon", "short": "Primary" @@ -3093,6 +3103,8 @@ "applyHealing": "Apply Healing" }, "markResource": "Mark {quantity} {resource}", + "reloadWeapon": "Reload {weapon}", + "restoreWeaponResource": "Restore {quantity} on {weapon}", "refreshMessage": { "title": "Feature Refresh", "header": "Refreshed" diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index b65e1cdf..9f5c89c5 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -160,7 +160,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length; if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack') - context.hasBaseDamage = !!this.action.parent.attack; + context.hasBaseDamage = !!this.action.parent.attack && !this.action.baseAction; } context.costOptions = this.getCostOptions(); diff --git a/module/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index f5c7dddf..e74a44db 100644 --- a/module/applications/sheets/items/weapon.mjs +++ b/module/applications/sheets/items/weapon.mjs @@ -5,6 +5,9 @@ export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) { /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['weapon'], + actions: { + configureAttack: WeaponSheet.#configureAttack + }, tagifyConfigs: [ { selector: '.features-input', @@ -46,6 +49,13 @@ export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) { return context; } + /** + * Open the action configuration sheet for the weapon's base attack. + */ + static #configureAttack() { + this.document.system.attack.sheet.render({ force: true }); + } + /** * Callback function used by `tagifyElement`. * @param {Array} selectedOptions - The currently selected tag objects. diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 188efafb..c31313de 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -191,6 +191,11 @@ export const healingTypes = { id: 'fear', label: 'DAGGERHEART.CONFIG.HealingType.fear.name', abbreviation: 'DAGGERHEART.CONFIG.HealingType.fear.abbreviation' + }, + weaponResource: { + id: 'weaponResource', + label: 'DAGGERHEART.CONFIG.HealingType.weaponResource.name', + abbreviation: 'DAGGERHEART.CONFIG.HealingType.weaponResource.abbreviation' } }; diff --git a/module/data/fields/action/costField.mjs b/module/data/fields/action/costField.mjs index 82cfcd23..96032cb4 100644 --- a/module/data/fields/action/costField.mjs +++ b/module/data/fields/action/costField.mjs @@ -197,11 +197,29 @@ export default class CostField extends fields.ArrayField { static getItemIdCostUpdate(r) { 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 { + path: 'system.resource.value', + value: hasMax ? max : resource.value + }; + } + const newValue = resource.value + r.value; return { path: 'system.resource.value', - value: r.target.system.resource.value + r.value + value: Math.max(hasMax ? Math.min(newValue, max) : newValue, 0) }; + } case CONFIG.DH.GENERAL.itemAbilityCosts.quantity.id: return { path: 'system.quantity', diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 2c6c7b30..a031a92e 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -45,9 +45,10 @@ export default class DamageField extends fields.SchemaField { return; 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, - applyTo: p.applyTo + applyTo: p.applyTo, + fullRestore: p.fullRestore })); 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) damageConfig.dialog.configure = false; + if (formulas.every(f => f.fullRestore)) damageConfig.dialog.configure = false; if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod; damageConfig.source.message = messageId; @@ -305,6 +307,10 @@ export class DHResourceData extends foundry.abstract.DataModel { initial: false, label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label' }), + fullRestore: new fields.BooleanField({ + initial: false, + label: 'DAGGERHEART.ACTIONS.Settings.fullRestore.label' + }), value: new fields.EmbeddedDataField(DHActionDiceData), valueAlt: new fields.EmbeddedDataField(DHActionDiceData) }; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index f8880597..1ba5cdf5 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -774,17 +774,21 @@ export default class DhpActor extends Actor { Object.entries(healings).forEach(([key, healing]) => { healing.parts.forEach(part => { const update = updates.find(u => u.key === key); - if (update) update.value += part.total; - else updates.push({ value: part.total, key }); + if (update) { + update.value += part.total; + update.clear ||= !!part.fullRestore; + } else updates.push({ value: part.total, key, clear: !!part.fullRestore }); }); }); - updates.forEach( - u => - (u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false) - ? u.value * -1 - : u.value) - ); + updates.forEach(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 * -1 + : u.value; + }); + + this.convertResourceHealingToReload(updates); await this.modifyResource(updates); @@ -810,7 +814,7 @@ export default class DhpActor extends Actor { resources.forEach(r => { if (r.itemId) { const { path, value } = game.system.api.fields.ActionFields.CostField.getItemIdCostUpdate(r); - updates.items[r.key] = { + updates.items[`${r.itemId}-${r.key}`] = { target: r.target, resources: { [path]: value } }; @@ -881,6 +885,22 @@ export default class DhpActor extends Actor { 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) { const stressDamage = resources.find(r => r.key === 'stress'), newValue = this.system.resources.stress.value + stressDamage.value; diff --git a/src/packs/journals/journal_Welcome___Information_g7NhKvwltwafmMyR.json b/src/packs/journals/journal_Welcome___Information_g7NhKvwltwafmMyR.json index c01157c2..b87dd745 100644 --- a/src/packs/journals/journal_Welcome___Information_g7NhKvwltwafmMyR.json +++ b/src/packs/journals/journal_Welcome___Information_g7NhKvwltwafmMyR.json @@ -132,7 +132,7 @@ "image": {}, "text": { "format": 1, - "content": "

This product includes materials from the Daggerheart System Reference Document 1.0, © Critical Role, LLC. under the terms of the Darrington Press Community Gaming (DPCGL) License. More information can be found at https://www.daggerheart.com. There are no previous modifications by others.

The Foundryborne Team

The Foundryborne Team consists of:

With Art from:

And special thanks to our hard working community testers:

We would also like to thank the FoundryVTT team for their support in publishing this system, as well as providing countless Icons to be used in many places.

And, of course, special thanks to the teams at Critical Role and Darrington Press for making such a wonderful game and updating the license to allow a FoundryVTT version of the system.

The Foundryborne Community

Without our amazing community this project would not have been possible.

You kept us going with both direct contributions and just endless support!

We thank you with all our hearts.

Come join us!

" + "content": "

This product includes materials from the Daggerheart System Reference Document 1.0, © Critical Role, LLC. under the terms of the Darrington Press Community Gaming (DPCGL) License. More information can be found at https://www.daggerheart.com. There are no previous modifications by others.

The Foundryborne Team

The Foundryborne Team consists of:

With Art from:

And special thanks to our hard working community testers:

We would also like to thank the FoundryVTT team for their support in publishing this system, as well as providing countless Icons to be used in many places.

And, of course, special thanks to the teams at Critical Role and Darrington Press for making such a wonderful game and updating the license to allow a FoundryVTT version of the system.

The Foundryborne Community

Without our amazing community this project would not have been possible.

You kept us going with both direct contributions and just endless support!

We thank you with all our hearts.

Come join us!

" }, "video": { "controls": true, diff --git a/styles/less/sheets/items/index.less b/styles/less/sheets/items/index.less index 7f9bb684..324fe92e 100644 --- a/styles/less/sheets/items/index.less +++ b/styles/less/sheets/items/index.less @@ -3,4 +3,6 @@ @import './class.less'; @import './domain-card.less'; @import './feature.less'; -@import './heritage.less'; \ No newline at end of file +@import './heritage.less'; +@import './item-sheet-shared.less'; +@import './weapon.less'; diff --git a/styles/less/sheets/items/weapon.less b/styles/less/sheets/items/weapon.less new file mode 100644 index 00000000..5a00a393 --- /dev/null +++ b/styles/less/sheets/items/weapon.less @@ -0,0 +1,6 @@ +.application.sheet.daggerheart.dh-style.weapon { + section.tab { + height: 400px; + overflow-y: auto; + } +} diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 03300840..cbdf2278 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -31,6 +31,10 @@ {{/unless}} + {{#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))}} {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}} {{/if}} @@ -48,6 +52,7 @@ {{else}} {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} {{/if}} + {{/unless}} {{#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}} diff --git a/templates/sheets/items/weapon/settings.hbs b/templates/sheets/items/weapon/settings.hbs index c9805e7c..3ac6da87 100644 --- a/templates/sheets/items/weapon/settings.hbs +++ b/templates/sheets/items/weapon/settings.hbs @@ -4,7 +4,10 @@ data-group='{{tabs.settings.group}}' >
- {{localize tabs.settings.label}} + + {{localize tabs.settings.label}} + + {{localize "DAGGERHEART.GENERAL.Tiers.singular"}} {{formInput systemFields.tier value=source.system.tier}} {{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full"}} diff --git a/templates/ui/chat/damageSummary.hbs b/templates/ui/chat/damageSummary.hbs index 721f153f..a3458185 100644 --- a/templates/ui/chat/damageSummary.hbs +++ b/templates/ui/chat/damageSummary.hbs @@ -8,7 +8,15 @@