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 1/4] [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}}
diff --git a/templates/sheets/global/partials/item-resource.hbs b/templates/sheets/global/partials/item-resource.hbs index fbcf02ca..cde10d37 100644 --- a/templates/sheets/global/partials/item-resource.hbs +++ b/templates/sheets/global/partials/item-resource.hbs @@ -1,7 +1,13 @@ {{#if (eq item.system.resource.type 'simple')}}
- - + {{#if item.system.hasReload}} + + + + {{else}} + + + {{/if}}
{{else if (eq item.system.resource.type 'diceValue')}}
diff --git a/templates/ui/chat/parts/button-part.hbs b/templates/ui/chat/parts/button-part.hbs index a0075b5f..b631192c 100644 --- a/templates/ui/chat/parts/button-part.hbs +++ b/templates/ui/chat/parts/button-part.hbs @@ -1,4 +1,7 @@
+ {{#if (eq automationSettings.reload 'button')}} + + {{/if}} {{#if areas.length}}{{/if}} {{#if hasDamage}} {{#if damage.active}} diff --git a/templates/ui/chat/roll.hbs b/templates/ui/chat/roll.hbs index c7b17b21..ab4e197e 100644 --- a/templates/ui/chat/roll.hbs +++ b/templates/ui/chat/roll.hbs @@ -1,5 +1,16 @@
{{title}}
+ + {{#if (eq action.type 'attack')}} + {{#if needsReload}} +
+ {{#if needsReload}} +

{{localize "DAGGERHEART.ACTIONS.Reload.reloadRequired"}}

+ {{/if}} +
+ {{/if}} + {{/if}} + {{#if actionDescription}}{{> 'systems/daggerheart/templates/ui/chat/parts/description-part.hbs'}}{{/if}} {{#if hasRoll}}
{{localize "Result"}}
From d50545af4ecc95e749e8f2bc334cffd48dbe6b97 Mon Sep 17 00:00:00 2001 From: Murilo Brito <91566541+moliloo@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:50:03 -0300 Subject: [PATCH 2/4] [Rework] Fear Tracker (#2097) * rework fear tracker application, add static positions to hold the tracker, enhance token and bar display --- lang/en.json | 10 + module/applications/ui/chatLog.mjs | 6 + module/applications/ui/fearTracker.mjs | 203 ++++++++++++- module/config/generalConfig.mjs | 8 + module/data/settings/Appearance.mjs | 5 + styles/less/ui/countdown/countdown.less | 3 +- styles/less/ui/resources/resources.less | 284 ++++++++++++------ .../settings/appearance-settings/main.hbs | 4 + templates/ui/fearTracker.hbs | 46 ++- 9 files changed, 457 insertions(+), 112 deletions(-) diff --git a/lang/en.json b/lang/en.json index d0b8d0f7..7aa87c7a 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2699,6 +2699,9 @@ "displayFear": { "label": "Display Fear" }, + "fearPosition": { + "label": "Fear Position" + }, "displayCountdownUI": { "label": "Display Countdown UI" }, @@ -2750,6 +2753,13 @@ "token": "Tokens", "bar": "Bar", "hide": "Hide" + }, + "fearPosition": { + "free": "Free", + "topCenter": "Top + Center", + "bottomCenter": "Bottom + Center", + "rightTop": "Right + Top", + "leftBottom": "Left + Bottom" } }, "Automation": { diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 0ad436ec..1ed4607f 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -1,6 +1,7 @@ import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs'; import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs'; import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs'; +import FearTracker from './fearTracker.mjs'; export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog { constructor(options) { @@ -279,6 +280,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo new game.system.api.applications.dialogs.RiskItAllDialog(actor, resourceValue).render({ force: true }); } + _toggleNotifications({ closing = false } = {}) { + super._toggleNotifications(closing) + FearTracker.handleOffSet(); + } + async onRollReloadCheck(_event, messageData) { const message = game.messages.get(messageData._id); const needsReload = await message.system.action.handleReload?.({ awaitRoll: true }); diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs index 8c247f79..d1813736 100644 --- a/module/applications/ui/fearTracker.mjs +++ b/module/applications/ui/fearTracker.mjs @@ -13,6 +13,14 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV2) { constructor(options = {}) { super(options); + + this._dragData = { + isDragging: false, + startX: 0, + startY: 0, + startLeft: 0, + startTop: 0 + } } /** @inheritDoc */ @@ -21,19 +29,20 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV classes: [], tag: 'div', window: { - frame: true, + frame: false, title: 'DAGGERHEART.GENERAL.fear', positioned: true, resizable: true, minimizable: false }, + classes: ['daggerheart', 'dh-style', 'fear-tracker'], actions: { setFear: FearTracker.setFear, increaseFear: FearTracker.increaseFear }, position: { - width: 222, - height: 222 + width: 540, + height: 'auto' } }; @@ -53,6 +62,10 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear; } + get fearPosition() { + return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).fearPosition; + } + /* -------------------------------------------- */ /* Rendering */ /* -------------------------------------------- */ @@ -63,15 +76,48 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV current = this.currentFear, max = this.maxFear, percent = (current / max) * 100, - isGM = game.user.isGM; + isGM = game.user.isGM, + locked = false, + isFree = this.fearPosition == 'free'; - return { display, current, max, percent, isGM }; + return { display, current, max, percent, isGM, locked, isFree }; } /** @override */ - async _preFirstRender(context, options) { - options.position = - game.user.getFlag(CONFIG.DH.id, 'app.resources.position') ?? FearTracker.DEFAULT_OPTIONS.position; + async _onRender(context, options) { + await super._onRender(context, options); + + this.#setupDragging(); + this.#setupResizing(); + + const fearPosition = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).fearPosition; + + if (options.isFirstRender) FearTracker.handleOffSet(); + if (!options.force) return; + + this.handleStyleElement(fearPosition); + + switch (fearPosition) { + case 'topCenter': + document.getElementById('ui-top')?.appendChild(this.element); + break; + case 'bottomCenter': + document.getElementById('ui-bottom')?.prepend(this.element); + break; + case 'rightTop': + document.getElementById('ui-right-column-1')?.appendChild(this.element); + break; + case 'leftBottom': + document.getElementById('ui-left-column-1')?.insertBefore(this.element, document.getElementById('players')); + break; + + default: + document.body?.appendChild(this.element); + const position = + game.user.getFlag(CONFIG.DH.id, 'app.resources.position') ?? FearTracker.DEFAULT_OPTIONS.position; + this.setPosition(position); + break; + } } /** @override */ @@ -80,13 +126,29 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear); } - _onPosition(position) { - game.user.setFlag(CONFIG.DH.id, 'app.resources.position', position); + handleStyleElement(fearPosition) { + for (const position of Object.values(CONFIG.DH.GENERAL.fearPosition)) { + this.element.classList.remove(position.value); + } + + this.element.classList.add(fearPosition); } - async close(options = {}) { - if (!options.allowed) return; - else super.close(options); + static handleOffSet() { + const fearTracker = document.getElementById('resources'); + const hotbar = document.getElementById('hotbar'); + + if (!fearTracker) return; + + const offset = Number(hotbar.style.getPropertyValue('--offset').replace(/px$/, '')) || 0; + + if (offset > 0) return; + + fearTracker.style.setProperty('--offset', `${offset - 13}px`); + } + + _onPosition(position) { + game.user.setFlag(CONFIG.DH.id, 'app.resources.position', position); } static async setFear(event, target) { @@ -110,4 +172,119 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV value ); } + + // TODO: Remove methods later to use Foundry's dragger and resize methods + /* -------------------------------------------- */ + /* Dragging handlers */ + /* -------------------------------------------- */ + #setupDragging() { + const dragHandle = this.element.querySelector('.drag-handle'); + if (!dragHandle) return; + dragHandle.addEventListener('mousedown', this.#onDragStart.bind(this)); + } + + #onDragStart(event) { + if (event.button !== 0) return; + this._dragData.isDragging = true; + this._dragData.startX = event.clientX; + this._dragData.startY = event.clientY; + const rect = this.element.getBoundingClientRect(); + this._dragData.startLeft = rect.left; + this._dragData.startTop = rect.top; + this.element.style.cursor = 'grabbing'; + + this._dragHandler = this.#onDragging.bind(this); + this._dragEndHandler = this.#onDragEnd.bind(this); + window.addEventListener('mousemove', this._dragHandler); + window.addEventListener('mouseup', this._dragEndHandler); + } + + #onDragging(event) { + if (!this._dragData.isDragging) return; + + const dragX = event.clientX - this._dragData.startX; + const dragY = event.clientY - this._dragData.startY; + + this.element.style.left = `${this._dragData.startLeft + dragX}px`; + this.element.style.top = `${this._dragData.startTop + dragY}px`; + } + + #onDragEnd() { + if (!this._dragData.isDragging) return; + this._dragData.isDragging = false; + this.element.style.cursor = ''; + + if (this._dragHandler) window.removeEventListener('mousemove', this._dragHandler); + if (this._dragEndHandler) window.removeEventListener('mouseup', this._dragEndHandler); + + const rect = this.element.getBoundingClientRect(); + const pos = { top: rect.top, left: rect.left }; + + this.setPosition(pos); + } + + /* -------------------------------------------- */ + /* Resize handlers */ + /* -------------------------------------------- */ + + #setupResizing() { + const resizeHandle = this.element.querySelector('.resize-handle'); + if (!resizeHandle) return; + resizeHandle.addEventListener('mousedown', this.#onResizeStart.bind(this)); + } + + #onResizeStart(e) { + if (e.button !== 0) return; + e.stopPropagation(); + + let maxAllowedWidth = 10000; + + this._resizeData = { + isResizing: true, + startX: e.clientX, + startY: e.clientY, + startWidth: this.element.offsetWidth, + startHeight: this.element.offsetHeight, + maxAllowedWidth: Math.max(50, maxAllowedWidth) + }; + + this._resizeHandler = this.#onResizing.bind(this); + this._resizeEndHandler = this.#onResizeEnd.bind(this); + window.addEventListener('mousemove', this._resizeHandler); + window.addEventListener('mouseup', this._resizeEndHandler); + } + + #onResizing(e) { + if (!this._resizeData?.isResizing) return; + + const currentDx = e.clientX - this._resizeData.startX; + const potentialWidth = Math.max(50, this._resizeData.startWidth + currentDx); + + const width = Math.min(potentialWidth, this._resizeData.maxAllowedWidth); + + this.element.style.width = `${width}px`; + + if (width < 100) { + this.element.classList.add('narrow'); + } else { + this.element.classList.remove('narrow'); + } + } + + #onResizeEnd() { + if (!this._resizeData?.isResizing) return; + this._resizeData.isResizing = false; + + if (this._resizeHandler) window.removeEventListener('mousemove', this._resizeHandler); + if (this._resizeEndHandler) window.removeEventListener('mouseup', this._resizeEndHandler); + + let width = parseFloat(this.element.style.width); + + + if (isNaN(width)) { + width = this.element.getBoundingClientRect().width; + } + + this.setPosition({ width: width }); + } } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 802f5907..3876f6b5 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -924,6 +924,14 @@ export const fearDisplay = { hide: { value: 'hide', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.hide' } }; +export const fearPosition = { + free: { value: 'free', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.free' }, + topCenter: { value: 'topCenter', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.topCenter' }, + bottomCenter: { value: 'bottomCenter', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.bottomCenter' }, + rightTop: { value: 'rightTop', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.rightTop' }, + leftBottom: { value: 'leftBottom', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.leftBottom' } +}; + export const basicOwnershiplevels = { 0: { value: 0, label: 'OWNERSHIP.NONE' }, 2: { value: 2, label: 'OWNERSHIP.OBSERVER' }, diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs index 4db27be0..16f5fac0 100644 --- a/module/data/settings/Appearance.mjs +++ b/module/data/settings/Appearance.mjs @@ -41,6 +41,11 @@ export default class DhAppearance extends foundry.abstract.DataModel { choices: CONFIG.DH.GENERAL.fearDisplay, initial: CONFIG.DH.GENERAL.fearDisplay.token.value }), + fearPosition: new StringField({ + required: true, + choices: CONFIG.DH.GENERAL.fearPosition, + initial: CONFIG.DH.GENERAL.fearPosition.topCenter.value + }), displayCountdownUI: new BooleanField({ initial: true }), diceSoNice: new SchemaField({ hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }), diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 96e01ffd..4e64d50a 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -32,7 +32,7 @@ background: var(--background); border-radius: 4px; opacity: var(--ui-fade-opacity); - transition: opacity var(--ui-fade-duration); + transition: opacity var(--ui-fade-delay) var(--ui-fade-duration); } &:not(.performance-low, .noblur) { @@ -41,6 +41,7 @@ &:hover::before { opacity: 1; + transition: opacity var(--ui-fade-duration); } #ui-right:has(#effects-display .effect-container) & { diff --git a/styles/less/ui/resources/resources.less b/styles/less/ui/resources/resources.less index 3982d990..27f11942 100644 --- a/styles/less/ui/resources/resources.less +++ b/styles/less/ui/resources/resources.less @@ -1,119 +1,221 @@ :root { - --shadow-text-stroke: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; - --fear-animation: background 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease, opacity 0.3s ease; + --hotbar-size: 60px; +} + +#interface.theme-dark, +body.theme-dark { + .daggerheart.dh-style.fear-tracker { + --background: url(../assets/parchments/dh-parchment-dark.png); + } +} + +#interface.theme-light, +body.theme-light { + .daggerheart.dh-style.fear-tracker { + --background: url('../assets/parchments/dh-parchment-light.png') no-repeat center; + } +} + +#ui-middle:has(#hotbar.sm), +#ui-middle:has(#hotbar.md.offset) { + #resources { + &.top-center, + &.bottom-center { + width: calc((var(--hotbar-size) * 5) + 32px) !important; + } + } } #resources { + position: static; min-height: calc(var(--header-height) + 4rem); min-width: 4rem; color: #d3d3d3; - transition: var(--fear-animation); - header, - .controls, - .window-resize-handle { - transition: var(--fear-animation); + pointer-events: all; + padding: var(--spacer-8); + max-width: 540px; + min-width: 100px; + + &::before { + content: ' '; + position: absolute; + inset: 0; + background: var(--background); + border-radius: 8px; + opacity: var(--ui-fade-opacity); + transition: opacity var(--ui-fade-delay) var(--ui-fade-duration); + } - .window-content { - padding: 0.5rem; - #resource-fear { + + &:hover::before { + opacity: 1; + transition: opacity var(--ui-fade-duration); + } + + &.free { + position: absolute; + } + + &.topCenter, + &.bottomCenter { + margin: 1rem 0; + width: 100% !important; + transform: translateX(var(--offset)); + transition: all 250ms ease; + } + + &.rightTop { + width: 300px !important; + max-width: 300px; + } + + &.leftBottom { + width: 200px !important; + max-width: 200px; + background: transparent; + } + + &:not(.performance-low, .noblur) { + backdrop-filter: blur(5px); + } + + #ui-right:has(#effects-display .effect-container) & { + right: 62px; + } + + #resource-fear { + position: relative; + + &:hover { + .fear-header { + opacity: 1; + height: 18.75px; + visibility: visible; + } + + .resize-handle { + opacity: 1; + } + } + + .fear-header { + display: flex; + gap: 5px; + pointer-events: all; + margin-bottom: 0.5rem; + opacity: 0; + height: 0; + visibility: hidden; + transition: all 0.3s ease; + + .drag-handle { + cursor: grab; + } + + .fear-title { + font-size: var(--font-size-13); + } + } + + .fear-tokens { display: flex; flex-direction: row; - gap: 0.5rem 0.25rem; flex-wrap: wrap; - i { - font-size: var(--font-size-18); - border: 1px solid rgba(0, 0, 0, 0.5); + justify-content: center; + gap: 0.5rem 0.25rem; + + .fear-token { + font-size: var(--font-size-16); + border: 1.5px double light-dark(@dark-15, @dark-golden-80); border-radius: 50%; aspect-ratio: 1; display: flex; justify-content: center; align-items: center; - width: 3rem; + width: 2.5rem; background-color: @primary-color-fear; - -webkit-box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); - box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); - color: #d3d3d3; + color: @beige; flex-grow: 0; + text-shadow: none; + &.inactive { filter: grayscale(1) !important; opacity: 0.5; } } - .controls, - .resource-bar { - border: 2px solid rgb(153 122 79); - background-color: rgb(24 22 46); - } - .controls { - display: flex; - align-self: center; - border-radius: 50%; - align-items: center; - justify-content: center; - width: 30px; - height: 30px; - font-size: var(--font-size-20); - cursor: pointer; - &:hover { - font-size: 1.5rem; - } - &.disabled { - opacity: 0.5; - } - } - .resource-bar { - display: flex; - justify-content: center; - border-radius: 6px; - font-size: var(--font-size-20); - overflow: hidden; - position: relative; - padding: 0.25rem 0.5rem; - flex: 1; - text-shadow: var(--shadow-text-stroke); - &:before { - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: var(--fear-percent); - max-width: 100%; - background: linear-gradient(90deg, rgba(2, 0, 38, 1) 0%, rgba(199, 1, 252, 1) 100%); - z-index: 0; - border-radius: 4px; - } - span { - position: inherit; - z-index: 1; - } - &.fear { - } - } - &.isGM { - i { - cursor: pointer; - &:hover { - font-size: var(--font-size-20); - } - } - } } - } - button[data-action='close'] { - display: none; - } - &:not(:hover):not(.minimized) { - background: transparent; - box-shadow: unset; - border-color: transparent; - header, - #resource-fear .controls, - .window-resize-handle { + + .resize-handle { + position: absolute; + bottom: -12px; + right: -9px; + cursor: nwse-resize; opacity: 0; + transition: all 0.3s ease; + } + + + .resource-bar { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + height: 30px; + width: 100%; + + .progress-bar { + position: absolute; + appearance: none; + width: 100%; + height: 100%; + border: 1px solid light-dark(@dark-15, @dark-golden-80); + border-radius: 999px; + z-index: 0; + background: @dark-blue; + + &::-webkit-progress-bar { + border: none; + background: @dark-blue; + border-radius: 999px; + } + &::-webkit-progress-value { + background: linear-gradient(90deg, rgba(2, 0, 38, 1) 0%, rgba(199, 1, 252, 1) 100%); + border-radius: 999px; + } + } + + .label { + margin: auto 0; + z-index: 2; + height: auto; + text-align: center; + font-size: var(--font-size-18); + color: @beige; + } + } + + .controls { + display: flex; + gap: 8px; + justify-content: center; + align-items: center; + margin-top: 0.5rem; + color: light-dark(@dark, @beige); + + .disabled { + opacity: 0.5; + } + } + + &.isGM { + .fear-token { + cursor: pointer; + transition: box-shadow 0.15s ease; + + &:hover { + box-shadow: 0 0 8px @primary-color-fear ; + } + } } - } - &:has(.fear-bar) { - min-width: 200px; } } diff --git a/templates/settings/appearance-settings/main.hbs b/templates/settings/appearance-settings/main.hbs index 32dd9e63..324ea653 100644 --- a/templates/settings/appearance-settings/main.hbs +++ b/templates/settings/appearance-settings/main.hbs @@ -8,6 +8,10 @@ value=setting.displayFear localize=true}} {{formGroup + fields.fearPosition + value=setting.fearPosition + localize=true}} + {{formGroup fields.displayCountdownUI value=setting.displayCountdownUI localize=true}} diff --git a/templates/ui/fearTracker.hbs b/templates/ui/fearTracker.hbs index 6832ab90..deae762a 100644 --- a/templates/ui/fearTracker.hbs +++ b/templates/ui/fearTracker.hbs @@ -1,16 +1,48 @@
-
+
+ {{#if isFree}} +
+
+ +
+ {{localize 'DAGGERHEART.GENERAL.fear'}} +
+ {{/if}} + {{#if (eq display 'token')}} - {{#times max}} - - {{/times}} +
+ {{#times max}} + + + + {{/times}} +
{{/if}} {{#if (eq display 'bar')}} - {{#if isGM}}
-
{{/if}}
- {{current}}/{{max}} + +

{{current}} / {{max}}

- {{#if isGM}}
+
{{/if}} + {{#if isGM}} +
+ + +
+ {{/if}} + {{/if}} + + {{#if isFree}} + + + {{/if}}
\ No newline at end of file From 7f61715adf555ec724ac2540abfb0633b825c7d1 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 22 Jul 2026 03:36:28 +0200 Subject: [PATCH 3/4] [Fix] Reload Fixes (#2099) --- lang/en.json | 11 ++- module/applications/ui/chatLog.mjs | 16 ++++- module/config/settingsConfig.mjs | 6 +- module/data/action/attackAction.mjs | 8 +-- module/data/chat-message/actorRoll.mjs | 19 +++-- module/data/settings/Automation.mjs | 2 +- module/dice/dhRoll.mjs | 8 ++- styles/less/ui/chat/chat.less | 96 ++++++++++++++++--------- templates/ui/chat/parts/button-part.hbs | 51 ++++++++----- templates/ui/chat/roll.hbs | 10 --- 10 files changed, 150 insertions(+), 77 deletions(-) diff --git a/lang/en.json b/lang/en.json index 7aa87c7a..232315c2 100755 --- a/lang/en.json +++ b/lang/en.json @@ -128,7 +128,12 @@ }, "Reload": { "checkReload": "Check Reload", - "reloadRequired": "Reload Required!" + "reloadRequired": "Reload Required!", + "notRolled": "Not Rolled", + "checkFailed": "Check Failed", + "checkPassed": "Check Passed", + "rerollConfirmationTitle": "Reroll Reload Check", + "rerollConfirmationText": "Are you sure you want to reload the reload check?" }, "RollField": { "diceRolling": { @@ -1328,7 +1333,7 @@ }, "ReloadChoices": { "off": { "label": "Don't Use" }, - "button": { "label": "Use button" }, + "manual": { "label": "Manual" }, "auto": { "label": "Automatic" } }, "RollTypes": { @@ -2820,7 +2825,7 @@ }, "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" + "hint": "If the system should automatically roll reload checks or wait for the manual press of a button in chat" }, "resourceScrollTexts": { "label": "Show Resource Change Scrolltexts", diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 1ed4607f..5fb61857 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -287,7 +287,19 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo 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 }); + + if (message.system.reloadCheckValue) { + const confirmed = await foundry.applications.api.DialogV2.confirm({ + window: { + title: _loc('DAGGERHEART.ACTIONS.Reload.rerollConfirmationTitle') + }, + content: _loc('DAGGERHEART.ACTIONS.Reload.rerollConfirmationText') + }); + + if (!confirmed) return; + } + + const { rollValue } = await message.system.action.handleReload?.({ awaitRoll: true }); + await message.update({ 'system.reloadCheckValue': rollValue }); } } diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index daa89959..92a3361c 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -68,9 +68,9 @@ export const reloadChoices = { id: 'off', label: 'DAGGERHEART.CONFIG.ReloadChoices.off.label' }, - button: { - id: 'button', - label: 'DAGGERHEART.CONFIG.ReloadChoices.button.label' + manual: { + id: 'manual', + label: 'DAGGERHEART.CONFIG.ReloadChoices.manual.label' }, auto: { id: 'auto', diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index bf78520d..ba11c6ff 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -74,13 +74,13 @@ export default class DHAttackAction extends DHDamageAction { else game.dice3d.showForRoll(roll, game.user, true); } - - const needsToReload = roll.total === 1; - if (needsToReload) { + + const needsReload = roll.total === 1; + if (needsReload) { this.item.update({ 'system.resource.value': 0 }); } - return needsToReload; + return { needsReload, rollValue: roll.total }; } /** diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index 7071ed1e..3dcf2a8d 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -42,7 +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 }), + reloadCheckValue: new fields.NumberField({ integer: true, nullable: true, initial: null }), isDirect: new fields.BooleanField({ initial: false }), onSave: new fields.StringField(), source: new fields.SchemaField({ @@ -76,10 +76,14 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { return fromUuidSync(this.source.actor); } - get actionItem() { + get item() { const actionActor = this.actionActor; if (!actionActor || !this.source.item) return null; + + return actionActor.items.get(this.source.item); + } + get actionItem() { switch (this.source.originItem.type) { case CONFIG.DH.ITEM.originItemType.restMove: const restMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves; @@ -87,11 +91,18 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { this.source.originItem.actionIndex ]; default: - const item = actionActor.items.get(this.source.item); - return item ? item.system.actionsList?.find(a => a.id === this.source.action) : null; + return this.item?.system.actionsList?.find(a => a.id === this.source.action); } } + get hasReload() { + return this.item?.system.hasReload; + } + + get reloadCheckFailed() { + return this.reloadCheckValue === 1; + } + get action() { const { actionActor, actionItem: itemAction } = this; if (!this.source.action) return null; diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index e7bc0458..523ddaac 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -199,7 +199,7 @@ 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, + initial: CONFIG.DH.SETTINGS.reloadChoices.manual.id, label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.reload.label', hint: 'DAGGERHEART.SETTINGS.Automation.FIELDS.reload.hint' }), diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 62d9ff9f..68185984 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -139,7 +139,7 @@ export default class DHRoll extends BaseRoll { item?.system.hasReload && action?.type === 'attack' && reloadSetting === CONFIG.DH.SETTINGS.reloadChoices.auto.id; - const needsReload = useReload ? await action?.handleReload?.() : false; + const reloadResult = useReload ? await action?.handleReload?.() : {}; const cls = getDocumentClass('ChatMessage'), msgData = { @@ -148,7 +148,11 @@ 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, needsReload }, + system: { + ...config, + actionDescription, + reloadCheckValue: reloadResult.rollValue + }, rolls: [roll] }; diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index 8c3f4b08..1b18a823 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -271,28 +271,6 @@ } } - .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; @@ -643,20 +621,74 @@ } .roll-buttons { - display: flex; - flex-wrap: wrap; - gap: 5px; - width: 100%; margin-top: 8px; + display: flex; + flex-direction: column; + gap: 5px; button { - height: 36px; - flex: 1 1 calc(50% - 5px); - font-family: @font-body; - font-weight: 700; + height: 32px; + } - &:nth-last-child(1):nth-child(odd) { - flex-basis: 100%; + .main-buttons { + display: flex; + gap: 5px; + + button { + flex: 1; + + &.end-button { + flex: 0; + } + } + } + + .extra-button-row { + width: 100%; + display: flex; + gap: 5px; + + .image-container { + position: relative; + display: flex; + align-items: center; + justify-content: center; + + img { + height: 24px; + filter: @dark-filter; + } + + .dice-result { + position: absolute; + font-size: 18px; + color: @beige; + filter: drop-shadow(0 0 1px @light-white); + } + } + + .reload-data { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; + gap: 5px; + cursor: pointer; + padding: 5px; + transition: all 0.3s ease; + color: @golden; + background: @golden-40; + + &.failed-check { + background: @red-40; + color: @red; + } + + &.passed-check { + background: @green-40; + color: @green; + } } } } diff --git a/templates/ui/chat/parts/button-part.hbs b/templates/ui/chat/parts/button-part.hbs index b631192c..6dc39804 100644 --- a/templates/ui/chat/parts/button-part.hbs +++ b/templates/ui/chat/parts/button-part.hbs @@ -1,21 +1,40 @@
- {{#if (eq automationSettings.reload 'button')}} - - {{/if}} - {{#if areas.length}}{{/if}} - {{#if hasDamage}} - {{#if damage.active}} - - {{else}} - +
+ {{#if areas.length}}{{/if}} + {{#if hasDamage}} + {{#if damage.active}} + + {{else}} + + {{/if}} {{/if}} - {{/if}} - {{#if hasHealing}} - {{#if damage.active}} - - {{else}} - + {{#if hasHealing}} + {{#if damage.active}} + + {{else}} + + {{/if}} {{/if}} + {{#if (and hasEffect)}}{{/if}} +
+ + {{#if parent.system.hasReload}} +
+ +
+ {{#unless reloadCheckValue}} + {{localize "DAGGERHEART.ACTIONS.Reload.notRolled"}} + {{else}} + {{ifThen parent.system.reloadCheckFailed (localize "DAGGERHEART.ACTIONS.Reload.checkFailed") (localize "DAGGERHEART.ACTIONS.Reload.checkPassed")}} + {{/unless}} +
+
{{/if}} - {{#if (and hasEffect)}}{{/if}}
\ No newline at end of file diff --git a/templates/ui/chat/roll.hbs b/templates/ui/chat/roll.hbs index ab4e197e..362aeadd 100644 --- a/templates/ui/chat/roll.hbs +++ b/templates/ui/chat/roll.hbs @@ -1,15 +1,5 @@
{{title}}
- - {{#if (eq action.type 'attack')}} - {{#if needsReload}} -
- {{#if needsReload}} -

{{localize "DAGGERHEART.ACTIONS.Reload.reloadRequired"}}

- {{/if}} -
- {{/if}} - {{/if}} {{#if actionDescription}}{{> 'systems/daggerheart/templates/ui/chat/parts/description-part.hbs'}}{{/if}} {{#if hasRoll}} From a9ac8fe148204f50c7cfd29525b11272f9d2fde8 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 22 Jul 2026 03:44:10 +0200 Subject: [PATCH 4/4] Fixed so that non attack/damage actions don't error out when used (#2098) --- module/data/action/baseAction.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 05890335..7c783f05 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -429,11 +429,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } get hasDamage() { - return this.type !== 'healing' && (Boolean(this.damage.main) || !foundry.utils.isEmpty(this.damage.resources)); + return this.type !== 'healing' && (Boolean(this.damage?.main) || !foundry.utils.isEmpty(this.damage?.resources)); } get hasHealing() { - return this.type === 'healing' && !foundry.utils.isEmpty(this.damage.resources); + return this.type === 'healing' && !foundry.utils.isEmpty(this.damage?.resources); } get hasSave() {