From 26bcc2dddc33dd6d4671ead3b1aef0881c6b1318 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 21 Jul 2026 02:08:52 +0200 Subject: [PATCH] [Feature] Reload Check (#2051) --- lang/en.json | 20 +++++++++-- .../applications/sheets/actors/character.mjs | 15 ++++++-- module/applications/ui/chatLog.mjs | 9 +++++ module/config/settingsConfig.mjs | 15 ++++++++ module/data/action/attackAction.mjs | 21 ++++++++++++ module/data/chat-message/actorRoll.mjs | 1 + module/data/item/weapon.mjs | 8 +++++ module/data/settings/Automation.mjs | 7 ++++ module/dice/dhRoll.mjs | 21 ++++++++++-- ...lack_Powder_Revolver_AokqTusPzn0hghkE.json | 9 +++++ .../weapon_Blunderbuss_SLFrK0WmldPo0shz.json | 9 +++++ .../weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json | 9 +++++ ...eapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json | 9 +++++ ...eapon_Magus_Revolver_jGykNGQiKm63tCiE.json | 9 +++++ styles/less/global/inventory-item.less | 4 +++ styles/less/ui/chat/ability-use.less | 5 +++ styles/less/ui/chat/action.less | 7 +++- styles/less/ui/chat/chat.less | 34 ++++++++++++++++--- .../settings/automation-settings/roll.hbs | 1 + .../sheets/global/partials/item-resource.hbs | 10 ++++-- templates/ui/chat/parts/button-part.hbs | 3 ++ templates/ui/chat/roll.hbs | 11 ++++++ 22 files changed, 223 insertions(+), 14 deletions(-) diff --git a/lang/en.json b/lang/en.json index b65cade7..d0b8d0f7 100755 --- a/lang/en.json +++ b/lang/en.json @@ -126,6 +126,10 @@ "damageOnSave": "Damage on Save", "useDefaultItemValues": "Use default Item values" }, + "Reload": { + "checkReload": "Check Reload", + "reloadRequired": "Reload Required!" + }, "RollField": { "diceRolling": { "compare": "Should be", @@ -1322,6 +1326,11 @@ "short": "V. Far" } }, + "ReloadChoices": { + "off": { "label": "Don't Use" }, + "button": { "label": "Use button" }, + "auto": { "label": "Automatic" } + }, "RollTypes": { "trait": { "name": "Trait" @@ -2221,7 +2230,9 @@ }, "Resource": { "single": "Resource", - "plural": "Resources" + "plural": "Resources", + "unloaded": "The weapon is not loaded", + "loaded": "The weapon is loaded" }, "Roll": { "attack": "Attack Roll", @@ -2797,6 +2808,10 @@ "hint": "Effects with defined range dependency will automatically turn on/off depending on range" } }, + "reload": { + "label": "Reload Checking", + "hint": "If the system should present a button for checking if the character will need to reload or do it automatically" + }, "resourceScrollTexts": { "label": "Show Resource Change Scrolltexts", "hint": "When a character is damaged, uses armor etc, a scrolling text will briefly appear by the token to signify this." @@ -3258,7 +3273,8 @@ "knowTheTide": "Know The Tide gained a token", "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}", "noTokenTargeted": "No token is targeted", - "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM" + "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM", + "reloadRequired": "The {weapon} must be reloaded to be used!" }, "Progress": { "migrationLabel": "Performing system migration. Please wait and do not close Foundry." diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index b5c04f78..b88ec6fd 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs'; import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; -import { getArmorSources, getDocFromElement, getDocFromElementSync, sortBy } from '../../../helpers/utils.mjs'; +import { getArmorSources, getDocFromElement, getDocFromElementSync, itemAbleRollParse, sortBy } from '../../../helpers/utils.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -29,6 +29,7 @@ export default class CharacterSheet extends DHBaseActorSheet { toggleResourceDice: CharacterSheet.#toggleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice, advanceResourceDie: CharacterSheet.#advanceResourceDie, + toggleItemReload: CharacterSheet.#onToggleItemReload, cancelBeastform: CharacterSheet.#cancelBeastform, toggleResourceManagement: CharacterSheet.#toggleResourceManagement, useDowntime: this.useDowntime, @@ -954,11 +955,21 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } - /** */ static #advanceResourceDie(_, target) { this.updateResourceDie(target, true); } + static async #onToggleItemReload(_, target) { + const item = await getDocFromElement(target); + if (!item || !item.system.resource?.max) + return; + + await item.update({ + 'system.resource.value': item.system.needsReload ? + itemAbleRollParse(item.system.resource.max, this.document, item) : 0 + }) + } + lowerResourceDie(event) { event.preventDefault(); event.stopPropagation(); diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index c457a6a6..0ad436ec 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -152,6 +152,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo html.querySelectorAll('.risk-it-all-button').forEach(element => element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data)) ); + for (const element of html.querySelectorAll('.roll-reload-check')) { + element.addEventListener('click', event => this.onRollReloadCheck(event, message)); + } }; setupHooks() { @@ -275,4 +278,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const actor = game.actors.get(event.target.dataset.actorId); new game.system.api.applications.dialogs.RiskItAllDialog(actor, resourceValue).render({ force: true }); } + + async onRollReloadCheck(_event, messageData) { + const message = game.messages.get(messageData._id); + const needsReload = await message.system.action.handleReload?.({ awaitRoll: true }); + await message.update({ 'system.needsReload': needsReload }); + } } diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 7aa1b0f7..daa89959 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -62,3 +62,18 @@ export const actionAutomationChoices = { label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.always' } }; + +export const reloadChoices = { + off: { + id: 'off', + label: 'DAGGERHEART.CONFIG.ReloadChoices.off.label' + }, + button: { + id: 'button', + label: 'DAGGERHEART.CONFIG.ReloadChoices.button.label' + }, + auto: { + id: 'auto', + label: 'DAGGERHEART.CONFIG.ReloadChoices.auto.label' + } +}; \ No newline at end of file diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index dadc85f0..bf78520d 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -52,6 +52,10 @@ export default class DHAttackAction extends DHDamageAction { } async use(event, options) { + if (this.item?.system.needsReload) { + return ui.notifications.error(_loc('DAGGERHEART.UI.Notifications.reloadRequired', { weapon: this.item.name })); + } + const result = await super.use(event, options); if (result?.message?.system.action?.roll?.type === 'attack') { @@ -62,6 +66,23 @@ export default class DHAttackAction extends DHDamageAction { return result; } + async handleReload(options = { awaitRoll: false }) { + const roll = await new Roll('1d6').evaluate(); + if (game.modules.get('dice-so-nice')?.active) { + if (options.awaitRoll) + await game.dice3d.showForRoll(roll, game.user, true); + else + game.dice3d.showForRoll(roll, game.user, true); + } + + const needsToReload = roll.total === 1; + if (needsToReload) { + this.item.update({ 'system.resource.value': 0 }); + } + + return needsToReload; + } + /** * Generate a localized label array for this item subtype. * @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects. diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index dd81952b..7071ed1e 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -42,6 +42,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { hasEffect: new fields.BooleanField({ initial: false }), hasSave: new fields.BooleanField({ initial: false }), hasTarget: new fields.BooleanField({ initial: false }), + needsReload: new fields.BooleanField({ initial: false }), isDirect: new fields.BooleanField({ initial: false }), onSave: new fields.StringField(), source: new fields.SchemaField({ diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 5ff2d8d1..3d50e68a 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -116,6 +116,14 @@ export default class DHWeapon extends AttachableItem { return this.weaponFeatures; } + get hasReload() { + return Boolean(this.weaponFeatures.find(x => x.value === 'reloading')); + } + + get needsReload() { + return this.hasReload && this.resource.value === 0; + } + /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index 35e87327..e7bc0458 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -196,6 +196,13 @@ export default class DhAutomation extends foundry.abstract.DataModel { }) }) }), + reload: new fields.StringField({ + required: true, + choices: CONFIG.DH.SETTINGS.reloadChoices, + initial: CONFIG.DH.SETTINGS.reloadChoices.button.id, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.reload.label', + hint: 'DAGGERHEART.SETTINGS.Automation.FIELDS.reload.hint' + }), autoExpireActiveEffects: new fields.BooleanField({ required: true, initial: true, diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index c78caa4f..62d9ff9f 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -117,7 +117,11 @@ export default class DHRoll extends BaseRoll { static async toMessage(roll, config) { const item = config.data.parent?.items?.get?.(config.source.item) ?? null; - const action = item ? item.system.actions.get(config.source.action) : null; + const actions = item ? [ + ...item.system.actions, + ...(item.system.attack?.id === config.source.action ? [item.system.attack] : []) + ] : []; + const action = actions.find(x => x.id === config.source.action); let actionDescription = null; if (action?.chatDisplay) { actionDescription = action @@ -129,6 +133,14 @@ export default class DHRoll extends BaseRoll { config.actionChatMessageHandled = true; } + const reloadSetting = + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).reload; + const useReload = + item?.system.hasReload && + action?.type === 'attack' && + reloadSetting === CONFIG.DH.SETTINGS.reloadChoices.auto.id; + const needsReload = useReload ? await action?.handleReload?.() : false; + const cls = getDocumentClass('ChatMessage'), msgData = { type: this.messageType, @@ -136,7 +148,7 @@ export default class DHRoll extends BaseRoll { title: roll.title, speaker: cls.getSpeaker({ actor: roll.data?.parent }), sound: config.mute ? null : CONFIG.sounds.dice, - system: { ...config, actionDescription }, + system: { ...config, actionDescription, needsReload }, rolls: [roll] }; @@ -158,14 +170,17 @@ export default class DHRoll extends BaseRoll { if (!this._evaluated) return; const metagamingSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming); + const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options }); return foundry.applications.handlebars.renderTemplate(template, { roll: this, ...chatData, + action: chatData.action, parent: chatData.parent, targetMode: chatData.targetMode, areas: chatData.action?.areas, - metagamingSettings + metagamingSettings, + automationSettings }); } diff --git a/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json b/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json index 34371c2b..f8b254f3 100644 --- a/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json +++ b/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 48, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json b/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json index 9b2f455a..570d8856 100644 --- a/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json +++ b/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 46, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json b/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json index 4967c6e4..f9d54bcd 100644 --- a/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json +++ b/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 50, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json b/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json index cddd762a..512168f5 100644 --- a/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json +++ b/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 49, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json b/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json index 9dbbb1c1..fd3e86d3 100644 --- a/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json +++ b/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 51, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index d942133c..d2ca76c6 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -144,6 +144,10 @@ display: flex; align-items: center; gap: 4px; + + .unloaded { + opacity: 0.5; + } } } diff --git a/styles/less/ui/chat/ability-use.less b/styles/less/ui/chat/ability-use.less index c31136ad..9d3cb84e 100644 --- a/styles/less/ui/chat/ability-use.less +++ b/styles/less/ui/chat/ability-use.less @@ -133,6 +133,11 @@ height: 40px; flex: 1 1 calc(50% - 5px); + span { + font-family: @font-body; + font-weight: 700; + } + &:nth-last-child(1):nth-child(odd) { flex-basis: 100%; } diff --git a/styles/less/ui/chat/action.less b/styles/less/ui/chat/action.less index 6eeb7a52..cff617d8 100644 --- a/styles/less/ui/chat/action.less +++ b/styles/less/ui/chat/action.less @@ -46,9 +46,14 @@ padding: 0 8px; button { - height: 40px; + height: 36px; flex: 1 1 calc(50% - 5px); + span { + font-family: @font-body; + font-weight: 700; + } + &:nth-last-child(1):nth-child(odd) { flex-basis: 100%; } diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index 4d627e39..8c3f4b08 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -271,6 +271,28 @@ } } + .roll-reload-container { + text-align: center; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + + .reload-warning { + display: flex; + align-items: center; + border-radius: 5px; + width: fit-content; + gap: 5px; + cursor: pointer; + padding: 5px; + transition: all 0.3s ease; + color: @beige; + background: @red-40; + outline: 1px solid @red; + } + } + .roll-part-extra { display: flex; justify-content: center; @@ -622,15 +644,19 @@ .roll-buttons { display: flex; + flex-wrap: wrap; gap: 5px; + width: 100%; margin-top: 8px; button { - height: 32px; - flex: 1; + height: 36px; + flex: 1 1 calc(50% - 5px); + font-family: @font-body; + font-weight: 700; - &.end-button { - flex: 0; + &:nth-last-child(1):nth-child(odd) { + flex-basis: 100%; } } } diff --git a/templates/settings/automation-settings/roll.hbs b/templates/settings/automation-settings/roll.hbs index dc65f8ae..a0b19ee4 100644 --- a/templates/settings/automation-settings/roll.hbs +++ b/templates/settings/automation-settings/roll.hbs @@ -18,6 +18,7 @@
{{localize (concat "DAGGERHEART.SETTINGS.Automation.FIELDS.roll." field.name ".hint")}}
{{/each}} + {{formGroup settingFields.schema.fields.reload value=settingFields.reload localize=true}}