From 829a6161ff8741d19ae4b6286ef6b3c0d079abbf Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 14 May 2026 18:46:18 -0400 Subject: [PATCH 01/73] Add hot reload configuration (#1881) --- .github/workflows/deploy.yml | 1 + system.json | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 553a1a17..4ffcc64d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -37,6 +37,7 @@ jobs: url: https://github.com/${{github.repository}} manifest: https://raw.githubusercontent.com/${{github.repository}}/v14/system.json download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip + flags.hotReload: false # Create a zip file with all files required by the module to add to the release - run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js build/tagify.css styles/daggerheart.css assets/ templates/ packs/ lang/ diff --git a/system.json b/system.json index 16a1f74b..e1daf56c 100644 --- a/system.json +++ b/system.json @@ -15,6 +15,11 @@ { "name": "WBHarry" }, + { + "name": "Supe", + "url": "https://github.com/CarlosFdez", + "discord": "supe" + }, { "name": "cptn-cosmo", "url": "https://github.com/cptn-cosmo", @@ -298,5 +303,11 @@ }, "background": "systems/daggerheart/assets/logos/FoundrybornBackgroundLogo.png", "primaryTokenAttribute": "resources.hitPoints", - "secondaryTokenAttribute": "resources.stress" + "secondaryTokenAttribute": "resources.stress", + "flags": { + "hotReload": { + "extensions": ["css", "hbs", "json"], + "paths": ["styles/daggerheart.css", "templates", "lang"] + } + } } From bc3c09fa2ec9e84cf7198547795cebb33814c916 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:01:03 -0400 Subject: [PATCH 02/73] Move scrollbar definition to global styling (#1887) --- styles/less/dialog/level-up/selections-container.less | 2 -- styles/less/dialog/level-up/summary-container.less | 2 -- styles/less/global/elements.less | 2 -- styles/less/global/feature-section.less | 2 -- styles/less/global/global.less | 5 +++++ styles/less/global/prose-mirror.less | 2 -- styles/less/sheets-settings/adversary-settings/features.less | 2 -- .../sheets-settings/environment-settings/adversaries.less | 2 -- .../less/sheets-settings/environment-settings/features.less | 2 -- styles/less/sheets/actors/adversary/actions.less | 3 --- styles/less/sheets/actors/adversary/effects.less | 3 --- styles/less/sheets/actors/adversary/sidebar.less | 3 +-- styles/less/sheets/actors/character/biography.less | 3 --- styles/less/sheets/actors/character/effects.less | 3 --- styles/less/sheets/actors/character/features.less | 3 --- styles/less/sheets/actors/character/inventory.less | 3 --- styles/less/sheets/actors/character/loadout.less | 3 --- styles/less/sheets/actors/character/sidebar.less | 4 +--- styles/less/sheets/actors/companion/effects.less | 3 --- styles/less/sheets/actors/environment/actions.less | 3 --- .../less/sheets/actors/environment/potentialAdversaries.less | 3 --- styles/less/sheets/actors/environment/sheet.less | 2 -- styles/less/sheets/actors/party/inventory.less | 3 --- styles/less/sheets/actors/party/sheet.less | 2 -- styles/less/sheets/items/domain-card.less | 2 -- styles/less/sheets/items/feature.less | 2 -- styles/less/ui/countdown/countdown-edit.less | 2 -- styles/less/ui/item-browser/item-browser.less | 2 -- styles/less/ui/settings/homebrew-settings/domains.less | 2 -- styles/less/ux/autocomplete/autocomplete.less | 2 -- styles/less/ux/tooltip/sheet.less | 3 --- 31 files changed, 7 insertions(+), 73 deletions(-) diff --git a/styles/less/dialog/level-up/selections-container.less b/styles/less/dialog/level-up/selections-container.less index 6a551865..4e8b9a48 100644 --- a/styles/less/dialog/level-up/selections-container.less +++ b/styles/less/dialog/level-up/selections-container.less @@ -5,8 +5,6 @@ .levelup-selections-container { overflow: auto; padding: 10px 0; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; max-height: 500px; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); diff --git a/styles/less/dialog/level-up/summary-container.less b/styles/less/dialog/level-up/summary-container.less index d67abff6..97353ba7 100644 --- a/styles/less/dialog/level-up/summary-container.less +++ b/styles/less/dialog/level-up/summary-container.less @@ -17,8 +17,6 @@ .levelup-summary-container { overflow: auto; padding: 10px 0; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; max-height: 700px; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 05e679b5..62896dd0 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -96,8 +96,6 @@ textarea { color: light-dark(@dark, @beige); - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } button:where(:not(.plain)) { diff --git a/styles/less/global/feature-section.less b/styles/less/global/feature-section.less index 7d5099e1..2fd4e20f 100644 --- a/styles/less/global/feature-section.less +++ b/styles/less/global/feature-section.less @@ -5,8 +5,6 @@ .tab.features { padding: 0 10px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .feature-list { display: flex; flex-direction: column; diff --git a/styles/less/global/global.less b/styles/less/global/global.less index b9af67c0..644d03b8 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -12,6 +12,11 @@ } .daggerheart.dh-style { + * { + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } + .hint { flex: 0 0 100%; margin: 0; diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index 8a663e28..8412235d 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -10,8 +10,6 @@ background-color: transparent; } .editor-content { - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; h1 { font-size: var(--font-size-32); } diff --git a/styles/less/sheets-settings/adversary-settings/features.less b/styles/less/sheets-settings/adversary-settings/features.less index 4e0f6a8f..15b1fa18 100644 --- a/styles/less/sheets-settings/adversary-settings/features.less +++ b/styles/less/sheets-settings/adversary-settings/features.less @@ -5,8 +5,6 @@ .tab.features { max-height: 450px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .add-feature-btn { width: 100%; diff --git a/styles/less/sheets-settings/environment-settings/adversaries.less b/styles/less/sheets-settings/environment-settings/adversaries.less index 1a27eaca..2ce4819a 100644 --- a/styles/less/sheets-settings/environment-settings/adversaries.less +++ b/styles/less/sheets-settings/environment-settings/adversaries.less @@ -5,8 +5,6 @@ .tab.adversaries { max-height: 450px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .add-action-btn { width: 100%; diff --git a/styles/less/sheets-settings/environment-settings/features.less b/styles/less/sheets-settings/environment-settings/features.less index d907837a..db6b544d 100644 --- a/styles/less/sheets-settings/environment-settings/features.less +++ b/styles/less/sheets-settings/environment-settings/features.less @@ -5,8 +5,6 @@ .tab.features { max-height: 450px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .add-feature-btn { width: 100%; diff --git a/styles/less/sheets/actors/adversary/actions.less b/styles/less/sheets/actors/adversary/actions.less index 00395ebd..af870d9b 100644 --- a/styles/less/sheets/actors/adversary/actions.less +++ b/styles/less/sheets/actors/adversary/actions.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/adversary/effects.less b/styles/less/sheets/actors/adversary/effects.less index 4afe2454..fbf74249 100644 --- a/styles/less/sheets/actors/adversary/effects.less +++ b/styles/less/sheets/actors/adversary/effects.less @@ -9,9 +9,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index 4e7535c1..ef99bc09 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -287,12 +287,11 @@ padding-top: 10px; padding-bottom: 20px; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); - scrollbar-width: thin; + scrollbar-gutter: stable; &:hover { overflow-y: auto; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } diff --git a/styles/less/sheets/actors/character/biography.less b/styles/less/sheets/actors/character/biography.less index b7c6ba6e..f8d56735 100644 --- a/styles/less/sheets/actors/character/biography.less +++ b/styles/less/sheets/actors/character/biography.less @@ -13,9 +13,6 @@ padding-top: 8px; padding-bottom: 20px; height: 100%; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } .characteristics-section { diff --git a/styles/less/sheets/actors/character/effects.less b/styles/less/sheets/actors/character/effects.less index ceadd05e..ae49fa2d 100644 --- a/styles/less/sheets/actors/character/effects.less +++ b/styles/less/sheets/actors/character/effects.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/character/features.less b/styles/less/sheets/actors/character/features.less index 6a6438ff..017254a3 100644 --- a/styles/less/sheets/actors/character/features.less +++ b/styles/less/sheets/actors/character/features.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/character/inventory.less b/styles/less/sheets/actors/character/inventory.less index 12f63753..c8d2b584 100644 --- a/styles/less/sheets/actors/character/inventory.less +++ b/styles/less/sheets/actors/character/inventory.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); padding: 20px 0; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/character/loadout.less b/styles/less/sheets/actors/character/loadout.less index eba55890..127d688a 100644 --- a/styles/less/sheets/actors/character/loadout.less +++ b/styles/less/sheets/actors/character/loadout.less @@ -92,9 +92,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%); padding: 20px 0; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index 0e6e3d97..e450891b 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -551,11 +551,9 @@ padding-bottom: 20px; mask-image: linear-gradient(0deg, transparent 0%, black 5%); scrollbar-gutter: stable; - scrollbar-width: thin; - + &:hover { overflow-y: auto; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } diff --git a/styles/less/sheets/actors/companion/effects.less b/styles/less/sheets/actors/companion/effects.less index 12e1d847..6d7fe061 100644 --- a/styles/less/sheets/actors/companion/effects.less +++ b/styles/less/sheets/actors/companion/effects.less @@ -9,9 +9,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/environment/actions.less b/styles/less/sheets/actors/environment/actions.less index 51385322..cc8a345a 100644 --- a/styles/less/sheets/actors/environment/actions.less +++ b/styles/less/sheets/actors/environment/actions.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/environment/potentialAdversaries.less b/styles/less/sheets/actors/environment/potentialAdversaries.less index 274362a5..f3c5776a 100644 --- a/styles/less/sheets/actors/environment/potentialAdversaries.less +++ b/styles/less/sheets/actors/environment/potentialAdversaries.less @@ -9,9 +9,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/environment/sheet.less b/styles/less/sheets/actors/environment/sheet.less index 3ea14bc7..a7c9605b 100644 --- a/styles/less/sheets/actors/environment/sheet.less +++ b/styles/less/sheets/actors/environment/sheet.less @@ -20,8 +20,6 @@ .tab { flex: 1; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; &.active { overflow: hidden; diff --git a/styles/less/sheets/actors/party/inventory.less b/styles/less/sheets/actors/party/inventory.less index ac59e1de..8af37a79 100644 --- a/styles/less/sheets/actors/party/inventory.less +++ b/styles/less/sheets/actors/party/inventory.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); padding: 20px 0; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/party/sheet.less b/styles/less/sheets/actors/party/sheet.less index 6b51de53..852b6cfc 100644 --- a/styles/less/sheets/actors/party/sheet.less +++ b/styles/less/sheets/actors/party/sheet.less @@ -20,8 +20,6 @@ .tab { flex: 1; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; scrollbar-gutter: stable; &.active { diff --git a/styles/less/sheets/items/domain-card.less b/styles/less/sheets/items/domain-card.less index a784b3a2..54378fd0 100644 --- a/styles/less/sheets/items/domain-card.less +++ b/styles/less/sheets/items/domain-card.less @@ -5,7 +5,5 @@ section.tab { height: 400px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } diff --git a/styles/less/sheets/items/feature.less b/styles/less/sheets/items/feature.less index b7493f15..f3c7cd49 100644 --- a/styles/less/sheets/items/feature.less +++ b/styles/less/sheets/items/feature.less @@ -14,7 +14,5 @@ section.tab { height: 400px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } diff --git a/styles/less/ui/countdown/countdown-edit.less b/styles/less/ui/countdown/countdown-edit.less index d6c4da93..78ad3a06 100644 --- a/styles/less/ui/countdown/countdown-edit.less +++ b/styles/less/ui/countdown/countdown-edit.less @@ -60,8 +60,6 @@ overflow-y: auto; overflow-x: hidden; max-height: 500px; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .countdown-edit-outer-container { display: flex; diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index f558a0ba..1142b8fd 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -237,8 +237,6 @@ .compendium-sidebar > .folder-list { overflow-y: auto; scrollbar-gutter: stable; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } .item-list-header, diff --git a/styles/less/ui/settings/homebrew-settings/domains.less b/styles/less/ui/settings/homebrew-settings/domains.less index da258fcd..406294ac 100644 --- a/styles/less/ui/settings/homebrew-settings/domains.less +++ b/styles/less/ui/settings/homebrew-settings/domains.less @@ -55,8 +55,6 @@ gap: 8px; max-height: 184px; overflow: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(#18162e, #f3c267) transparent; .domain-container { position: relative; diff --git a/styles/less/ux/autocomplete/autocomplete.less b/styles/less/ux/autocomplete/autocomplete.less index 7f799449..e778f0da 100644 --- a/styles/less/ux/autocomplete/autocomplete.less +++ b/styles/less/ux/autocomplete/autocomplete.less @@ -21,8 +21,6 @@ flex-direction: column; gap: 2px; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - .group { font-weight: bold; font-size: var(--font-size-14); diff --git a/styles/less/ux/tooltip/sheet.less b/styles/less/ux/tooltip/sheet.less index 59e4e638..ad774fcd 100644 --- a/styles/less/ux/tooltip/sheet.less +++ b/styles/less/ux/tooltip/sheet.less @@ -48,9 +48,6 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip), overflow-y: auto; position: relative; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - .tooltip-tag { display: flex; gap: 10px; From 46e552eb3d49bd7f104f45270fbf0dcb8b72a091 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:01:28 -0400 Subject: [PATCH 03/73] Fix scroll restoration of party members (#1886) --- module/applications/sheets/actors/party.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 403960c0..c703ad85 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -44,7 +44,10 @@ export default class Party extends DHBaseActorSheet { static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, - partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' }, + partyMembers: { + template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs', + scrollable: [''] + }, /* NOT YET IMPLEMENTED */ // projects: { // template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs', From 6b4de71a0a1d1e791aff5ad0182ba31aefc34c65 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:04:22 -0400 Subject: [PATCH 04/73] Fix compendium browser sometimes spawning behind other windows (#1885) --- module/applications/ui/itemBrowser.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 99b9a23d..d98cf2da 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -109,8 +109,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position ); - options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position; + delete options.position.zIndex; if (!userPresetPosition) { const width = noFolder === true || lite === true ? 600 : 850; From 855f4549ec8dc9962b9d93fee7e83ae0d36a14bf Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:21:05 -0400 Subject: [PATCH 05/73] [Fix] Errors when updating max hp and implement bar update animations (#1883) * Fix an error that can break the canvas when importing or resetting a character * Animate bar updates * Use non-static mix function instead --- module/canvas/placeables/token.mjs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 77c178d6..68e325c2 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -249,9 +249,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { /** @inheritDoc */ _drawBar(number, bar, data) { - const val = Number(data.value); - const pct = Math.clamp(val, 0, data.max) / data.max; - // Determine sizing const { width, height } = this.document.getSize(); const s = canvas.dimensions.uiScale; @@ -259,17 +256,19 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { const bh = 8 * (this.document.height >= 2 ? 1.5 : 1) * s; // Determine the color to use - const fillColor = - number === 0 ? foundry.utils.Color.fromRGB([1, 0, 0]) : foundry.utils.Color.fromString('#0032b1'); + const Color = foundry.utils.Color; + const fillColor = number === 0 ? Color.fromRGB([1, 0, 0]) : Color.fromString('#0032b1'); + const emptyColor = Color.fromRGB([0, 0, 0]); - // Draw the bar - const widthUnit = bw / data.max; + // Draw the bar (accounting floating point numbers from bar animations) + const widthUnit = bw / Math.ceil(data.max); bar.clear().lineStyle(s, 0x000000, 1.0); - const sections = [...Array(data.max).keys()]; - for (let mark of sections) { + const sections = [...Array(Math.ceil(data.max)).keys()]; + for (const mark of sections) { const x = mark * widthUnit; - const marked = mark + 1 <= data.value; - const color = marked ? fillColor : foundry.utils.Color.fromRGB([0, 0, 0]); + const marked = mark < Math.ceil(data.value); + const remainder = mark === Math.ceil(data.value) - 1 ? data.value % 1 : 0; + const color = !marked ? emptyColor : remainder ? emptyColor.mix(fillColor, remainder) : fillColor; if (mark === 0 || mark === sections.length - 1) { bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s); // Would like drawRoundedRect, but it's very troublsome with the corners. Leaving for now. } else { From dd2aa108710a7df024dd92cdf723b7c2405f398f Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:23:55 -0400 Subject: [PATCH 06/73] [Fix] selecting multiclass and multiple sheet issues (#1884) * Fix error with adding multiclass * Make it more card like * Fix issues with responsiveness when resized * Fix cards spilling out of container when multiple lines * Remove mask and fix regression in scrollbar --- .../applications/levelup/characterLevelup.mjs | 14 ++-- .../selections-container.less | 3 - styles/less/dialog/index.less | 7 +- styles/less/dialog/level-up/index.less | 6 ++ .../dialog/level-up/selections-container.less | 44 ++++++----- styles/less/dialog/level-up/sheet.less | 22 +++--- styles/less/global/elements.less | 1 + templates/levelup/tabs/selections.hbs | 78 +++++++++---------- 8 files changed, 90 insertions(+), 85 deletions(-) create mode 100644 styles/less/dialog/level-up/index.less diff --git a/module/applications/levelup/characterLevelup.mjs b/module/applications/levelup/characterLevelup.mjs index f7ef2ffa..e8d6cf1c 100644 --- a/module/applications/levelup/characterLevelup.mjs +++ b/module/applications/levelup/characterLevelup.mjs @@ -156,6 +156,7 @@ export default class DhCharacterLevelUp extends LevelUpBase { if (multiclasses?.[0]) { const data = multiclasses[0]; const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {}; + const subclasses = (await multiclass?.system?.fetchSubclasses()) ?? []; context.multiclass = { ...data, @@ -175,13 +176,12 @@ export default class DhCharacterLevelUp extends LevelUpBase { alreadySelected }; }) ?? [], - subclasses: - multiclass?.system?.subclasses.map(subclass => ({ - ...subclass, - uuid: subclass.uuid, - selected: data.secondaryData.subclass === subclass.uuid, - disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid - })) ?? [], + subclasses: subclasses.map(subclass => ({ + ...subclass, + uuid: subclass.uuid, + selected: data.secondaryData.subclass === subclass.uuid, + disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid + })), compendium: 'classes', limit: 1 }; diff --git a/styles/less/dialog/character-creation/selections-container.less b/styles/less/dialog/character-creation/selections-container.less index 2bbac484..24217dbf 100644 --- a/styles/less/dialog/character-creation/selections-container.less +++ b/styles/less/dialog/character-creation/selections-container.less @@ -114,9 +114,6 @@ .card-preview-container { flex: 1; - } - - .card-preview-container { border-color: light-dark(@dark-blue, @golden); } diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index eb882eeb..11d9635e 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -1,10 +1,5 @@ @import './attribution/sheet.less'; -@import './level-up/navigation-container.less'; -@import './level-up/selections-container.less'; -@import './level-up/sheet.less'; -@import './level-up/summary-container.less'; -@import './level-up/tiers-container.less'; -@import './level-up/footer.less'; +@import './level-up/index.less'; @import './resource-dice/sheet.less'; diff --git a/styles/less/dialog/level-up/index.less b/styles/less/dialog/level-up/index.less new file mode 100644 index 00000000..849a4d36 --- /dev/null +++ b/styles/less/dialog/level-up/index.less @@ -0,0 +1,6 @@ +@import './navigation-container.less'; +@import './selections-container.less'; +@import './summary-container.less'; +@import './tiers-container.less'; +@import './footer.less'; +@import './sheet.less'; diff --git a/styles/less/dialog/level-up/selections-container.less b/styles/less/dialog/level-up/selections-container.less index 4e8b9a48..8c0dbaec 100644 --- a/styles/less/dialog/level-up/selections-container.less +++ b/styles/less/dialog/level-up/selections-container.less @@ -3,10 +3,7 @@ .daggerheart.levelup { .levelup-selections-container { - overflow: auto; padding: 10px 0; - max-height: 500px; - mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); .achievement-experience-cards { display: flex; @@ -43,20 +40,22 @@ .levelup-card-selection { display: flex; - flex-wrap: wrap; justify-content: center; gap: 40px; height: 190px; + align-items: stretch; .card-preview-container { - height: 100%; + height: 190px; max-width: 200px; } .levelup-domains-selection-container { - display: flex; - flex-direction: column; - gap: 8px; + display: grid; + grid-auto-flow: column; + grid-template-rows: repeat(2, minmax(0, 1fr)); + height: 100%; + gap: 4px; .levelup-domain-selection-container { display: flex; @@ -64,6 +63,8 @@ align-items: center; position: relative; cursor: pointer; + overflow: hidden; + width: 93px; &.disabled { pointer-events: none; @@ -72,16 +73,20 @@ .levelup-domain-label { position: absolute; + left: 0; + right: 0; + bottom: 0; text-align: center; - top: 4px; background: grey; - padding: 0 12px; - border-radius: 6px; + padding: 2px 12px; z-index: 2; + line-height: 1; } img { - height: 124px; + object-fit: cover; + width: auto; + height: auto; &.svg { filter: @beige-filter; @@ -90,17 +95,18 @@ .levelup-domain-selected { position: absolute; - height: 54px; - width: 54px; + height: 40px; + width: 40px; border-radius: 50%; - border: 2px solid; - font-size: var(--font-size-48); + border: 2px solid @golden; + font-size: var(--font-size-24); display: flex; align-items: center; justify-content: center; - background-image: url(../assets/parchments/dh-parchment-light.png); - color: var(--color-dark-5); - top: calc(50% - 29px); + background: @dark-golden; + color: @golden; + top: 10px; + z-index: 2; i { position: relative; diff --git a/styles/less/dialog/level-up/sheet.less b/styles/less/dialog/level-up/sheet.less index ade7c8a9..c663f304 100644 --- a/styles/less/dialog/level-up/sheet.less +++ b/styles/less/dialog/level-up/sheet.less @@ -11,9 +11,11 @@ }); .daggerheart.levelup { - .window-content { - max-height: 960px; + .tab.active { + flex: 1; overflow: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; } div[data-application-part='form'] { @@ -22,15 +24,13 @@ gap: 8px; } - section { - .section-container { - display: flex; - flex-direction: row; - justify-content: center; - gap: 20px 8px; - margin-top: 8px; - flex-wrap: wrap; - } + .section-container { + display: flex; + flex-direction: row; + justify-content: center; + gap: 20px 8px; + margin-top: 8px; + flex-wrap: wrap; } .levelup-footer { diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 62896dd0..181bd0d3 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -802,6 +802,7 @@ .preview-image-container { width: 100%; + min-height: 0; flex-grow: 1; object-fit: cover; border-radius: 4px 4px 0 0; diff --git a/templates/levelup/tabs/selections.hbs b/templates/levelup/tabs/selections.hbs index 7ebe32bb..deb7f6c0 100644 --- a/templates/levelup/tabs/selections.hbs +++ b/templates/levelup/tabs/selections.hbs @@ -43,45 +43,6 @@ {{/if}} - {{#if (gt this.domainCards.length 0)}} -
-
- -

{{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.domainCards"}}

- -
-
- -
- -
- {{#each this.domainCards}} - {{#> "systems/daggerheart/templates/components/card-preview.hbs" this }} - {{#each this.emptySubtexts}} -
{{this}}
- {{/each}} - {{/"systems/daggerheart/templates/components/card-preview.hbs"}} - {{/each}} -
-
- {{/if}} - - {{#if (gt this.subclassCards.length 0)}} -
-
- -

{{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.subclass"}}

- -
- -
- {{#each this.subclassCards}} - {{> "systems/daggerheart/templates/levelup/parts/selectable-card-preview.hbs" img=this.img header=this.featureLabel name=this.name path=this.path selected=this.selected uuid=this.uuid isMulticlass=this.isMulticlass featureState=this.featureState disabled=this.disabled }} - {{/each}} -
-
- {{/if}} - {{#if this.multiclass}}
@@ -128,6 +89,45 @@
{{/if}} + {{#if (gt this.domainCards.length 0)}} +
+
+ +

{{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.domainCards"}}

+ +
+
+ +
+ +
+ {{#each this.domainCards}} + {{#> "systems/daggerheart/templates/components/card-preview.hbs" this }} + {{#each this.emptySubtexts}} +
{{this}}
+ {{/each}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}} + {{/each}} +
+
+ {{/if}} + + {{#if (gt this.subclassCards.length 0)}} +
+
+ +

{{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.subclass"}}

+ +
+ +
+ {{#each this.subclassCards}} + {{> "systems/daggerheart/templates/levelup/parts/selectable-card-preview.hbs" img=this.img header=this.featureLabel name=this.name path=this.path selected=this.selected uuid=this.uuid isMulticlass=this.isMulticlass featureState=this.featureState disabled=this.disabled }} + {{/each}} +
+
+ {{/if}} + {{#if this.vicious}}

{{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.vicious"}}

From 88e64531b4aa481ed00a93464354519da8d180c1 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:32:00 -0400 Subject: [PATCH 07/73] [UI] Minor updates to tag team initialization visuals (#1882) * Visual updates to tag team initialization * Center players and handlet he case where is 5 or 6 players --- module/applications/dialogs/tagTeamDialog.mjs | 3 + .../dialog/group-roll-dialog/_common.less | 4 +- .../tag-team-dialog/initialization.less | 70 +++++++++++++++---- styles/less/dialog/tag-team-dialog/sheet.less | 8 ++- .../dialogs/tagTeamDialog/initialization.hbs | 14 +++- templates/dialogs/tagTeamDialog/result.hbs | 5 +- 6 files changed, 84 insertions(+), 20 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index e06cbe48..ba76831f 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -38,6 +38,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio tag: 'form', classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'], position: { width: 550, height: 'auto' }, + window: { + icon: 'fa-solid fa-user-group' + }, actions: { toggleSelectMember: TagTeamDialog.#toggleSelectMember, startTagTeamRoll: TagTeamDialog.#startTagTeamRoll, diff --git a/styles/less/dialog/group-roll-dialog/_common.less b/styles/less/dialog/group-roll-dialog/_common.less index 41573718..b04f6893 100644 --- a/styles/less/dialog/group-roll-dialog/_common.less +++ b/styles/less/dialog/group-roll-dialog/_common.less @@ -1,9 +1,7 @@ h1 { color: light-dark(@dark-blue, @golden); - font-family: var(--dh-font-subtitle); - font-size: var(--font-size-24); + font: 700 var(--font-size-24) var(--dh-font-subtitle); text-align: center; - font-weight: 700; } header { diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index 8557d231..2d015141 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -7,39 +7,85 @@ } .daggerheart.dialog.dh-style.views.tag-team-dialog { - .initialization-container { + .initialization-container.active { + display: flex; + flex-direction: column; + gap: var(--spacer-4); + h2 { text-align: center; } .members-container { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; + display: flex; + flex-wrap: wrap; + justify-content: center; gap: 8px; + // Force 3 columns for 5 -> 6 players + &:has(> :nth-child(5)):not(:has(> :nth-child(7))) { + padding-left: 10%; + padding-right: 10%; + } + .member-container { position: relative; display: flex; + align-items: stretch; justify-content: center; + border-radius: 6px; + border: 1px solid light-dark(@dark-blue, @golden); + overflow: hidden; + height: 11.5rem; + width: 122px; &.inactive { - opacity: 0.4; + border-color: light-dark(@dark-blue-40, @golden-40); + img { + opacity: 0.4; + } } .member-name { + --shadow-color: light-dark(white, black); position: absolute; - padding: 0 2px; - border: 1px solid; - border-radius: 6px; - margin-top: 4px; - color: light-dark(@dark, @beige); - background-image: url('../assets/parchments/dh-parchment-dark.png'); + bottom: 0; + left: 0; + right: 0; + + display: flex; + flex-direction: column; + justify-content: flex-end; + min-height: 4rem; + padding: 5rem 4px var(--spacer-8) 4px; text-align: center; + + color: var(--color-text-primary); + text-shadow: 1px 1px 2px var(--shadow-color), 0 0 10px var(--shadow-color); + + // Basic "scrim" gradient + background-image: linear-gradient( + to top, + var(--shadow-color), + rgba(from var(--shadow-color) r g b / 0.834) 10.6%, + rgba(from var(--shadow-color) r g b / 0.541) 34%, + rgba(from var(--shadow-color) r g b / 0.382) 47%, + rgba(from var(--shadow-color) r g b / 0.194) 65%, + transparent 100% + ); } img { - border-radius: 6px; - border: 1px solid light-dark(@dark-blue, @golden); + object-fit: cover; + object-position: top center; + flex: 1; + } + + .leader-mark { + position: absolute; + top: 4px; + right: 4px; + text-shadow: var(--shadow-text-stroke), 0 0 20px black; } } } diff --git a/styles/less/dialog/tag-team-dialog/sheet.less b/styles/less/dialog/tag-team-dialog/sheet.less index dc8f16dc..a8dffbd2 100644 --- a/styles/less/dialog/tag-team-dialog/sheet.less +++ b/styles/less/dialog/tag-team-dialog/sheet.less @@ -1,4 +1,10 @@ -.daggerheart.dialog.dh-style.views.tag-team-dialog { +.daggerheart.dialog.dh-style.views.tag-team-dialog .window-content { + h1 { + color: light-dark(@dark-blue, @golden); + font: 700 var(--font-size-24) var(--dh-font-subtitle); + text-align: center; + } + .team-container { display: flex; gap: 16px; diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs index 7ccdf566..0b92e68e 100644 --- a/templates/dialogs/tagTeamDialog/initialization.hbs +++ b/templates/dialogs/tagTeamDialog/initialization.hbs @@ -1,13 +1,18 @@
-

{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.selectParticipants"}}

+

{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.selectParticipants"}}

@@ -30,10 +35,13 @@
-
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.openDialogForAll"}}
+
\ No newline at end of file diff --git a/templates/dialogs/tagTeamDialog/result.hbs b/templates/dialogs/tagTeamDialog/result.hbs index 79b5138e..151c8c87 100644 --- a/templates/dialogs/tagTeamDialog/result.hbs +++ b/templates/dialogs/tagTeamDialog/result.hbs @@ -32,7 +32,10 @@
- +
\ No newline at end of file From e6c27926d0aaa8ddfba7162089712b273c364f49 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 15 May 2026 09:48:41 +0200 Subject: [PATCH 08/73] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index e1daf56c..7c1321fb 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.2.4", + "version": "2.2.5", "compatibility": { "minimum": "14.359", "verified": "14.361", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.4/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.5/system.zip", "authors": [ { "name": "WBHarry" From d372f3df9b15b5e600a924eb3690b3ac52618873 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 16 May 2026 12:38:07 -0400 Subject: [PATCH 09/73] Fix creating domain cards on character sheet (#1890) --- .../sheets/api/application-mixin.mjs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 36477821..c79db99b 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -89,7 +89,7 @@ export default function DHApplicationMixin(Base) { classes: ['daggerheart', 'sheet', 'dh-style'], actions: { triggerContextMenu: DHSheetV2.#triggerContextMenu, - createDoc: DHSheetV2.#createDoc, + createDoc: DHSheetV2.#onCreateDoc, editDoc: DHSheetV2.#editDoc, deleteDoc: DHSheetV2.#deleteDoc, toChat: DHSheetV2.#toChat, @@ -97,8 +97,8 @@ export default function DHApplicationMixin(Base) { viewItem: DHSheetV2.#viewItem, toggleEffect: DHSheetV2.#toggleEffect, toggleExtended: DHSheetV2.#toggleExtended, - addNewItem: DHSheetV2.#addNewItem, - browseItem: DHSheetV2.#browseItem, + addNewItem: DHSheetV2.#onAddNewItem, + browseItem: DHSheetV2.#onBrowseItem, editAttribution: DHSheetV2.#editAttribution }, contextMenus: [ @@ -639,7 +639,7 @@ export default function DHApplicationMixin(Base) { /* Application Clicks Actions */ /* -------------------------------------------- */ - static async #addNewItem(event, target) { + static async #onAddNewItem(event, target) { const createChoice = await foundry.applications.api.DialogV2.wait({ classes: ['dh-style', 'two-big-buttons'], buttons: [ @@ -658,11 +658,11 @@ export default function DHApplicationMixin(Base) { if (!createChoice) return; - if (createChoice === 'browse') return DHSheetV2.#browseItem.call(this, event, target); - else return DHSheetV2.#createDoc.call(this, event, target); + if (createChoice === 'browse') return DHSheetV2.#onBrowseItem.call(this, event, target); + else return DHSheetV2.#onCreateDoc.call(this, event, target); } - static async #browseItem(event, target) { + static async #onBrowseItem(_event, target) { const type = target.dataset.compendium ?? target.dataset.type; const presets = { @@ -713,7 +713,7 @@ export default function DHApplicationMixin(Base) { * Create an embedded document. * @type {ApplicationClickAction} */ - static async #createDoc(event, target) { + static async #onCreateDoc(event, target) { const { documentClass, type, inVault, disabled } = target.dataset; const parentIsItem = this.document.documentName === 'Item'; const featureOnCharacter = this.document.parent?.type === 'character' && type === 'feature'; @@ -725,7 +725,7 @@ export default function DHApplicationMixin(Base) { : null : this.document; - let systemData = null; + let systemData = {}; if (featureOnCharacter) { systemData = { originItemType: this.document.type, @@ -738,15 +738,18 @@ export default function DHApplicationMixin(Base) { const data = { name: cls.defaultName({ type, parent }), - type + type, + system: systemData }; - - if (systemData) data.system = systemData; - - if (inVault) data['system.inVault'] = true; if (disabled) data.disabled = true; - if (type === 'domainCard' && parent?.system.domains?.length) { - data.system.domain = parent.system.domains[0]; + + if (type === 'domainCard') { + if (parent?.system.domains?.length) data.system.domain = parent.system.domains[0]; + if (inVault) data.system.inVault = true; + } else if (type === 'weapon') { + // Passing an empty system object to weapon causes validation failure due to attack action initialization + // todo: determine why, fix it at its source, then remove this fallback + delete data.system; } const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey }); From 47960fdd61258c4f4b12eb75877acab15e869ef3 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 17 May 2026 07:27:10 -0400 Subject: [PATCH 10/73] Fix uses of font awesome and adjust browser filter spacing (#1896) --- styles/less/global/global.less | 2 +- styles/less/ui/chat/sheet.less | 2 +- styles/less/ui/item-browser/item-browser.less | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/styles/less/global/global.less b/styles/less/global/global.less index 644d03b8..c0e7f3fc 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -38,7 +38,7 @@ } &:before { - font-family: 'Font Awesome 6 Pro'; + font-family: var(--font-awesome); content: '\f110'; position: absolute; height: 100%; diff --git a/styles/less/ui/chat/sheet.less b/styles/less/ui/chat/sheet.less index 66b539b4..fb8cc104 100644 --- a/styles/less/ui/chat/sheet.less +++ b/styles/less/ui/chat/sheet.less @@ -195,7 +195,7 @@ fieldset.daggerheart.chat { &:before, &:after { content: '\f0d8'; - font-family: 'Font Awesome 6 Pro'; + font-family: var(--font-awesome); } } &.expanded { diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index 1142b8fd..066da73b 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -125,6 +125,7 @@ .form-group { white-space: nowrap; + gap: 0; } .filter-header { @@ -168,6 +169,10 @@ } } } + + .filter-content { + margin-top: 8px; + } } .folder-list { @@ -272,7 +277,7 @@ div[data-sort-key] { &:after { - font-family: 'Font Awesome 6 Pro'; + font-family: var(--font-awesome); margin-left: 5px; } From 0492507bd1a25ae29c5d90d885c11b8bc87117ce Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 17 May 2026 07:43:55 -0400 Subject: [PATCH 11/73] Further decrease sheet opacity (#1897) --- styles/less/global/sheet.less | 4 ++-- styles/less/utils/colors.less | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index 0c400564..5ccb8788 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -4,8 +4,8 @@ // Theme handling .appTheme({ - background: @dark-blue-90; - backdrop-filter: blur(8px); + background: @dark-blue-d0; + backdrop-filter: blur(7px); }, { background: url('../assets/parchments/dh-parchment-light.png') no-repeat center; }); diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 18b981ad..80519a5b 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -51,6 +51,7 @@ --dark-blue-50: #18162e50; --dark-blue-60: #18162e60; --dark-blue-90: #18162e90; + --dark-blue-d0: #18162ed0; --semi-transparent-dark-blue: rgba(24, 22, 46, 0.33); --dark: #222222; @@ -136,6 +137,7 @@ @dark-blue-50: var(--dark-blue-50, #18162e50); @dark-blue-60: var(--dark-blue-60, #18162e60); @dark-blue-90: var(--dark-blue-90, #18162e90); +@dark-blue-d0: var(--dark-blue-d0, #18162ed0); @semi-transparent-dark-blue: var(--semi-transparent-dark-blue, rgba(24, 22, 46, 0.33)); @dark: var(--dark, #222222); From ab412367f997079911bd031ead92abed1268932c Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 17 May 2026 14:07:54 -0400 Subject: [PATCH 12/73] Add tier/type headers and filters to environments in browser (#1895) --- module/config/itemBrowserConfig.mjs | 46 ++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 3e40c97b..c87b4c4d 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -70,6 +70,49 @@ export const typeConfig = { } ] }, + environments: { + columns: [ + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular' + }, + { + key: 'system.type', + label: 'DAGGERHEART.GENERAL.type', + format: type => { + if (!type) return '-'; + + return CONFIG.DH.ACTOR.environmentTypes[type].label; + } + } + ], + filters: [ + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular', + field: 'system.api.models.actors.DhEnvironment.schema.fields.tier' + }, + { + key: 'system.type', + label: 'DAGGERHEART.GENERAL.type', + field: 'system.api.models.actors.DhEnvironment.schema.fields.type' + }, + { + key: 'system.difficulty', + name: 'difficulty.min', + label: 'DAGGERHEART.UI.ItemBrowser.difficultyMin', + field: 'system.api.models.actors.DhEnvironment.schema.fields.difficulty', + operator: 'gte' + }, + { + key: 'system.difficulty', + name: 'difficulty.max', + label: 'DAGGERHEART.UI.ItemBrowser.difficultyMax', + field: 'system.api.models.actors.DhEnvironment.schema.fields.difficulty', + operator: 'lte' + } + ] + }, items: { columns: [ { @@ -559,7 +602,8 @@ export const compendiumConfig = { id: 'environments', keys: ['environments'], label: 'DAGGERHEART.UI.ItemBrowser.folders.environments', - type: ['environment'] + type: ['environment'], + listType: 'environments' }, beastforms: { id: 'beastforms', From 98049bd76b7e0e6b1125c313c1ad192a8ee3cf1b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 19 May 2026 04:13:49 -0400 Subject: [PATCH 13/73] Remove unused resources tab styling (#1902) --- styles/less/sheets/actors/party/party-members.less | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index a29f7c88..a3ec90ec 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -2,16 +2,6 @@ @import '../../../utils/fonts.less'; @import '../../../utils/mixin.less'; -body.game:is(.performance-low, .noblur) { - .application.sheet.daggerheart.actor.dh-style.party .tab.resources .actors-list .actor-resources { - background: light-dark(@dark-blue, @dark-golden); - - .actor-name { - background: light-dark(@dark-blue, @dark-golden); - } - } -} - .application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers { overflow: auto; From d78c6b1183d5da73f439508e1826ef276271b655 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 19 May 2026 10:14:03 +0200 Subject: [PATCH 14/73] Fixed so that already destroyed companion tokens that foundry still lists are not considered (#1901) --- module/applications/hud/tokenHUD.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs index 943f3506..671b01a1 100644 --- a/module/applications/hud/tokenHUD.mjs +++ b/module/applications/hud/tokenHUD.mjs @@ -124,7 +124,9 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { const animationDuration = 500; const scene = game.scenes.get(game.user.viewedScene); /* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */ - const activeTokens = actors.flatMap(member => member.getDependentTokens({ scenes: scene }).filter(x => x._id)); + const activeTokens = actors.flatMap(member => + member.getDependentTokens({ scenes: scene }).filter(x => x._id && !x._destroyed) + ); const { x: actorX, y: actorY } = this.document; if (activeTokens.length > 0) { for (let token of activeTokens) { From ac5f84fff77a2dfee992e48e5eb49c9fcba49422 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 19 May 2026 10:16:50 +0200 Subject: [PATCH 15/73] Fixed so that the tokenHUD matches the current V14 foundry layout. Fixed so that the tooltips have the correct translations (#1894) --- templates/hud/tokenHUD.hbs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/templates/hud/tokenHUD.hbs b/templates/hud/tokenHUD.hbs index ab0872bf..b620ab11 100644 --- a/templates/hud/tokenHUD.hbs +++ b/templates/hud/tokenHUD.hbs @@ -4,10 +4,6 @@ - - {{#if canChangeLevel}} + {{#if hasCompanion}} {{/if}} @@ -49,8 +49,9 @@
{{#if isGM}} - {{/if}} @@ -119,13 +120,15 @@ {{/each}}
- {{#if canToggleCombat}} - {{/if}} From b91d943dd1f702c19b70ba1ced4b6cbcaf75557a Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 19 May 2026 10:45:31 +0200 Subject: [PATCH 16/73] Raised foundry minimum version --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index 7c1321fb..9ae54190 100644 --- a/system.json +++ b/system.json @@ -4,7 +4,7 @@ "description": "An unofficial implementation of the Daggerheart system", "version": "2.2.5", "compatibility": { - "minimum": "14.359", + "minimum": "14.361", "verified": "14.361", "maximum": "14" }, From d152bfc906aa56ba69ef2ca870856d85f1596444 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 19 May 2026 05:11:38 -0400 Subject: [PATCH 17/73] Fix party rerenders from shields and weapons (#1899) --- module/data/item/armor.mjs | 10 ---------- module/documents/actor.mjs | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index b0e4847f..21c56f9a 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -141,16 +141,6 @@ export default class DHArmor extends AttachableItem { } } - _onUpdate(a, b, c) { - super._onUpdate(a, b, c); - - if (this.actor?.type === 'character') { - for (const party of this.actor.parties) { - party.render(); - } - } - } - /** @inheritDoc */ static migrateDocumentData(source) { if (!source.system.armor) { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 5df87b6c..91bf7190 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -112,10 +112,22 @@ export default class DhpActor extends Actor { this.updateSource(update); } + /** Perform a render, debounced in order to prevent overloading repeat render requests */ + renderDebounced = foundry.utils.debounce(options => { + return this.render(options); + }, 10); + + _onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) { + super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId); + for (const party of this.parties) { + party.renderDebounced({ parts: ['partyMembers'] }); + } + } + _onUpdate(changes, options, userId) { super._onUpdate(changes, options, userId); for (const party of this.parties) { - party.render({ parts: ['partyMembers'] }); + party.renderDebounced({ parts: ['partyMembers'] }); } } @@ -134,7 +146,7 @@ export default class DhpActor extends Actor { _onDelete(options, userId) { super._onDelete(options, userId); for (const party of this.parties) { - party.render({ parts: ['partyMembers'] }); + party.renderDebounced({ parts: ['partyMembers'] }); } } From 4504379fcf6df8682acd6df2c7cf5525215a2e6b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 19 May 2026 05:13:59 -0400 Subject: [PATCH 18/73] Fix journey end calculation and hope reduction when gaining scars (#1900) --- module/applications/dialogs/deathMove.mjs | 3 ++- module/data/actor/character.mjs | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/module/applications/dialogs/deathMove.mjs b/module/applications/dialogs/deathMove.mjs index 69ff758e..4a949b99 100644 --- a/module/applications/dialogs/deathMove.mjs +++ b/module/applications/dialogs/deathMove.mjs @@ -57,6 +57,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar'); if (config.roll.fate.value <= this.actor.system.levelData.level.current) { + const maxHope = this.actor.system.resources.hope.max + this.actor.system.scars; const newScarAmount = this.actor.system.scars + 1; await this.actor.update({ system: { @@ -64,7 +65,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV } }); - if (newScarAmount >= this.actor.system.resources.hope.max) { + if (newScarAmount >= maxHope) { await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id); return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount }); } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index a1cd13e8..70f83236 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -840,12 +840,13 @@ export default class DhCharacter extends DhCreature { const newHopeMax = this.resources.hope.max + diff; const newHopeValue = Math.min(newHopeMax, this.resources.hope.value); if (newHopeValue != this.resources.hope.value) { - if (!changes.system.resources.hope) changes.system.resources.hope = { value: 0 }; - - changes.system.resources.hope = { - ...changes.system.resources.hope, - value: changes.system.resources.hope.value + newHopeValue - }; + changes.system = foundry.utils.mergeObject(changes.system ?? {}, { + resources: { + hope: { + value: (changes.system?.resources?.hope?.value ?? 0) + newHopeMax + } + } + }); } } From 6a2d09caac5c076b1ea76ecf7352f722160c82d8 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 19 May 2026 17:18:10 -0400 Subject: [PATCH 19/73] Create sourcemaps for css files (#1904) --- .gitignore | 1 + gulpfile.js | 8 +- package-lock.json | 477 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 4 files changed, 483 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 9a22c0ce..e41a67d3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Build build foundry styles/daggerheart.css +styles/daggerheart.css.map diff --git a/gulpfile.js b/gulpfile.js index 1a81f5ae..2e873ced 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,9 +1,15 @@ // Less configuration var gulp = require('gulp'); var less = require('gulp-less'); +var sourcemaps = require('gulp-sourcemaps'); gulp.task('less', function (cb) { - gulp.src('styles/daggerheart.less').pipe(less()).on('error', console.error.bind(console)).pipe(gulp.dest('styles')); + gulp.src('styles/daggerheart.less') + .pipe(sourcemaps.init()) + .pipe(less()) + .on('error', console.error.bind(console)) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('styles')); cb(); }); diff --git a/package-lock.json b/package-lock.json index 47c5dede..28223032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "autocompleter": "^9.3.2", "gulp": "^5.0.0", "gulp-less": "^5.0.0", + "gulp-sourcemaps": "^3.0.0", "rollup": "^4.40.0" }, "devDependencies": { @@ -202,6 +203,132 @@ "node": ">17.0.0" } }, + "node_modules/@gulp-sourcemaps/identity-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", + "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", + "license": "MIT", + "dependencies": { + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha512-o/EatdaGt8+x2qpb0vFLC/2Gug/xYPRXb6a+ET1wGYKozKN3krDWC/zZFZAtrzxJHuDL12mwdfEFKcKMNvc55A==", + "license": "MIT", + "dependencies": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, "node_modules/@gulpjs/messages": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", @@ -894,6 +1021,18 @@ "node": ">= 10.13.0" } }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autocompleter": { "version": "9.3.2", "resolved": "https://registry.npmjs.org/autocompleter/-/autocompleter-9.3.2.tgz", @@ -1482,6 +1621,12 @@ "node": ">= 10.13.0" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1496,6 +1641,17 @@ "node": ">= 8" } }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -1667,6 +1823,19 @@ "node": ">=8.0.0" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -1700,6 +1869,35 @@ } } }, + "node_modules/debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "license": "MIT", + "dependencies": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + } + }, + "node_modules/debug-fabulous/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1741,6 +1939,15 @@ "node": ">=0.10.0" } }, + "node_modules/detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -1905,6 +2112,58 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2106,6 +2365,21 @@ "node": ">=6" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", @@ -2176,6 +2450,16 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", @@ -2194,6 +2478,15 @@ "node": ">=0.10.0" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2820,6 +3113,86 @@ "node": ">=6" } }, + "node_modules/gulp-sourcemaps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", + "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", + "license": "ISC", + "dependencies": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gulp-sourcemaps/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/gulp-sourcemaps/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-sourcemaps/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/gulp-sourcemaps/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, "node_modules/gulplog": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", @@ -3248,6 +3621,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -3333,6 +3712,12 @@ "node": ">=0.10.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3647,6 +4032,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -3692,6 +4086,25 @@ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "dev": true }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3779,8 +4192,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mute-stdout": { "version": "2.0.0", @@ -3846,6 +4258,12 @@ "node": ">= 4.4.x" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -4827,6 +5245,12 @@ "node": ">=6.0.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/promise.series": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", @@ -5376,7 +5800,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -5390,6 +5813,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "license": "MIT", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, "node_modules/sparkles": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", @@ -5515,6 +5949,15 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-inject": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", @@ -5650,6 +6093,19 @@ "readable-stream": "3" } }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/tinyexec": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", @@ -5696,6 +6152,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5989,6 +6451,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 52bb4ce7..73a7fe99 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "autocompleter": "^9.3.2", "gulp": "^5.0.0", "gulp-less": "^5.0.0", + "gulp-sourcemaps": "^3.0.0", "rollup": "^4.40.0" }, "scripts": { From 10a608a1a548181a3d6903483a40006decc6d009 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 20 May 2026 10:49:45 -0400 Subject: [PATCH 20/73] Refocus armor slots when using number keys (#1908) --- templates/sheets/actors/character/sidebar.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index 91779e9c..313c81f9 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -44,7 +44,7 @@ {{else}}
- + / {{document.system.armorScore.max}}
From 2f589c1b8ebf0013e5c80e5e5645297c4996c554 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 20 May 2026 10:54:22 -0400 Subject: [PATCH 21/73] Minor visual tweaks to daggerheart setting dialogs (#1905) --- styles/less/global/elements.less | 66 ++++--------------- .../settings/homebrew-settings/resources.less | 2 +- styles/less/ui/settings/settings.less | 66 ++++++++++++++++++- .../settings/metagaming-settings/general.hbs | 6 +- 4 files changed, 80 insertions(+), 60 deletions(-) diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 181bd0d3..7d46d627 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -7,12 +7,12 @@ input[type='text'], input[type='number'], textarea, + file-picker, .input[contenteditable] { background: light-dark(transparent, transparent); border-radius: 6px; box-shadow: 0 4px 30px @soft-shadow; backdrop-filter: blur(9.5px); - -webkit-backdrop-filter: blur(9.5px); outline: 2px solid transparent; color: light-dark(@dark-blue, @golden); border: 1px solid light-dark(@dark, @beige); @@ -98,7 +98,7 @@ color: light-dark(@dark, @beige); } - button:where(:not(.plain)) { + button:where(:not(.plain, color-picker *, file-picker *)) { background: light-dark(transparent, @golden); border: 1px solid light-dark(@dark-blue, @dark-blue); color: light-dark(@dark-blue, @dark-blue); @@ -252,6 +252,15 @@ text-shadow: 0 0 1px currentColor, 0 0 1px currentColor, 0 0 8px light-dark(@dark-blue, @golden); } + file-picker, color-picker { + > input[type=text] { + background: transparent; + border: none; + outline: none; + backdrop-filter: unset; + } + } + fieldset { align-items: center; margin-top: 5px; @@ -597,59 +606,6 @@ } } -.application.setting.dh-style { - h2, - h3, - h4 { - margin: 8px 0 4px; - text-align: center; - } - - footer { - margin-top: 8px; - display: flex; - gap: 8px; - - button { - flex: 1; - } - } - - .form-group { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.25rem 0.5rem; - flex-wrap: wrap; - - label { - font-size: var(--font-size-14); - font-weight: normal; - } - - .form-fields { - display: flex; - gap: 4px; - align-items: center; - } - - &.setting-two-values { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 0.25rem 0.5rem; - - .form-group { - justify-content: end; - flex-wrap: nowrap; - } - - .hint { - grid-column: 1 / -1; - } - } - } -} - .system-daggerheart { .tagify { background: light-dark(transparent, transparent); diff --git a/styles/less/ui/settings/homebrew-settings/resources.less b/styles/less/ui/settings/homebrew-settings/resources.less index 5333e54d..1184904b 100644 --- a/styles/less/ui/settings/homebrew-settings/resources.less +++ b/styles/less/ui/settings/homebrew-settings/resources.less @@ -24,7 +24,7 @@ .resource-icons-container { display: flex; justify-content: space-between; - gap: 8px; + gap: 10px; width: 100%; .resource-icon-container { diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index 34f17d53..d08f74e6 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -1,6 +1,67 @@ @import '../../utils/colors.less'; .daggerheart.dh-style.setting { + --color-form-label: var(--color-text-primary); + + h2, + h3, + h4 { + margin: 8px 0 4px; + text-align: center; + } + + footer { + margin-top: 8px; + display: flex; + gap: 8px; + + button { + flex: 1; + } + } + + .standard-form { + gap: var(--spacer-8); + .form-group .form-fields { + width: unset; + } + } + + .form-group { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.25rem 0.5rem; + flex-wrap: wrap; + + label { + font-size: var(--font-size-14); + font-weight: normal; + line-height: var(--input-height); + } + + .form-fields { + display: flex; + gap: 4px; + align-items: center; + } + + &.setting-two-values { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.25rem 0.5rem; + + .form-group { + justify-content: end; + flex-wrap: nowrap; + } + + .hint { + grid-column: 1 / -1; + } + } + } + fieldset { display: flex; flex-direction: column; @@ -19,7 +80,10 @@ &.three-columns { display: grid; grid-template-columns: 1fr 1fr 1fr; - gap: 2px; + gap: 4px; + .form-group label { + line-height: unset; + } } &.six-columns { diff --git a/templates/settings/metagaming-settings/general.hbs b/templates/settings/metagaming-settings/general.hbs index 775ff016..9f5bd514 100644 --- a/templates/settings/metagaming-settings/general.hbs +++ b/templates/settings/metagaming-settings/general.hbs @@ -1,4 +1,4 @@ -
+
{{formGroup settingFields.schema.fields.hideObserverPermissionInChat value=settingFields._source.hideObserverPermissionInChat localize=true}} - {{formGroup settingFields.schema.fields.hidePartyStats value=settingFields._source.hidePartyStats localize=true}} -
\ No newline at end of file + {{formGroup settingFields.schema.fields.hidePartyStats value=settingFields._source.hidePartyStats localize=true}} +
From b23095cb2f2a16ae2ac63c77166fdb150fc52a14 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 20 May 2026 12:45:42 -0400 Subject: [PATCH 22/73] [Fix] finishing levelup with a multiclass (#1906) * Fix finishing levelup with a multiclass * Fix removal when de-leveling * Also delete multiclass related stuff if reducing below the minimum multiclass level --- .../applications/sheets/actors/character.mjs | 2 +- module/data/item/subclass.mjs | 54 ++++++++----------- module/documents/actor.mjs | 42 ++++++++------- 3 files changed, 47 insertions(+), 51 deletions(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index f40c144a..2a557411 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -718,7 +718,7 @@ export default class CharacterSheet extends DHBaseActorSheet { ? { 'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', - value: this.document.system.class.value._stats.compendiumSource + value: this.document.system.class.value?._stats.compendiumSource } } : undefined, diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 12d85c1e..ecf72de3 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -56,38 +56,30 @@ export default class DHSubclass extends BaseDataItem { if (allowed === false) return; if (this.actor?.type === 'character') { - const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `Item.${data._id}`; - if (this.actor.system.class.subclass) { - if (this.actor.system.multiclass.subclass) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent')); - return false; - } else { - const multiclass = this.actor.items.find(x => x.type === 'class' && x.system.isMulticlass); - if (!multiclass) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingMulticlass')); - return false; - } + const { value: actorClass, subclass: existingSubclass } = this.actor.system.class; + const { value: multiclass, subclass: existingMultisubclass } = this.actor.system.multiclass; + if (!actorClass && !multiclass) { + ui.notifications.warn('DAGGERHEART.UI.Notifications.missingClass', { localize: true }); + return false; + } + if (existingSubclass && existingMultisubclass) { + ui.notifications.warn('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent', { localize: true }); + return false; + } + if (existingSubclass && !multiclass) { + ui.notifications.warn('DAGGERHEART.UI.Notifications.missingMulticlass', { localize: true }); + return false; + } - if (multiclass.system.subclasses.every(x => x.uuid !== dataUuid)) { - ui.notifications.error( - game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInMulticlass') - ); - return false; - } - - await this.updateSource({ isMulticlass: true }); - } - } else { - const actorClass = this.actor.items.find(x => x.type === 'class' && !x.system.isMulticlass); - if (!actorClass) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass')); - return false; - } - - if ((await actorClass.system.fetchSubclasses()).every(x => x.uuid !== dataUuid)) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass')); - return false; - } + const match = [multiclass, actorClass].find( + c => c && (c._stats.compendiumSource ?? c.uuid) === this.linkedClass + ); + if (!match) { + const key = multiclass ? 'subclassNotInMulticlass' : 'subclassNotInClass'; + ui.notifications.warn(`DAGGERHEART.UI.Notifications.${key}`, { localize: true }); + return false; + } else if (match.system.isMulticlass) { + await this.updateSource({ isMulticlass: true }); } } } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 91bf7190..e4c11a5c 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -153,10 +153,13 @@ export default class DhpActor extends Actor { async updateLevel(newLevel) { if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return; + const tiers = Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers); + const maxLevel = tiers.reduce((acc, tier) => Math.max(acc, tier.levels.end), 0); + const multiclassMinLevel = Math.min( + maxLevel, + ...tiers.filter(t => t.options.multiclass).map(t => t.levels.start) + ); if (newLevel > this.system.levelData.level.current) { - const maxLevel = Object.values( - game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers - ).reduce((acc, tier) => Math.max(acc, tier.levels.end), 0); if (newLevel > maxLevel) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.tooHighLevel')); } @@ -231,18 +234,19 @@ export default class DhpActor extends Actor { this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass }); } - if (multiclass) { - const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid); - const multiclassFeatures = this.items.filter( - x => x.system.originItemType === 'class' && x.system.multiclassOrigin - ); - const subclassFeatures = this.items.filter( - x => x.system.originItemType === 'subclass' && x.system.multiclassOrigin + // Remove multiclass if we're removing a multiclass feature or if we're below the multiclass minimum level + // Multclasses cannot be manually removed on the sheet, so this allows recovering in the case of errors + if (multiclass || newLevel < multiclassMinLevel) { + const multiclassItems = this.items.filter( + x => + x.uuid === multiclass?.itemUuid || + x.system.isMulticlass || + (['class', 'subclass'].includes(x.system.originItemType) && x.system.multiclassOrigin) ); this.deleteEmbeddedDocuments( 'Item', - [multiclassItem, ...multiclassFeatures, ...subclassFeatures].map(x => x.id) + multiclassItems.map(x => x.id) ); this.update({ @@ -281,6 +285,7 @@ export default class DhpActor extends Actor { async levelUp(levelupData) { const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto; + const getStatsWithSource = document => ({ ...(document._stats ?? {}), compendiumSource: document.uuid }); const levelups = {}; for (var levelKey of Object.keys(levelupData)) { @@ -393,8 +398,8 @@ export default class DhpActor extends Actor { const embeddedItem = await this.createEmbeddedDocuments('Item', [ { ...multiclassData, - uuid: multiclassItem.uuid, - _stats: multiclassItem._stats, + uuid: multiclassItem.uuid, // todo: replace with setting an id and using keepId + _stats: getStatsWithSource(multiclassItem), system: { ...multiclassData.system, features: multiclassData.system.features.filter(x => x.type !== 'hope'), @@ -407,8 +412,8 @@ export default class DhpActor extends Actor { await this.createEmbeddedDocuments('Item', [ { ...subclassData, - uuid: subclassItem.uuid, - _stats: subclassItem._stats, + uuid: subclassItem.uuid, // todo: replace with setting an id and using keepId + _stats: getStatsWithSource(subclassItem), system: { ...subclassData.system, isMulticlass: true @@ -428,8 +433,8 @@ export default class DhpActor extends Actor { const embeddedItem = await this.createEmbeddedDocuments('Item', [ { ...cardData, - uuid: cardItem.uuid, - _stats: cardItem._stats, + uuid: cardItem.uuid, // todo: replace with setting an id and using keepId + _stats: getStatsWithSource(cardItem), system: { ...cardData.system, inVault: true @@ -450,8 +455,7 @@ export default class DhpActor extends Actor { const embeddedItem = await this.createEmbeddedDocuments('Item', [ { ...cardData, - uuid: cardItem.uuid, - _stats: cardItem._stats, + _stats: getStatsWithSource(cardItem), system: { ...cardData.system, inVault: true From b145f515d0441ccf34b00b17cca83dd7bb9bf930 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 20 May 2026 21:43:11 +0200 Subject: [PATCH 23/73] Fixed so that a non-existing class link uuid on a subclass doesn't make the subclass filter error (#1910) --- module/config/itemBrowserConfig.mjs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index c87b4c4d..ae5fa71b 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -443,10 +443,12 @@ export const typeConfig = { const list = []; for (const item of items.filter(item => item.system.linkedClass)) { const linkedClass = await foundry.utils.fromUuid(item.system.linkedClass); - list.push({ - value: linkedClass.uuid, - label: linkedClass.name - }); + if (linkedClass) { + list.push({ + value: linkedClass.uuid, + label: linkedClass.name + }); + } } return list.reduce((a, c) => { From b631525b6e989c6334250ae9ed368388e8c1d892 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 20 May 2026 21:44:50 +0200 Subject: [PATCH 24/73] Fixed so that Reaction roll chat messages do not mention 'withHope'/'withFear' (#1912) --- templates/ui/chat/parts/roll-part.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index 78f4dcd9..14e3eaa6 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -6,7 +6,7 @@ {{#if roll.isCritical}} {{localize "DAGGERHEART.GENERAL.criticalShort"}} {{else}} - {{#if (and roll.dHope (not (eq roll.type "reaction")))}} + {{#if (and roll.dHope (not (eq roll.options.roll.type "reaction")))}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}} {{/if}} {{/if}} From ed53d9ed4cfefda31288afcf917d6156f10d16e7 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 20 May 2026 16:18:40 -0400 Subject: [PATCH 25/73] [Feature] Add way to delete unbound character creation items (#1907) * Add way to delete unbound character creation items * Temporarily reduce functionality * Fixed missing fetchSubclass call * Revert "Fixed missing fetchSubclass call" This reverts commit 4fc9ee39b6e9b246ee0a05be3faa4a38c16251f6. --------- Co-authored-by: WBHarry --- .../applications/sheets/actors/character.mjs | 58 +++++++++++++++++++ templates/sheets/actors/character/header.hbs | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 2a557411..5f6c854b 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -57,6 +57,14 @@ export default class CharacterSheet extends DHBaseActorSheet { } ], contextMenus: [ + { + handler: CharacterSheet.#getCreationMainContextOptions, + selector: '.character-details [data-action="editDoc"]', + options: { + parentClassHooks: false, + fixed: true + } + }, { handler: CharacterSheet.#getDomainCardContextOptions, selector: '[data-item-uuid][data-type="domainCard"]', @@ -319,6 +327,56 @@ export default class CharacterSheet extends DHBaseActorSheet { /* Context Menu */ /* -------------------------------------------- */ + static #getCreationMainContextOptions() { + /** Returns true if the item is managed by the level up wizard. Such items shouldn't allow things like manual removal */ + function isItemWizardManaged(item) { + const actor = item?.actor; + if (!actor) return false; + + // If levelup automation is off in general or for this character, all items are unmanaged + // This is disabled until we have proper granted feature removal, for now this feature is to correct errors + // const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto; + // if (!levelupAuto) return false; + + // Core items aren't part of levelup data. TODO: add some way to flag a specific character as no auto leveling + const classPair = actor.system.class; + const coreItems = [actor.system.ancestry, actor.system.community, classPair?.value, classPair?.subclass]; + if (coreItems.includes(item)) return true; + + const levelups = Object.values(actor.system.levelData?.levelups) ?? []; + const uuid = item.uuid; + const sourceUuid = item._stats.compendiumSource; // on older characters this may be missing + return levelups.some(data => { + if (item.type === 'subclass') { + const selectedSubclasses = data.selections.map(s => s.secondaryData?.subclass).filter(s => !!s); + return sourceUuid + ? selectedSubclasses.includes(sourceUuid) + : selectedSubclasses.length && item.system.isMulticlass; + } + + const matchesCard = data.achievements.domainCards.some(i => i.itemUuid === uuid); + const matchesSelection = data.selections.some(s => s.itemUuid === uuid); + return matchesCard || matchesSelection; + }); + } + + return [ + { + label: 'CONTROLS.CommonDelete', + icon: 'fa-solid fa-trash', + visible: target => { + const doc = getDocFromElementSync(target); + return doc?.isOwner && !isItemWizardManaged(doc); + }, + callback: async (target, event) => { + const doc = await getDocFromElement(target); + if (event.shiftKey) return doc.delete(); + else return doc.deleteDialog(); + } + } + ]; + } + /** * Get the set of ContextMenu options for DomainCards. * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs index dfc9af16..459911af 100644 --- a/templates/sheets/actors/character/header.hbs +++ b/templates/sheets/actors/character/header.hbs @@ -64,7 +64,7 @@ {{/if}} - {{#if document.system.multiclass.value}} + {{#if (or document.system.multiclass.value document.system.multiclass.subclass)}}
{{#if document.system.multiclass.value}} {{document.system.multiclass.value.name}} From da06381748c1f876860130a25bf787445be510b3 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 20 May 2026 23:08:18 +0200 Subject: [PATCH 26/73] [Fix] Remove System Slugify (#1913) --- module/applications/settings/homebrewSettings.mjs | 7 +++---- module/helpers/utils.mjs | 4 ---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 9f0b22c4..40ea0301 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -1,6 +1,5 @@ import { DhHomebrew } from '../../data/settings/_module.mjs'; import { Resource } from '../../data/settings/Homebrew.mjs'; -import { slugify } from '../../helpers/utils.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -403,12 +402,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli const domainName = button.form.elements.domainName.value; if (!domainName) return; - const newSlug = slugify(domainName); + const newSlug = domainName.slugify(); const existingDomains = [ ...Object.values(this.settings.domains), ...Object.values(CONFIG.DH.DOMAIN.domains) ]; - if (existingDomains.find(x => slugify(game.i18n.localize(x.label)) === newSlug)) { + if (existingDomains.find(x => x.id === newSlug)) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.domains.duplicateDomain')); return; } @@ -529,7 +528,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli const identifier = button.form.elements.identifier.value; if (!identifier) return; - const sluggedIdentifier = slugify(identifier); + const sluggedIdentifier = identifier.slugify(); await this.settings.updateSource({ [`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier) diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 90937db4..3452640c 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -453,10 +453,6 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) { await actor.createEmbeddedDocuments('Item', effectData); } -export const slugify = name => { - return name.toLowerCase().replaceAll(' ', '-').replaceAll('.', ''); -}; - export function shuffleArray(array) { let currentIndex = array.length; while (currentIndex != 0) { From f4c21a6a1b040363040e5deff99033d646f191fe Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 21 May 2026 01:33:07 +0200 Subject: [PATCH 27/73] [Fix] CompendiumBrowser Pack Toggling (#1909) * Fixed so that CompendiumBrowserSettings saves source/pack names as slugified version to avoid foundrdy not saving names with dots in the middle * Updated excludedPacks with another layer of TypedObjectField * Renmamed variable * Update module/applications/dialogs/CompendiumBrowserSettings.mjs Co-authored-by: Carlos Fernandez --------- Co-authored-by: Carlos Fernandez --- .../dialogs/CompendiumBrowserSettings.mjs | 26 ++++++++++--------- module/data/compendiumBrowserSettings.mjs | 14 +++++----- module/helpers/utils.mjs | 1 - .../compendiumBrowserSettingsDialog/packs.hbs | 2 +- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/module/applications/dialogs/CompendiumBrowserSettings.mjs b/module/applications/dialogs/CompendiumBrowserSettings.mjs index bef54a6f..b0d83fb3 100644 --- a/module/applications/dialogs/CompendiumBrowserSettings.mjs +++ b/module/applications/dialogs/CompendiumBrowserSettings.mjs @@ -50,7 +50,7 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi const excludedSourceData = this.browserSettings.excludedSources; const excludedPackData = this.browserSettings.excludedPacks; context.typePackCollections = game.packs.reduce((acc, pack) => { - const { type, label, packageType, packageName: basePackageName, id } = pack.metadata; + const { type, label, packageType, packageName: basePackageName, name, id } = pack.metadata; if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc; const isWorldPack = packageType === 'world'; @@ -68,13 +68,15 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi if (!acc[type].sources[packageName]) acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] }; - const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type); + const included = + !excludedPackData[packageName] || + !excludedPackData[packageName][name]?.excludedDocumentTypes.includes(type); acc[type].sources[packageName].packs.push({ - pack: id, + name, type, label: id === game.system.id ? game.system.title : game.i18n.localize(label), - checked: checked + checked: included }); return acc; @@ -106,16 +108,16 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi toggleTypedPack(event) { event.stopPropagation(); - const { type, pack } = event.target.dataset; - const currentlyExcluded = this.browserSettings.excludedPacks[pack] - ? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type) + const { type, source, packName } = event.target.dataset; + const currentlyExcluded = this.browserSettings.excludedPacks[source]?.[packName] + ? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.includes(type) : false; - if (!this.browserSettings.excludedPacks[pack]) - this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] }; - this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded - ? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type) - : [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type]; + this.browserSettings.excludedPacks[source] ??= {}; + this.browserSettings.excludedPacks[source][packName] ??= { excludedDocumentTypes: [] }; + this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes = currentlyExcluded + ? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.filter(x => x !== type) + : [...(this.browserSettings.excludedPacks[source][packName]?.excludedDocumentTypes ?? []), type]; this.render(); } diff --git a/module/data/compendiumBrowserSettings.mjs b/module/data/compendiumBrowserSettings.mjs index ea71c439..ed70c88f 100644 --- a/module/data/compendiumBrowserSettings.mjs +++ b/module/data/compendiumBrowserSettings.mjs @@ -11,11 +11,13 @@ export default class CompendiumBrowserSettings extends foundry.abstract.DataMode }) ), excludedPacks: new fields.TypedObjectField( - new fields.SchemaField({ - excludedDocumentTypes: new fields.ArrayField( - new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES }) - ) - }) + new fields.TypedObjectField( + new fields.SchemaField({ + excludedDocumentTypes: new fields.ArrayField( + new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES }) + ) + }) + ) ) }; } @@ -28,7 +30,7 @@ export default class CompendiumBrowserSettings extends foundry.abstract.DataMode const excludedSourceData = this.excludedSources[packageName]; if (excludedSourceData && excludedSourceData.excludedDocumentTypes.includes(pack.metadata.type)) return true; - const excludedPackData = this.excludedPacks[item.pack]; + const excludedPackData = this.excludedPacks[packageName]?.[pack.metadata.name]; if (excludedPackData && excludedPackData.excludedDocumentTypes.includes(pack.metadata.type)) return true; return false; diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 3452640c..7bc5fa25 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -449,7 +449,6 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) { effects: data.effects?.map(effect => effect.toObject()) }); } - await actor.createEmbeddedDocuments('Item', effectData); } diff --git a/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs b/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs index dcda8108..ca2739b2 100644 --- a/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs +++ b/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs @@ -22,7 +22,7 @@
{{#each source.packs as |pack|}}
- +
{{/each}} From d782b2525423182a299ad9e2235f7076647906dd Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 21 May 2026 01:38:31 +0200 Subject: [PATCH 28/73] [Feature] Class/Multiclass Feature Split (#1911) * Changed so that multiclass features and multiclassSubclass features are displayed in separate fieldsets from the base class features in the character sheet * Changed to tertiaries for class/multiclass feature divide --- module/data/actor/character.mjs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 70f83236..10d53c13 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -577,6 +577,8 @@ export default class DhCharacter extends DhCreature { communityFeatures = [], classFeatures = [], subclassFeatures = [], + multiclassFeatures = [], + multiclassSubclassFeatures = [], companionFeatures = [], features = []; @@ -586,9 +588,9 @@ export default class DhCharacter extends DhCreature { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) { communityFeatures.push(item); } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) { - classFeatures.push(item); + (item.system.multiclassOrigin ? multiclassFeatures : classFeatures).push(item); } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { - subclassFeatures.push(item); + (item.system.multiclassOrigin ? multiclassSubclassFeatures : subclassFeatures).push(item); } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) { companionFeatures.push(item); } else if (item.type === 'feature' && !item.system.type) { @@ -617,6 +619,24 @@ export default class DhCharacter extends DhCreature { type: 'subclass', values: subclassFeatures }, + ...(multiclassFeatures.length + ? { + multiclassFeatures: { + title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`, + type: 'multiclass', + values: multiclassFeatures + } + } + : {}), + ...(multiclassSubclassFeatures.length + ? { + multiclassSubclassFeatures: { + title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`, + type: 'multiclassSubclass', + values: multiclassSubclassFeatures + } + } + : {}), companionFeatures: { title: game.i18n.localize('DAGGERHEART.ACTORS.Character.companionFeatures'), type: 'companion', From 273f66678496fdde7478e988c54723a076710b12 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 21 May 2026 01:44:13 +0200 Subject: [PATCH 29/73] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 9ae54190..d28b38b7 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.2.5", + "version": "2.2.6", "compatibility": { "minimum": "14.361", "verified": "14.361", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.5/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.6/system.zip", "authors": [ { "name": "WBHarry" From bae9006f64c0639cffd32957d0218ae7b9a39d8a Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 21 May 2026 19:15:30 +0200 Subject: [PATCH 30/73] Raised foundry verified version --- system.json | 2 +- .../ui/sceneNavigation/scene-navigation.hbs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/system.json b/system.json index d28b38b7..613c26e7 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.2.6", "compatibility": { "minimum": "14.361", - "verified": "14.361", + "verified": "14.362", "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart", diff --git a/templates/ui/sceneNavigation/scene-navigation.hbs b/templates/ui/sceneNavigation/scene-navigation.hbs index 5bdbbdad..1b65c289 100644 --- a/templates/ui/sceneNavigation/scene-navigation.hbs +++ b/templates/ui/sceneNavigation/scene-navigation.hbs @@ -12,15 +12,20 @@ {{/with}} {{#if scenes.levels}} - + {{#each scenes.levels}}
  • - {{#with button}} - - {{/with}}
    {{ name }} + {{#if users}} +
      + {{#each users}} +
    • {{ letter }}
    • + {{/each}} +
    + {{/if}}
  • {{/each}} @@ -40,7 +45,8 @@ {{#*inline ".scene"}}
  • -
    +
    {{ name }} {{#if users}}
      From 53e8da77c6127b4df34373b50147d6e9de229913 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 23 May 2026 05:52:23 -0400 Subject: [PATCH 31/73] Use the main deleteDoc handler instead of the party sheet specific one (#1920) --- module/applications/sheets/actors/party.mjs | 20 ------------------- .../global/partials/inventory-item-V2.hbs | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index c703ad85..cec1e1f0 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -26,7 +26,6 @@ export default class Party extends DHBaseActorSheet { actions: { openDocument: Party.#openDocument, deletePartyMember: Party.#deletePartyMember, - deleteItem: Party.#deleteItem, toggleHope: Party.#toggleHope, toggleHitPoints: Party.#toggleHitPoints, toggleStress: Party.#toggleStress, @@ -509,23 +508,4 @@ export default class Party extends DHBaseActorSheet { const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid); await this.document.update({ 'system.partyMembers': newMembersList }); } - - static async #deleteItem(event, target) { - const doc = await getDocFromElement(target.closest('.inventory-item')); - if (!event.shiftKey) { - const confirmed = await foundry.applications.api.DialogV2.confirm({ - window: { - title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: game.i18n.localize('TYPES.Actor.party'), - name: doc.name - }) - }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name }) - }); - - if (!confirmed) return; - } - - this.document.deleteEmbeddedDocuments('Item', [doc.id]); - } } diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index d76f2897..9330386b 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -117,7 +117,7 @@ Parameters: {{#if (not isActor)}} - + {{else if (eq type 'adversary')}} From 2931377d53e1f67d05c9934b42a1ff0589b2370d Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 23 May 2026 05:53:44 -0400 Subject: [PATCH 32/73] Remove edit and remove icons from adversary and party features (#1919) --- templates/sheets/actors/adversary/features.hbs | 1 + templates/sheets/actors/environment/features.hbs | 1 + .../sheets/global/partials/inventory-fieldset-items-V2.hbs | 3 +++ templates/sheets/global/partials/inventory-item-V2.hbs | 4 +++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/templates/sheets/actors/adversary/features.hbs b/templates/sheets/actors/adversary/features.hbs index d320b0d8..3b495e74 100644 --- a/templates/sheets/actors/adversary/features.hbs +++ b/templates/sheets/actors/adversary/features.hbs @@ -6,6 +6,7 @@ type='feature' collection=@root.features hideContextMenu=true + hideModifyControls=true canCreate=@root.editable showActions=@root.editable }} diff --git a/templates/sheets/actors/environment/features.hbs b/templates/sheets/actors/environment/features.hbs index 3fd512da..35fcb038 100644 --- a/templates/sheets/actors/environment/features.hbs +++ b/templates/sheets/actors/environment/features.hbs @@ -9,6 +9,7 @@ type='feature' collection=@root.features hideContextMenu=true + hideModifyControls=true canCreate=@root.editable showActions=@root.editable }} diff --git a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs index 3f58b80b..db2fb6b7 100644 --- a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs +++ b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs @@ -19,6 +19,8 @@ Parameters: - showLabels {boolean} : If true, show label-tags else show simple tags. - hideTooltip {boolean} : If true, disables the tooltip on the item image. - hideControls {boolean} : If true, hides the controls inside inventory-item partials. +- hideContextMenu {boolean}: If true, hides the context menu dropdown button +- hideModifyControls {boolean}: If true, hides the edit and delete options - hideDescription {boolean} : If true, hides the item's description. - hideResources {boolean} : If true, hides the item's resources. - showActions {boolean} : If true show feature's actions. @@ -59,6 +61,7 @@ Parameters: actorType=(ifThen ../actorType ../actorType @root.document.type) hideControls=../hideControls hideContextMenu=../hideContextMenu + hideModifyControls=../hideModifyControls isActor=../isActor categoryAdversary=../categoryAdversary hideTooltip=../hideTooltip diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index 9330386b..523e9304 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -12,6 +12,8 @@ Parameters: - hideTags {boolean} : If true, hide simple-tags else show simple-tags. - hideTooltip {boolean} : If true, disables the tooltip on the item image. - hideControls {boolean} : If true, hides the controls inside inventory-item partials. +- hideContextMenu {boolean}: If true, hides the context menu dropdown button +- hideModifyControls {boolean}: If true, hides the edit and delete options (todo: swap to show, only party cares to show this) - hideDescription {boolean} : If true, hides the item's description. - hideResources {boolean} : If true, hides the item's resources. - showActions {boolean} : If true show feature's actions. @@ -112,7 +114,7 @@ Parameters: - {{else if @root.editable}} + {{else if (and @root.editable (not hideModifyControls))}} From e4a3f105dcd99b7c58fe83d18ddf612831c6801c Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 23 May 2026 12:16:25 +0200 Subject: [PATCH 33/73] [Feature] V14 Cleanup (#1918) * Fixedin PrototypeToken preview * Fixed translations * Fixed tokenSize linking to token.depth * Fixed beastform depth * Raised foundry version --- lang/en.json | 9 ++- module/data/activeEffect/beastformEffect.mjs | 4 +- module/data/item/beastform.mjs | 8 ++- module/documents/scene.mjs | 4 +- module/documents/token.mjs | 5 +- module/documents/tokenManager.mjs | 1 + system.json | 2 +- templates/hud/tokenHUD.hbs | 9 ++- .../token-config/appearance.hbs | 61 +++++++++++++------ templates/sheets/items/beastform/settings.hbs | 7 +++ .../ui/combatTracker/combatTrackerSection.hbs | 8 +-- 11 files changed, 85 insertions(+), 33 deletions(-) diff --git a/lang/en.json b/lang/en.json index 0a9448c4..a06c46c2 100755 --- a/lang/en.json +++ b/lang/en.json @@ -778,7 +778,9 @@ "title": "Group Roll" }, "TokenConfig": { - "actorSizeUsed": "Actor size is set, determining the dimensions" + "actorSizeUsed": "Actor size is set, determining the dimensions", + "tokenSize": "Token Size", + "sizeCategory": "Size Category" } }, "CLASS": { @@ -2565,10 +2567,11 @@ "tokenImg": { "label": "Token Image" }, "tokenRingImg": { "label": "Subject Texture" }, "tokenSize": { - "placeholder": "Using character dimensions", - "disabledPlaceholder": "Set by character size", + "placeholder": "Token Size", + "disabledPlaceholder": "Token Size", "height": { "label": "Height" }, "width": { "label": "Width" }, + "depth": { "label": "Depth" }, "scale": { "label": "Token Scale" } }, "evolved": { diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 128c0c52..0fbea122 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -35,6 +35,7 @@ export default class BeastformEffect extends BaseEffect { static migrateData(source) { if (!source.characterTokenData.tokenSize.height) source.characterTokenData.tokenSize.height = 1; if (!source.characterTokenData.tokenSize.width) source.characterTokenData.tokenSize.width = 1; + if (!source.characterTokenData.tokenSize.depth) source.characterTokenData.tokenSize.depth = 1; return super.migrateData(source); } @@ -52,7 +53,8 @@ export default class BeastformEffect extends BaseEffect { if (this.parent.parent.type === 'character') { const baseUpdate = { height: this.characterTokenData.tokenSize.height, - width: this.characterTokenData.tokenSize.width + width: this.characterTokenData.tokenSize.width, + depth: this.characterTokenData.tokenSize.depth }; const update = { ...baseUpdate, diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index d30d6046..ee9d9839 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -51,7 +51,8 @@ export default class DHBeastform extends BaseDataItem { }), scale: new fields.NumberField({ nullable: false, min: 0.2, max: 3, step: 0.05, initial: 1 }), height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }), - width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }) + width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }), + depth: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }) }), mainTrait: new fields.StringField({ required: true, @@ -192,7 +193,8 @@ export default class DHBeastform extends BaseDataItem { tokenSize: { scale: this.parent.parent.prototypeToken.texture.scaleX, height: this.parent.parent.prototypeToken.height, - width: this.parent.parent.prototypeToken.width + width: this.parent.parent.prototypeToken.width, + depth: this.parent.parent.prototypeToken.depth } }, advantageOn: this.advantageOn, @@ -211,10 +213,12 @@ export default class DHBeastform extends BaseDataItem { : null; const width = autoTokenSize ?? this.tokenSize.width; const height = autoTokenSize ?? this.tokenSize.height; + const depth = autoTokenSize ?? this.tokenSize.depth; const prototypeTokenUpdate = { height, width, + depth, texture: { src: this.tokenImg, scaleX: this.tokenSize.scale, diff --git a/module/documents/scene.mjs b/module/documents/scene.mjs index 59b8091f..bf700610 100644 --- a/module/documents/scene.mjs +++ b/module/documents/scene.mjs @@ -20,7 +20,7 @@ export default class DhScene extends Scene { const prototype = tokenDoc.actor?.prototypeToken ?? tokenDoc; this.#sizeSyncBatch.set(tokenDoc.id, { size: tokenSize, - prototypeSize: { width: prototype.width, height: prototype.height }, + prototypeSize: { width: prototype.width, height: prototype.height, depth: prototype.depth }, position: { x: tokenDoc.x, y: tokenDoc.y, elevation: tokenDoc.elevation } }); this.#processSyncBatch(); @@ -36,11 +36,13 @@ export default class DhScene extends Scene { const tokenSize = tokenSizes[size]; const width = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.width; const height = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.height; + const depth = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.depth; const updatedPosition = DHToken.getSnappedPositionInSquareGrid(this.grid, position, width, height); return { _id, width, height, + depth, ...updatedPosition }; }); diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 4ee7ce05..30862724 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -66,7 +66,8 @@ export default class DHToken extends CONFIG.Token.documentClass { if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) { document.updateSource({ width: tokenSize, - height: tokenSize + height: tokenSize, + depth: tokenSize }); } } @@ -90,7 +91,7 @@ export default class DHToken extends CONFIG.Token.documentClass { ) { const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; const tokenSize = tokenSizes[update.system.size]; - if (tokenSize !== this.width || tokenSize !== this.height) { + if (tokenSize !== this.width || tokenSize !== this.height || tokenSize !== this.depth) { this.parent?.syncTokenDimensions(this, update.system.size); } } diff --git a/module/documents/tokenManager.mjs b/module/documents/tokenManager.mjs index 3ccff4e2..7678d2c7 100644 --- a/module/documents/tokenManager.mjs +++ b/module/documents/tokenManager.mjs @@ -15,6 +15,7 @@ export default class DhTokenManager { if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) { tokenData.width = tokenSize; tokenData.height = tokenSize; + tokenData.depth = tokenSize; } } diff --git a/system.json b/system.json index 613c26e7..bbee2c09 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.2.6", "compatibility": { "minimum": "14.361", - "verified": "14.362", + "verified": "14.363", "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart", diff --git a/templates/hud/tokenHUD.hbs b/templates/hud/tokenHUD.hbs index b620ab11..cf43e15d 100644 --- a/templates/hud/tokenHUD.hbs +++ b/templates/hud/tokenHUD.hbs @@ -17,7 +17,7 @@ {{/if}} {{#if hasCompanion}} @@ -26,6 +26,13 @@ {{/if}} + {{#if isGM}} + + {{/if}} + {{#if canConfigure}} + {{imagePreview.current}} / {{imagePreview.total}} + +
    +
    + {{/if}}
  • - {{/if}} +
    - {{localize "Token Size"}} + {{localize "DAGGERHEART.APPLICATIONS.TokenConfig.tokenSize"}} {{#if usesActorSize}} -
    - +
    + -
    -
    - {{/if}} - - {{#if isItemEffect}} - {{formGroup fields.transfer value=source.transfer rootId=rootId label=legacyTransfer.label hint=(localize "DAGGERHEART.EFFECTS.Attachments.transferHint")}} + {{formGroup fields.origin value=source.origin rootId=rootId disabled=true}} + {{else if isItemEffect}} + {{formGroup fields.transfer value=source.transfer rootId=rootId}} {{/if}} {{formGroup fields.statuses value=source.statuses options=statuses rootId=rootId classes="statuses"}} From 0e8c3dc74a265477fa04583b495ac9862e9620e3 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 25 May 2026 17:55:57 -0400 Subject: [PATCH 37/73] [UI] Adjust actor sheet headers (#1923) --- .../tag-team-dialog/initialization.less | 14 +- .../sheets/actors/actor-sheet-shared.less | 2 +- .../less/sheets/actors/companion/header.less | 15 ++- .../sheets/actors/environment/header.less | 126 ++++++++++-------- .../less/sheets/actors/environment/sheet.less | 4 - styles/less/sheets/actors/party/header.less | 35 ++--- styles/less/utils/colors.less | 4 + styles/less/utils/mixin.less | 45 +++++++ .../sheets/actors/environment/header.hbs | 42 +++--- templates/sheets/actors/party/header.hbs | 4 +- 10 files changed, 172 insertions(+), 119 deletions(-) diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index 2d015141..f53a7af4 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -1,3 +1,5 @@ +@import '../../utils/mixin.less'; + .theme-light .daggerheart.dialog.dh-style.views.tag-team-dialog { .initialization-container .members-container .member-container { .member-name { @@ -62,17 +64,7 @@ color: var(--color-text-primary); text-shadow: 1px 1px 2px var(--shadow-color), 0 0 10px var(--shadow-color); - - // Basic "scrim" gradient - background-image: linear-gradient( - to top, - var(--shadow-color), - rgba(from var(--shadow-color) r g b / 0.834) 10.6%, - rgba(from var(--shadow-color) r g b / 0.541) 34%, - rgba(from var(--shadow-color) r g b / 0.382) 47%, - rgba(from var(--shadow-color) r g b / 0.194) 65%, - transparent 100% - ); + .smooth-gradient-ease-in-out(background-image, to bottom, var(--shadow-color), 100%); } img { diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index bd82ef83..6ef73035 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -34,7 +34,7 @@ .attribution-header-label { font-style: italic; font-family: @font-body; - color: light-dark(@chat-blue-bg, @beige-50); + color: @color-text-subtle; } .tab.inventory { diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index 2a162a25..6cf886ab 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -11,7 +11,7 @@ .profile { height: 235px; cursor: pointer; - mask-image: linear-gradient(0deg, transparent 0%, black 10%); + .smooth-gradient-ease-in-out(mask-image, to top, black, 2.25rem); } .actor-name { @@ -24,11 +24,16 @@ margin-bottom: -30px; input[type='text'] { + backdrop-filter: none; + border: none; + font-family: @font-title; font-size: var(--font-size-24); - height: 32px; - text-align: center; - border: 1px solid transparent; outline: 2px solid transparent; + box-shadow: unset; + text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); + + height: 2rem; + text-align: center; transition: all 0.3s ease; &:hover { @@ -243,7 +248,7 @@ .companion-navigation { display: flex; gap: 8px; - align-items: center; + align-items: baseline; width: 100%; } } diff --git a/styles/less/sheets/actors/environment/header.less b/styles/less/sheets/actors/environment/header.less index 670f6c92..ce7e6163 100644 --- a/styles/less/sheets/actors/environment/header.less +++ b/styles/less/sheets/actors/environment/header.less @@ -1,5 +1,6 @@ @import '../../../utils/colors.less'; @import '../../../utils/fonts.less'; +@import '../../../utils/mixin.less'; .application.sheet.daggerheart.actor.dh-style.environment { .environment-header-sheet { @@ -10,60 +11,83 @@ .profile { height: 235px; - mask-image: linear-gradient(0deg, transparent 0%, black 10%); cursor: pointer; + .smooth-gradient-ease-in-out(mask-image, to top, black, 3.5rem); } .item-container { - display: flex; + display: grid; + grid-auto-flow: row; + grid-template-columns: 1fr min-content; + align-items: center; position: relative; - top: -45px; - gap: 20px; + top: -36px; + gap: 0 var(--spacer-12); padding: 0 20px; - margin-bottom: -30px; + margin-bottom: -26px; - .item-info { + .item-name input[type='text'] { + backdrop-filter: none; + border: none; + font-family: @font-title; + font-size: var(--font-size-32); + text-align: start; + transition: all 0.3s ease; + outline: 2px solid transparent; + box-shadow: none; + text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); + + padding-left: 0; + height: 2.625rem; + + &:hover[type='text'], + &:focus[type='text'] { + box-shadow: none; + outline: 2px solid light-dark(@dark-blue, @golden); + } + } + + .flexrow { + align-items: baseline; + grid-column: span 2; + } + + .tags { display: flex; - flex-direction: column; - gap: 8px; + gap: 10px; + padding-bottom: 0; + flex: 0; - .tags { + .tag { display: flex; - gap: 10px; - padding-bottom: 0; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: var(--font-size-12); + font: @font-body; + white-space: nowrap; - .tag { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 3px 5px; - font-size: var(--font-size-12); - font: @font-body; - - background: light-dark(@dark-15, @beige-15); - border: 1px solid light-dark(@dark, @beige); - border-radius: 3px; - } - - .label { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - font-size: var(--font-size-12); - } + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; } - .attribution-header-label { - text-align: left; - position: relative; - top: 4px; - margin-bottom: -6px; + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: var(--font-size-12); } } + .attribution-header-label { + text-align: right; + position: relative; + } + .status-number { display: flex; align-items: center; @@ -81,7 +105,7 @@ font-size: 1.2rem; align-items: center; justify-content: center; - background: light-dark(transparent, @dark-blue); + background: light-dark(#e8e6e3, @dark-blue); z-index: 2; &.armor-slots { @@ -93,7 +117,7 @@ .status-label { padding: 2px 10px; width: 100%; - border-radius: 3px; + border-radius: 0 0 3px 3px; background: light-dark(@dark-blue, @golden); h4 { @@ -105,37 +129,23 @@ } } } - - .item-name { - input[type='text'] { - font-size: var(--font-size-32); - height: 42px; - text-align: start; - transition: all 0.3s ease; - outline: 2px solid transparent; - border: 1px solid transparent; - - &:hover[type='text'], - &:focus[type='text'] { - box-shadow: none; - outline: 2px solid light-dark(@dark-blue, @golden); - } - } - } } .environment-info { display: flex; flex-direction: column; - gap: 12px; + gap: var(--spacer-8); padding: 10px 20px; } .environment-navigation { display: flex; gap: 20px; - align-items: center; + align-items: baseline; padding: 0 20px; + .tab-navigation { + margin-top: 0; + } } } } diff --git a/styles/less/sheets/actors/environment/sheet.less b/styles/less/sheets/actors/environment/sheet.less index a7c9605b..2d9cc188 100644 --- a/styles/less/sheets/actors/environment/sheet.less +++ b/styles/less/sheets/actors/environment/sheet.less @@ -5,10 +5,6 @@ .appTheme({ &.environment { background-image: url('../assets/parchments/dh-parchment-dark.png'); - - .attribution-header-label { - background-image: url('../assets/parchments/dh-parchment-dark.png'); - } } }, { &.environment { diff --git a/styles/less/sheets/actors/party/header.less b/styles/less/sheets/actors/party/header.less index 9a2c7350..18d69834 100644 --- a/styles/less/sheets/actors/party/header.less +++ b/styles/less/sheets/actors/party/header.less @@ -1,5 +1,6 @@ @import '../../../utils/colors.less'; @import '../../../utils/fonts.less'; +@import '../../../utils/mixin.less'; .party-header-sheet { display: flex; @@ -9,26 +10,30 @@ .profile { height: 235px; - mask-image: linear-gradient(0deg, transparent 0%, black 10%); cursor: pointer; + .smooth-gradient-ease-in-out(mask-image, to top, black, 3.5rem); } .item-container { - .item-name { - padding: 0 20px; - input[type='text'] { - font-size: 32px; - height: 42px; - text-align: center; - transition: all 0.3s ease; - outline: 2px solid transparent; - border: 1px solid transparent; + margin-top: -2rem; + z-index: 1; + input.item-name[type='text'] { + backdrop-filter: none; + border: none; + font-family: @font-title; + font-size: var(--font-size-32); + outline: 2px solid transparent; + box-shadow: unset; + text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); - &:hover[type='text'], - &:focus[type='text'] { - box-shadow: none; - outline: 2px solid light-dark(@dark-blue, @golden); - } + text-align: center; + transition: all 0.3s ease; + width: calc(100% - 40px); + height: 2.625rem; + + &:hover[type='text'], + &:focus[type='text'] { + outline: 2px solid light-dark(@dark-blue, @golden); } } diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 80519a5b..d35ad8b3 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -83,6 +83,8 @@ --gradient-stress: linear-gradient(15deg, rgb(130, 59, 1) 0%, rgb(252, 142, 69) 65%, rgb(190, 0, 0) 100%); --primary-color-fear: rgba(9, 71, 179, 0.75); + + --dh-color-text-subtle: light-dark(#555, #a29086); } @primary-blue: var(--primary-blue, #1488cc); @@ -190,3 +192,5 @@ box-shadow: 0 0 2px 2px @dark-blue; } } + +@color-text-subtle: var(--dh-color-text-subtle); \ No newline at end of file diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index 49e97a1f..b37bfc06 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -114,3 +114,48 @@ font-family: @font-body; } } + + +// A simple ease-out mask +.smooth-gradient-ease-out(@param, @to, @destination, @length) { + @{param}+: linear-gradient( + @to, + transparent 0%, + rgb(from @destination r g b / ~"calc(alpha * 0.013)") calc(0.008 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.049)") calc(0.029 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.104)") calc(0.064 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.259)") calc(0.166 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.45)") calc(0.304 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.741)") calc(0.554 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.825)") calc(0.644 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.896)") calc(0.735 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.951)") calc(0.825 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.987)") calc(0.914 * @length), + @destination @length + ); +} + +/** + * A simple ease in and out mask. + * @param - the parameter to add to + * @param - direction, such as "to top" + * @destination - the color at the destination. The origin is always transparent + * @length - the value at the destination + */ +.smooth-gradient-ease-in-out(@param, @to, @destination, @length: 100%) { + @{param}+: linear-gradient( + @to, + transparent 0%, + rgb(from @destination r g b / ~"calc(alpha * 0.013)") calc(0.081 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.049)") calc(0.155 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.104)") calc(0.225 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.259)") calc(0.353 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.45)") calc(0.471 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.741)") calc(0.647 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.825)") calc(0.71 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.896)") calc(0.775 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.951)") calc(0.845 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.987)") calc(0.914 * @length), + @destination @length + ); +} diff --git a/templates/sheets/actors/environment/header.hbs b/templates/sheets/actors/environment/header.hbs index b7eab3db..2c6bbb5a 100644 --- a/templates/sheets/actors/environment/header.hbs +++ b/templates/sheets/actors/environment/header.hbs @@ -1,28 +1,7 @@
    -
    -

    -
    -
    -
    - - {{localize (concat 'DAGGERHEART.GENERAL.Tiers.' source.system.tier)}} - -
    - {{#if source.system.type}} -
    - - {{localize (concat 'DAGGERHEART.CONFIG.EnvironmentType.' source.system.type '.label')}} - -
    - {{/if}} -
    - {{#if (and showAttribution document.system.attributionLabel)}} - - {{/if}} -
    -
    +

    {{#if source.system.difficulty}} @@ -35,6 +14,25 @@

    {{localize "DAGGERHEART.GENERAL.difficulty"}}

    +
    +
    +
    + + {{localize (concat 'DAGGERHEART.GENERAL.Tiers.' source.system.tier)}} + +
    + {{#if source.system.type}} +
    + + {{localize (concat 'DAGGERHEART.CONFIG.EnvironmentType.' source.system.type '.label')}} + +
    + {{/if}} +
    + {{#if (and showAttribution document.system.attributionLabel)}} + + {{/if}} +
    diff --git a/templates/sheets/actors/party/header.hbs b/templates/sheets/actors/party/header.hbs index c48902c8..efa6e5b8 100644 --- a/templates/sheets/actors/party/header.hbs +++ b/templates/sheets/actors/party/header.hbs @@ -2,9 +2,7 @@
    -

    - -

    +
    \ No newline at end of file From e529dd0f883c3bdd21eb6f7abd8913e7146779cb Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 26 May 2026 00:49:46 +0200 Subject: [PATCH 38/73] Fixed so that advantage dice do not get duplicated (#1929) --- module/applications/dialogs/d20RollDialog.mjs | 14 +++++------ module/dice/dualityRoll.mjs | 24 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 067aa473..76b2e751 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -175,14 +175,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.disadvantage = advantage === -1; this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage; + if (this.config.roll.advantage === 0) return this.render(); - if (this.config.roll.advantage === 1 && this.config.data.rules.roll.defaultAdvantageDice) { - const faces = Number.parseInt(this.config.data.rules.roll.defaultAdvantageDice); - this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces; - } else if (this.config.roll.advantage === -1 && this.config.data.rules.roll.defaultDisadvantageDice) { - const faces = Number.parseInt(this.config.data.rules.roll.defaultDisadvantageDice); - this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces; - } + const defaultFaces = + this.config.roll.advantage === 1 + ? this.config.data.rules.roll.defaultAdvantageDice + : this.config.data.rules.roll.defaultDisadvantageDice; + const faces = Number.parseInt(defaultFaces); + this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces; this.render(); } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 5e03a680..d58811fe 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -148,14 +148,22 @@ export default class DualityRoll extends D20Roll { } applyAdvantage() { - if (this.hasAdvantage || this.hasDisadvantage) { - const dieFaces = this.advantageFaces, - advDie = new foundry.dice.terms.Die({ faces: dieFaces, number: this.advantageNumber }); - if (this.advantageNumber > 1) advDie.modifiers = ['kh']; - this.terms.push( - new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }), - advDie - ); + const advDieClass = this.hasAdvantage + ? game.system.api.dice.diceTypes.AdvantageDie + : this.hasDisadvantage + ? game.system.api.dice.diceTypes.DisadvantageDie + : null; + if (advDieClass) { + const advDie = new advDieClass({ faces: this.advantageFaces, number: this.advantageNumber }); + if (this.terms.length < 4) { + if (this.advantageNumber > 1) advDie.modifiers = ['kh']; + this.terms.push( + new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }), + advDie + ); + } else { + this.terms[4] = advDie; + } } if (this.rallyFaces) this.terms.push( From ccc4186e42e96771ed5c00d4e9c3f8479f97792d Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 06:27:17 -0400 Subject: [PATCH 39/73] Disable contenteditable inputs when read only (#1935) --- module/applications/sheets/actors/character.mjs | 3 +++ module/applications/sheets/api/base-actor.mjs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index d85838e1..19b82712 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -184,6 +184,9 @@ export default class CharacterSheet extends DHBaseActorSheet { for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) { input.disabled = disabled; } + for (const element of form.querySelectorAll('.input[contenteditable]')) { + element.classList.toggle('disabled', disabled); + } } /** @inheritDoc */ diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index e23a4426..5cd0f6a5 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -166,6 +166,15 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { } } + /** Add support for input content editables */ + _toggleDisabled(disabled) { + super._toggleDisabled(disabled); + const form = this.form; + for (const element of form.querySelectorAll('.input[contenteditable]')) { + element.classList.toggle('disabled', disabled); + } + } + /* -------------------------------------------- */ /* Context Menu */ /* -------------------------------------------- */ From b9416ead5a31b28ea187f883432b5362dfa5c439 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 09:41:26 -0400 Subject: [PATCH 40/73] Fix usable checks on adversary features and locked compendium actors (#1934) --- module/data/action/baseAction.mjs | 8 ++++++-- module/documents/item.mjs | 6 ++++-- .../sheets/global/partials/inventory-item-compact.hbs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index b3775dc9..acd104a7 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -148,10 +148,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel : null; } - /** Returns true if the action is usable */ + /** + * Returns true if the action is usable. + * An action is usable on any actor type. For example, an adversary might have a base attack action. + */ get usable() { const actor = this.actor; - return this.isOwner && actor?.type === 'character'; + const pack = actor?.pack ? game.packs.get(actor.pack) : null; + return !pack?.locked && this.isOwner; } static getRollType(parent) { diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 93aa3b28..603ca594 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -73,8 +73,10 @@ export default class DHItem extends foundry.documents.Item { /** Returns true if the item can be used */ get usable() { const actor = this.actor; - const actionsList = this.system.actionsList; - return this.isOwner && actor?.type === 'character' && (actionsList?.size || actionsList?.length); + const pack = actor?.pack ? game.packs.get(actor.pack) : null; + const hasActions = this.system.actionsList?.size || this.system.actionsList?.length; + const isValidType = actor?.type === 'character' || this.type === 'feature'; + return !pack?.locked && this.isOwner && isValidType && hasActions; } /** @inheritdoc */ diff --git a/templates/sheets/global/partials/inventory-item-compact.hbs b/templates/sheets/global/partials/inventory-item-compact.hbs index daba6721..78eaf087 100644 --- a/templates/sheets/global/partials/inventory-item-compact.hbs +++ b/templates/sheets/global/partials/inventory-item-compact.hbs @@ -5,7 +5,7 @@ (hasProperty item "toChat" ) "toChat" "editDoc" ) }}' {{#unless hideTooltip}} {{#if (eq type 'attack' )}} data-tooltip="#attack#{{item.actor.uuid}}" {{else}} data-tooltip="#item#{{item.uuid}}" {{/if}} {{/unless}}> - {{#if (or item.system.actionsList.size item.system.actionsList.length item.actionType)}} + {{#if item.usable}} {{#if @root.isNPC}} d20 {{else}} From de0ab9d0476d7a1118c7d9152e8fcf1eb2285e41 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 09:43:36 -0400 Subject: [PATCH 41/73] Include more item types when viewing compendium browser from item tab (#1931) --- module/applications/ui/itemBrowser.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index d98cf2da..45e8863f 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -606,7 +606,16 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { items: { folder: 'equipments', render: { - noFolder: true + folders: [ + 'equipments', + 'ancestries', + 'classes', + 'subclasses', + 'domains', + 'communities', + 'beastforms' + // excluded: features + ] } }, compendium: {} From c2f8b34ef25a4e51e12585fb72c9de3ef4a26342 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 09:46:08 -0400 Subject: [PATCH 42/73] Show actor sheet when clicking actors in item browser (#1930) --- module/applications/ui/itemBrowser.mjs | 12 ++++++++++-- styles/less/ui/item-browser/item-browser.less | 2 +- templates/ui/itemBrowser/itemContainer.hbs | 16 +++++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 45e8863f..8f38918a 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -1,3 +1,4 @@ +import { getDocFromElement } from '../../helpers/utils.mjs'; import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -47,7 +48,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { expandContent: this.expandContent, resetFilters: this.resetFilters, sortList: this.sortList, - openSettings: this.openSettings + openSettings: this.openSettings, + viewSheet: this.#onViewSheet }, position: { left: 100, @@ -306,7 +308,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { { items: this.items, menu: this.selectedMenu, - formatLabel: this.formatLabel + formatLabel: this.formatLabel, + viewSheet: this.items[0] instanceof Actor } ); @@ -568,6 +571,11 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { } } + static async #onViewSheet(_, target) { + const document = await getDocFromElement(target); + document?.sheet?.render(true); + } + _createDragProcess() { new foundry.applications.ux.DragDrop.implementation({ dragSelector: '.item-container', diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index 066da73b..a40c0fae 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -245,7 +245,7 @@ } .item-list-header, - .item-list [data-action='expandContent'] { + .item-list .item-info[data-action] { display: flex; > * { diff --git a/templates/ui/itemBrowser/itemContainer.hbs b/templates/ui/itemBrowser/itemContainer.hbs index 8dd75156..a5f067e8 100644 --- a/templates/ui/itemBrowser/itemContainer.hbs +++ b/templates/ui/itemBrowser/itemContainer.hbs @@ -1,7 +1,7 @@ {{#each items}}
    -
    +
    {{name}} {{#each ../menu.data.columns}} @@ -9,11 +9,13 @@ {{/each}}
    -
    - - {{{system.enrichedTags}}} - {{{system.enrichedDescription}}} - -
    + {{#unless viewSheet}} +
    + + {{{system.enrichedTags}}} + {{{system.enrichedDescription}}} + +
    + {{/unless}}
    {{/each}} \ No newline at end of file From fa6f9d56b807cb893062632a5d413e697d23dfb7 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 16:18:52 -0400 Subject: [PATCH 43/73] Add emphatic color variable and set up scoped based overrides for core variables (#1932) --- .../character-creation/tab-navigation.less | 2 +- .../compendiumBrowserPackDialog/sheet.less | 1 - .../less/dialog/dice-roll/roll-selection.less | 4 ++-- .../dialog/downtime/downtime-container.less | 2 +- .../dialog/group-roll-dialog/_common.less | 2 +- .../less/dialog/group-roll-dialog/main.less | 2 +- .../dialog/level-up/navigation-container.less | 2 +- .../less/dialog/multiclass-choice/sheet.less | 2 +- .../tag-team-dialog/initialization.less | 2 +- styles/less/dialog/tag-team-dialog/sheet.less | 8 ++++---- styles/less/global/dialog.less | 4 ++-- styles/less/global/elements.less | 12 +++++------ styles/less/global/filter-menu.less | 4 ++-- styles/less/global/resource-bar.less | 2 +- styles/less/global/sheet.less | 2 +- styles/less/global/tab-navigation.less | 2 +- styles/less/hud/token-hud/token-hud.less | 2 +- styles/less/sheets-settings/header.less | 2 +- .../sheets/actors/actor-sheet-shared.less | 10 +++++----- .../less/sheets/actors/adversary/sidebar.less | 4 ++-- .../less/sheets/actors/character/header.less | 8 ++++---- .../less/sheets/actors/character/loadout.less | 2 +- .../less/sheets/actors/character/sidebar.less | 10 +++++----- .../sheets/actors/party/party-members.less | 14 ++++++------- styles/less/ui/chat/group-roll.less | 6 +++--- styles/less/ui/chat/sheet.less | 2 +- styles/less/ui/countdown/countdown.less | 2 +- styles/less/ui/item-browser/item-browser.less | 6 +++--- .../settings/homebrew-settings/resources.less | 2 +- styles/less/ui/settings/settings.less | 2 +- styles/less/ui/sidebar/daggerheartMenu.less | 2 +- styles/less/utils/colors.less | 20 +++++++++++++++++-- styles/less/ux/tooltip/armorManagement.less | 2 +- .../less/ux/tooltip/resource-management.less | 4 ++-- styles/less/ux/tooltip/sheet.less | 2 +- styles/less/ux/tooltip/tooltip.less | 2 +- 36 files changed, 86 insertions(+), 71 deletions(-) diff --git a/styles/less/dialog/character-creation/tab-navigation.less b/styles/less/dialog/character-creation/tab-navigation.less index 36b89d5a..85541db7 100644 --- a/styles/less/dialog/character-creation/tab-navigation.less +++ b/styles/less/dialog/character-creation/tab-navigation.less @@ -7,7 +7,7 @@ border-top: 0; a { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &[disabled] { opacity: 0.4; diff --git a/styles/less/dialog/compendiumBrowserPackDialog/sheet.less b/styles/less/dialog/compendiumBrowserPackDialog/sheet.less index dfe375b5..b16f1086 100644 --- a/styles/less/dialog/compendiumBrowserPackDialog/sheet.less +++ b/styles/less/dialog/compendiumBrowserPackDialog/sheet.less @@ -67,7 +67,6 @@ i { font-size: 18px; - // color: light-dark(@dark-blue, @golden); } } } diff --git a/styles/less/dialog/dice-roll/roll-selection.less b/styles/less/dialog/dice-roll/roll-selection.less index a1a01e6b..e3551902 100644 --- a/styles/less/dialog/dice-roll/roll-selection.less +++ b/styles/less/dialog/dice-roll/roll-selection.less @@ -56,7 +56,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .label { font-style: normal; @@ -129,7 +129,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .label { font-style: normal; diff --git a/styles/less/dialog/downtime/downtime-container.less b/styles/less/dialog/downtime/downtime-container.less index eb615ef0..a7945d4c 100644 --- a/styles/less/dialog/downtime/downtime-container.less +++ b/styles/less/dialog/downtime/downtime-container.less @@ -37,7 +37,7 @@ .activity-marker { font-size: 0.5rem; flex: none; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; margin-right: 4px; } diff --git a/styles/less/dialog/group-roll-dialog/_common.less b/styles/less/dialog/group-roll-dialog/_common.less index b04f6893..f74ab8a0 100644 --- a/styles/less/dialog/group-roll-dialog/_common.less +++ b/styles/less/dialog/group-roll-dialog/_common.less @@ -1,5 +1,5 @@ h1 { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font: 700 var(--font-size-24) var(--dh-font-subtitle); text-align: center; } diff --git a/styles/less/dialog/group-roll-dialog/main.less b/styles/less/dialog/group-roll-dialog/main.less index f266dcc7..e30f4e29 100644 --- a/styles/less/dialog/group-roll-dialog/main.less +++ b/styles/less/dialog/group-roll-dialog/main.less @@ -110,7 +110,7 @@ display: flex; flex-direction: row; button { - --button-text-color: var(--color-text-primary); + --button-text-color: @color-text-primary; --button-size: 1.5em; padding: 0 var(--spacer-4); img { diff --git a/styles/less/dialog/level-up/navigation-container.less b/styles/less/dialog/level-up/navigation-container.less index 282d683f..6bf80d7c 100644 --- a/styles/less/dialog/level-up/navigation-container.less +++ b/styles/less/dialog/level-up/navigation-container.less @@ -19,7 +19,7 @@ a, span { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/dialog/multiclass-choice/sheet.less b/styles/less/dialog/multiclass-choice/sheet.less index 1f38449a..d848f203 100644 --- a/styles/less/dialog/multiclass-choice/sheet.less +++ b/styles/less/dialog/multiclass-choice/sheet.less @@ -35,7 +35,7 @@ width: 120px; height: 120px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.selected { background: light-dark(@dark-blue-40, @golden-40); diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index f53a7af4..14a3f41b 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -62,7 +62,7 @@ padding: 5rem 4px var(--spacer-8) 4px; text-align: center; - color: var(--color-text-primary); + color: @color-text-primary; text-shadow: 1px 1px 2px var(--shadow-color), 0 0 10px var(--shadow-color); .smooth-gradient-ease-in-out(background-image, to bottom, var(--shadow-color), 100%); } diff --git a/styles/less/dialog/tag-team-dialog/sheet.less b/styles/less/dialog/tag-team-dialog/sheet.less index a8dffbd2..22f0d0bb 100644 --- a/styles/less/dialog/tag-team-dialog/sheet.less +++ b/styles/less/dialog/tag-team-dialog/sheet.less @@ -1,6 +1,6 @@ .daggerheart.dialog.dh-style.views.tag-team-dialog .window-content { h1 { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font: 700 var(--font-size-24) var(--dh-font-subtitle); text-align: center; } @@ -64,7 +64,7 @@ .roll-title { font-size: var(--font-size-20); font-weight: bold; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; text-align: center; display: flex; align-items: center; @@ -72,7 +72,7 @@ &::before, &::after { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; content: ''; flex: 1; height: 2px; @@ -202,7 +202,7 @@ justify-content: center; i { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-size: 48px; &.inactive { diff --git a/styles/less/global/dialog.less b/styles/less/global/dialog.less index a3400700..fb4097e7 100644 --- a/styles/less/global/dialog.less +++ b/styles/less/global/dialog.less @@ -37,7 +37,7 @@ &:hover { border: 1px solid light-dark(@dark-blue, @golden); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } @@ -81,7 +81,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .label { font-style: normal; diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 7d46d627..d918e809 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -14,7 +14,7 @@ box-shadow: 0 4px 30px @soft-shadow; backdrop-filter: blur(9.5px); outline: 2px solid transparent; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; border: 1px solid light-dark(@dark, @beige); transition: all 0.3s ease; @@ -107,7 +107,7 @@ &:hover { background: light-dark(@light-black, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } &.glow { @@ -128,7 +128,7 @@ &.reverted { background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; border: 1px solid light-dark(@dark, transparent); &:hover { background: light-dark(transparent, @golden); @@ -175,7 +175,7 @@ height: inherit; .tag { padding: 0.3rem 0.5rem; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; background-color: light-dark(@dark-blue-10, @golden-40); border-radius: 3px; transition: 0.13s ease-out; @@ -353,7 +353,7 @@ legend { font-weight: bold; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.with-icon { display: flex; @@ -571,7 +571,7 @@ border: 0; &:hover { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } &:not(:first-child) { diff --git a/styles/less/global/filter-menu.less b/styles/less/global/filter-menu.less index 65a184f8..a0545950 100644 --- a/styles/less/global/filter-menu.less +++ b/styles/less/global/filter-menu.less @@ -13,7 +13,7 @@ legend { font-weight: bold; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-size: var(--font-size-12); } @@ -25,7 +25,7 @@ button { background: light-dark(@light-black, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; outline: none; box-shadow: none; border: 1px solid light-dark(@dark-blue, @dark-blue); diff --git a/styles/less/global/resource-bar.less b/styles/less/global/resource-bar.less index be9bc68b..ef411eee 100644 --- a/styles/less/global/resource-bar.less +++ b/styles/less/global/resource-bar.less @@ -53,7 +53,7 @@ border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; z-index: 1; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; width: fit-content; .slot { diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index 5ccb8788..e04c7573 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -43,7 +43,7 @@ body.game:is(.performance-low, .noblur) { &:hover { border-color: light-dark(@dark-blue, @golden); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/global/tab-navigation.less b/styles/less/global/tab-navigation.less index 3f8844f2..038a9749 100755 --- a/styles/less/global/tab-navigation.less +++ b/styles/less/global/tab-navigation.less @@ -20,7 +20,7 @@ white-space: nowrap; a { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/hud/token-hud/token-hud.less b/styles/less/hud/token-hud/token-hud.less index e31ede4a..3cb94e1e 100644 --- a/styles/less/hud/token-hud/token-hud.less +++ b/styles/less/hud/token-hud/token-hud.less @@ -24,7 +24,7 @@ .palette-category-title { grid-column: span var(--effect-columns); font-weight: bold; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/sheets-settings/header.less b/styles/less/sheets-settings/header.less index 82f3c488..04e2fa90 100644 --- a/styles/less/sheets-settings/header.less +++ b/styles/less/sheets-settings/header.less @@ -13,7 +13,7 @@ font-size: var(--font-size-24); margin: 0; text-align: center; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 6ef73035..5d669d4d 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -127,7 +127,7 @@ .title-name { text-align: start; font-size: var(--font-size-28); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; text-align: center; } } @@ -180,7 +180,7 @@ display: flex; gap: 10px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -194,7 +194,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } .domain { @@ -206,7 +206,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } img { @@ -230,7 +230,7 @@ padding: 10px; border-radius: 5px; min-width: 90px; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; background-color: light-dark(@dark-blue-10, @golden-40); } } diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index ef99bc09..8bb9834c 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -65,7 +65,7 @@ display: flex; gap: 10px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -77,7 +77,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.threshold-value { color: light-dark(@dark, @beige); diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index 21ea4846..9bd3d1ff 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -103,7 +103,7 @@ padding: 5px 0; margin-bottom: 8px; font-size: var(--font-size-12); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .missing-header-feature { opacity: 0.5; @@ -170,7 +170,7 @@ display: flex; gap: 4px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -182,7 +182,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; margin-right: 4px; } @@ -195,7 +195,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } img { diff --git a/styles/less/sheets/actors/character/loadout.less b/styles/less/sheets/actors/character/loadout.less index 127d688a..5c0abef3 100644 --- a/styles/less/sheets/actors/character/loadout.less +++ b/styles/less/sheets/actors/character/loadout.less @@ -54,7 +54,7 @@ span { margin: 1px; width: 26px; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.list-icon { i { diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index e450891b..a4c7c0db 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -286,7 +286,7 @@ h4, i { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } @@ -314,7 +314,7 @@ z-index: 1; background: @dark-blue; justify-content: center; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .armor-slot { cursor: pointer; @@ -348,7 +348,7 @@ .label, .value, i { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } @@ -513,7 +513,7 @@ align-self: center; gap: 10px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -525,7 +525,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.threshold-value { color: light-dark(@dark, @beige); diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index a3ec90ec..380f98b1 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -79,7 +79,7 @@ display: flex; gap: 4px; background-color: light-dark(var(--color-light-1), @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 4px 6px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 3px; @@ -93,7 +93,7 @@ &.threshold-label { font-size: var(--font-size-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } &.threshold-value { @@ -116,7 +116,7 @@ width: 100%; z-index: 1; font-size: var(--font-size-20); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-weight: bold; } @@ -132,7 +132,7 @@ .hope-section { display: flex; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 3px 6px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 3px; @@ -144,7 +144,7 @@ font-size: var(--font-size-12); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; margin-right: 3px; } @@ -212,7 +212,7 @@ gap: 4px; background-color: light-dark(@dark-blue-10, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 2px 5px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 0 6px 6px 0; @@ -260,7 +260,7 @@ justify-content: space-between; gap: 3px; .label { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } .value { font-weight: 600; diff --git a/styles/less/ui/chat/group-roll.less b/styles/less/ui/chat/group-roll.less index 9ed87220..98f0cfac 100644 --- a/styles/less/ui/chat/group-roll.less +++ b/styles/less/ui/chat/group-roll.less @@ -31,7 +31,7 @@ } i { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } @@ -71,7 +71,7 @@ align-items: center; justify-content: center; gap: 10px; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .main-value { font-size: var(--font-size-24); @@ -153,7 +153,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.finished { background-color: initial; diff --git a/styles/less/ui/chat/sheet.less b/styles/less/ui/chat/sheet.less index fb8cc104..bdf22364 100644 --- a/styles/less/ui/chat/sheet.less +++ b/styles/less/ui/chat/sheet.less @@ -93,7 +93,7 @@ } a[href] { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } a[href]:hover, diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 380eb454..66a6c88a 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -17,7 +17,7 @@ position: relative; border: 0; box-shadow: none; - color: var(--color-text-primary); + color: @color-text-primary; width: 300px; pointer-events: all; align-self: flex-end; diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index a40c0fae..90da7ed3 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -200,7 +200,7 @@ font-weight: bold; border-radius: 3px; background-color: light-dark(@dark-blue-40, @golden-40); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } .subfolder-list { @@ -218,7 +218,7 @@ font-weight: bold; border-radius: 3px; background-color: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } .wrapper { @@ -265,7 +265,7 @@ .item-list-header { align-items: center; background-color: light-dark(@dark-15, @dark-golden-80); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 3px; min-height: 30px; diff --git a/styles/less/ui/settings/homebrew-settings/resources.less b/styles/less/ui/settings/homebrew-settings/resources.less index 1184904b..9d562756 100644 --- a/styles/less/ui/settings/homebrew-settings/resources.less +++ b/styles/less/ui/settings/homebrew-settings/resources.less @@ -61,7 +61,7 @@ display: flex; align-items: center; gap: 4px; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; i { font-size: 14px; diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index d08f74e6..35c48480 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -1,7 +1,7 @@ @import '../../utils/colors.less'; .daggerheart.dh-style.setting { - --color-form-label: var(--color-text-primary); + --color-form-label: @color-text-primary; h2, h3, diff --git a/styles/less/ui/sidebar/daggerheartMenu.less b/styles/less/ui/sidebar/daggerheartMenu.less index 88b139c5..280d5ad3 100644 --- a/styles/less/ui/sidebar/daggerheartMenu.less +++ b/styles/less/ui/sidebar/daggerheartMenu.less @@ -34,7 +34,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .label { font-style: normal; diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index d35ad8b3..3eeb4d54 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -83,8 +83,21 @@ --gradient-stress: linear-gradient(15deg, rgb(130, 59, 1) 0%, rgb(252, 142, 69) 65%, rgb(190, 0, 0) 100%); --primary-color-fear: rgba(9, 71, 179, 0.75); +} - --dh-color-text-subtle: light-dark(#555, #a29086); +@scope (.theme-light) to (.themed) { + .dh-style, + .duality { + --color-text-emphatic: @dark-blue; + --color-text-subtle: #555; + } +} +@scope (.theme-dark) to (.themed) { + .dh-style, + .duality { + --color-text-emphatic: @golden; + --color-text-subtle: #a29086; + } } @primary-blue: var(--primary-blue, #1488cc); @@ -193,4 +206,7 @@ } } -@color-text-subtle: var(--dh-color-text-subtle); \ No newline at end of file +// LESS variable versions of core foundry color variables +@color-text-emphatic: var(--color-text-emphatic); +@color-text-primary: var(--color-text-primary); +@color-text-subtle: var(--color-text-subtle); diff --git a/styles/less/ux/tooltip/armorManagement.less b/styles/less/ux/tooltip/armorManagement.less index e1ac6bb9..ca26e2e8 100644 --- a/styles/less/ux/tooltip/armorManagement.less +++ b/styles/less/ux/tooltip/armorManagement.less @@ -124,7 +124,7 @@ background: @dark-blue; align-items: center; justify-content: center; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; min-height: 30px; width: 100%; diff --git a/styles/less/ux/tooltip/resource-management.less b/styles/less/ux/tooltip/resource-management.less index ff1f4dd2..5daccd32 100644 --- a/styles/less/ux/tooltip/resource-management.less +++ b/styles/less/ux/tooltip/resource-management.less @@ -9,7 +9,7 @@ display: flex; gap: 10px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -22,7 +22,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; margin: 0; } diff --git a/styles/less/ux/tooltip/sheet.less b/styles/less/ux/tooltip/sheet.less index ad774fcd..cc4166da 100644 --- a/styles/less/ux/tooltip/sheet.less +++ b/styles/less/ux/tooltip/sheet.less @@ -3,7 +3,7 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip), #tooltip.bordered-tooltip { .tooltip-title { font-size: var(--font-size-20); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-weight: 700; } diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less index 1566059f..541b3160 100644 --- a/styles/less/ux/tooltip/tooltip.less +++ b/styles/less/ux/tooltip/tooltip.less @@ -132,7 +132,7 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip.card-style) { border-radius: 3px; padding: 3px; background: light-dark(@dark-blue-60, @rustic-brown-80); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-size: 12px; margin-bottom: 10px; } From 48f9ffc318e3eda42d68baea51e0f19f7fba63eb Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 21:58:05 -0400 Subject: [PATCH 44/73] Fix recent regression for scope rules (#1938) --- styles/less/utils/colors.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 3eeb4d54..dd626358 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -86,6 +86,8 @@ } @scope (.theme-light) to (.themed) { + .dh-style :scope, + :scope.dh-style, .dh-style, .duality { --color-text-emphatic: @dark-blue; @@ -93,6 +95,8 @@ } } @scope (.theme-dark) to (.themed) { + .dh-style :scope, + :scope.dh-style, .dh-style, .duality { --color-text-emphatic: @golden; From 1ab8170d2ff562693295fe1ad68ea3d4f18b7e71 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 27 May 2026 16:20:16 -0400 Subject: [PATCH 45/73] [Refactor] Define more border and input color variables (#1937) * Define more border and input color variables * Rename custom color variables * Fix assignment of variables * Apply border color variable to matching borders * Add trait header colors and shadow contrast --- styles/less/dialog/beastform/sheet.less | 10 +- .../selections-container.less | 14 +-- .../damage-reduction-container.less | 4 +- .../dialog/downtime/downtime-container.less | 4 +- styles/less/dialog/image-select/sheet.less | 2 +- .../less/dialog/multiclass-choice/sheet.less | 2 +- .../tag-team-dialog/initialization.less | 2 +- styles/less/dialog/tag-team-dialog/sheet.less | 2 +- styles/less/global/dialog.less | 2 +- styles/less/global/elements.less | 32 ++---- styles/less/global/inventory-item.less | 2 +- styles/less/global/resource-bar.less | 6 +- styles/less/global/sheet.less | 2 +- .../sheets/actors/actor-sheet-shared.less | 2 +- .../less/sheets/actors/adversary/sidebar.less | 6 +- .../less/sheets/actors/character/header.less | 16 +-- .../less/sheets/actors/character/loadout.less | 2 +- .../less/sheets/actors/character/sidebar.less | 18 +-- .../less/sheets/actors/companion/header.less | 106 +----------------- .../sheets/actors/environment/header.less | 7 +- styles/less/sheets/actors/party/header.less | 5 +- .../sheets/actors/party/party-members.less | 12 +- styles/less/ui/effects-display/sheet.less | 2 +- styles/less/ui/item-browser/item-browser.less | 2 +- .../settings/homebrew-settings/domains.less | 4 +- .../ui/settings/homebrew-settings/types.less | 2 +- styles/less/utils/colors.less | 35 +++++- styles/less/utils/mixin.less | 9 +- styles/less/ux/tooltip/armorManagement.less | 4 +- .../less/ux/tooltip/resource-management.less | 2 +- styles/less/ux/tooltip/tooltip.less | 2 +- 31 files changed, 120 insertions(+), 200 deletions(-) diff --git a/styles/less/dialog/beastform/sheet.less b/styles/less/dialog/beastform/sheet.less index 0e1fe746..6d1a8a2a 100644 --- a/styles/less/dialog/beastform/sheet.less +++ b/styles/less/dialog/beastform/sheet.less @@ -43,7 +43,7 @@ text-align: center; font-size: var(--font-size-16); margin: 0 4px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); @@ -58,7 +58,7 @@ position: relative; display: flex; justify-content: center; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; cursor: pointer; width: 120px; @@ -164,7 +164,7 @@ .hybrid-data { padding: 0 2px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); @@ -191,7 +191,7 @@ flex-direction: column; gap: 4px; padding: 0 4px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); @@ -226,7 +226,7 @@ gap: 4px; .trait-card { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; padding: 2px; opacity: 0.4; diff --git a/styles/less/dialog/character-creation/selections-container.less b/styles/less/dialog/character-creation/selections-container.less index 24217dbf..ebf12eda 100644 --- a/styles/less/dialog/character-creation/selections-container.less +++ b/styles/less/dialog/character-creation/selections-container.less @@ -79,7 +79,7 @@ font-weight: bold; padding: 0 2px; background-image: url(../assets/parchments/dh-parchment-light.png); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@beige, @dark); opacity: 0.4; @@ -203,7 +203,7 @@ height: 16px; width: 110px; min-height: unset; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; color: light-dark(@beige, @beige); background-color: light-dark(var(--color-warm-3), var(--color-warm-3)); @@ -230,7 +230,7 @@ .suggested-trait-container { width: 56px; white-space: nowrap; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@beige, @dark); background-image: url('../assets/parchments/dh-parchment-light.png'); @@ -345,7 +345,7 @@ display: flex; justify-content: center; position: relative; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; .nav-section-text { @@ -383,7 +383,7 @@ width: 56px; text-align: center; line-height: 1; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@beige, @dark); background-image: url(../assets/parchments/dh-parchment-light.png); @@ -447,7 +447,7 @@ height: 100%; .simple-equipment { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 8px; position: relative; display: flex; @@ -466,7 +466,7 @@ top: -8px; font-size: var(--font-size-12); white-space: nowrap; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: @dark; background-image: url('../assets/parchments/dh-parchment-light.png'); diff --git a/styles/less/dialog/damage-reduction/damage-reduction-container.less b/styles/less/dialog/damage-reduction/damage-reduction-container.less index e8242bdd..6f7ffb51 100644 --- a/styles/less/dialog/damage-reduction/damage-reduction-container.less +++ b/styles/less/dialog/damage-reduction/damage-reduction-container.less @@ -81,7 +81,7 @@ .mark-container { cursor: pointer; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; height: 26px; padding: 0 1px; @@ -126,7 +126,7 @@ width: 100%; .chip-inner-container { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; height: 26px; padding: 0 4px; diff --git a/styles/less/dialog/downtime/downtime-container.less b/styles/less/dialog/downtime/downtime-container.less index a7945d4c..33d153fd 100644 --- a/styles/less/dialog/downtime/downtime-container.less +++ b/styles/less/dialog/downtime/downtime-container.less @@ -55,7 +55,7 @@ .activity-selected-marker { font-size: var(--font-size-14); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url(../assets/parchments/dh-parchment-dark.png); @@ -78,7 +78,7 @@ } .refreshable-container { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); diff --git a/styles/less/dialog/image-select/sheet.less b/styles/less/dialog/image-select/sheet.less index 3ed4f583..7a3a8468 100644 --- a/styles/less/dialog/image-select/sheet.less +++ b/styles/less/dialog/image-select/sheet.less @@ -12,7 +12,7 @@ img { width: 136px; height: 136px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; opacity: 0.4; diff --git a/styles/less/dialog/multiclass-choice/sheet.less b/styles/less/dialog/multiclass-choice/sheet.less index d848f203..0c487cbc 100644 --- a/styles/less/dialog/multiclass-choice/sheet.less +++ b/styles/less/dialog/multiclass-choice/sheet.less @@ -57,7 +57,7 @@ display: flex; flex-wrap: wrap; font-style: italic; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; padding: 4px 4px; } diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index 14a3f41b..d6f7ad29 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -36,7 +36,7 @@ align-items: stretch; justify-content: center; border-radius: 6px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; overflow: hidden; height: 11.5rem; width: 122px; diff --git a/styles/less/dialog/tag-team-dialog/sheet.less b/styles/less/dialog/tag-team-dialog/sheet.less index 22f0d0bb..3a112146 100644 --- a/styles/less/dialog/tag-team-dialog/sheet.less +++ b/styles/less/dialog/tag-team-dialog/sheet.less @@ -42,7 +42,7 @@ img { height: 64px; border-radius: 6px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; } .member-name { diff --git a/styles/less/global/dialog.less b/styles/less/global/dialog.less index fb4097e7..1313d68b 100644 --- a/styles/less/global/dialog.less +++ b/styles/less/global/dialog.less @@ -36,7 +36,7 @@ padding: 0; &:hover { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; color: @color-text-emphatic; } } diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index d918e809..7af8becd 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -2,8 +2,6 @@ @import '../utils/fonts.less'; .dh-style { - border: 1px solid light-dark(@dark-blue, @golden); - input[type='text'], input[type='number'], textarea, @@ -14,12 +12,12 @@ box-shadow: 0 4px 30px @soft-shadow; backdrop-filter: blur(9.5px); outline: 2px solid transparent; - color: @color-text-emphatic; - border: 1px solid light-dark(@dark, @beige); + color: @input-color-text; + border: 1px solid @input-color-border; transition: all 0.3s ease; &::placeholder { - color: light-dark(@dark-40, @beige-50); + color: @color-text-subtle; } &:hover, @@ -30,7 +28,7 @@ &:focus[type='number'] { background: light-dark(@soft-shadow, @semi-transparent-dark-blue); box-shadow: none; - outline: 2px solid light-dark(@dark, @beige); + outline: 2px solid @input-color-border; } &:disabled[type='text'], @@ -47,7 +45,7 @@ .input[contenteditable] { cursor: var(--cursor-text); &:empty:before { - color: light-dark(@dark-40, @beige-50); + color: @color-text-subtle; content: attr(placeholder); } } @@ -265,7 +263,7 @@ align-items: center; margin-top: 5px; border-radius: 6px; - border-color: light-dark(@dark-blue, @golden); + border-color: @color-fieldset-border; &.glassy { background-color: light-dark(@dark-blue-10, @golden-10); @@ -274,7 +272,7 @@ legend { padding: 2px 12px; border-radius: 3px; - background-color: light-dark(@dark-blue, @golden); + background-color: @color-fieldset-border; color: light-dark(@beige, @dark-blue); margin-bottom: var(--spacer-4); } @@ -365,18 +363,6 @@ } } - input[type='text'], - input[type='number'] { - color: light-dark(@dark, @beige); - transition: all 0.3s ease; - outline: 2px solid transparent; - - &:focus, - &:hover { - outline: 2px solid light-dark(@dark, @beige); - } - } - &[disabled], &.child-disabled .form-group, select[disabled], @@ -514,7 +500,7 @@ display: block; height: 1px; width: 100%; - border-bottom: 1px solid light-dark(@dark-blue, @golden); + border-bottom: 1px solid @color-border; mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); } @@ -522,7 +508,7 @@ display: block; height: 1px; width: 100%; - border-bottom: 1px solid light-dark(@dark-blue, @golden); + border-bottom: 1px solid @color-border; mask-image: linear-gradient(270deg, transparent 0%, black 100%); &.invert { diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index a2b9ebd8..3a5a9321 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -287,7 +287,7 @@ position: relative; height: 120px; width: 98px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; cursor: pointer; diff --git a/styles/less/global/resource-bar.less b/styles/less/global/resource-bar.less index ef411eee..d06b43a8 100644 --- a/styles/less/global/resource-bar.less +++ b/styles/less/global/resource-bar.less @@ -50,7 +50,7 @@ flex-wrap: wrap; gap: 5px; padding: 5px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; color: @color-text-emphatic; @@ -59,7 +59,7 @@ .slot { width: 15px; height: 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; background: light-dark(@dark-blue-10, @golden-10); border-radius: 3px; transition: all 0.3s ease; @@ -148,7 +148,7 @@ appearance: none; width: 100px; height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index e04c7573..e3072da1 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -4,7 +4,7 @@ // Theme handling .appTheme({ - background: @dark-blue-d0; + background: @dark-blue-c0; backdrop-filter: blur(7px); }, { background: url('../assets/parchments/dh-parchment-light.png') no-repeat center; diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 5d669d4d..470067ca 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -182,7 +182,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index 8bb9834c..b1bb51db 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -67,7 +67,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; @@ -191,7 +191,7 @@ appearance: none; width: 100px; height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; @@ -237,7 +237,7 @@ display: flex; width: 50px; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; padding: 0 6px; diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index 9bd3d1ff..91b3545a 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -172,7 +172,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; @@ -226,7 +226,6 @@ padding-left: 0.5rem; .trait { - --color-border: light-dark(@semi-transparent-dark-blue, @golden-60); cursor: pointer; position: relative; @@ -238,10 +237,10 @@ .trait-name { position: relative; - background-color: light-dark(@semi-transparent-dark-blue, @golden-40); - border: 1px solid var(--color-border); + background-color: @trait-color-bg; + border: 1px solid @trait-color-border; border-radius: 3px; - color: light-dark(var(--color-light-1), @golden); + color: @color-text-emphatic; font-size: var(--font-size-12); font-weight: 600; height: 1rem; @@ -250,7 +249,7 @@ width: 100%; padding: 0 0.1876px 0 0.375rem; margin-right: 0.125rem; /* makes it center SLIGHTLY */ - text-shadow: 1px 1px 2px @light-black; + text-shadow: 1px 1px 3px @color-text-shadow-contrast; display: flex; align-items: center; @@ -259,7 +258,7 @@ .tier-mark { position: absolute; background-color: @dark-blue; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 50%; width: 1rem; height: 1rem; @@ -279,6 +278,7 @@ } .trait-value-area { + --color-border: @trait-color-border; --background: light-dark(#e8e6e3, @dark-blue); display: flex; position: relative; @@ -298,7 +298,7 @@ .spellcasting-mark { position: absolute; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; color: @golden; left: 0; right: 0; diff --git a/styles/less/sheets/actors/character/loadout.less b/styles/less/sheets/actors/character/loadout.less index 5c0abef3..a896b92e 100644 --- a/styles/less/sheets/actors/character/loadout.less +++ b/styles/less/sheets/actors/character/loadout.less @@ -45,7 +45,7 @@ .btn-toggle-view { background: light-dark(@dark-blue-10, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 15px; padding: 0; gap: 0; diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index a4c7c0db..3c358d8f 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -40,7 +40,7 @@ .application.sheet.dh-style .character-sidebar-sheet { width: 275px; min-width: 275px; - border-right: 1px solid light-dark(@dark-blue, @golden); + border-right: 1px solid @color-border; .portrait { position: relative; @@ -168,7 +168,7 @@ appearance: none; width: 100px; height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; @@ -282,7 +282,7 @@ &:hover { background: light-dark(@light-black, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; h4, i { @@ -309,7 +309,7 @@ flex-wrap: wrap; gap: 4px; padding: 5px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; @@ -343,7 +343,7 @@ &:hover { background: light-dark(@light-black, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; .label, .value, @@ -375,7 +375,7 @@ text-align: center; z-index: 2; color: light-dark(@dark-blue, @beige); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; @@ -411,7 +411,7 @@ appearance: none; width: 80px; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: light-dark(transparent, @dark-blue); @@ -450,7 +450,7 @@ display: flex; width: 50px; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; padding: 0 6px; @@ -515,7 +515,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index 6cf886ab..b4df96bf 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -30,14 +30,14 @@ font-size: var(--font-size-24); outline: 2px solid transparent; box-shadow: unset; - text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); - + text-shadow: 0 0 4px @color-text-shadow-contrast, 0 0 8px @color-text-shadow-contrast, 0 0 14px @color-text-shadow-contrast; + height: 2rem; text-align: center; transition: all 0.3s ease; &:hover { - outline: 2px solid light-dark(@dark, @golden); + outline: 2px solid @color-border; } } } @@ -68,7 +68,7 @@ display: flex; width: 50px; height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; padding: 0 6px; @@ -100,104 +100,6 @@ } } - // .status-bar { - // display: flex; - // justify-content: center; - // position: relative; - // width: 100px; - // height: 40px; - - // .status-label { - // position: relative; - // top: 40px; - // height: 22px; - // width: 79px; - // clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); - // background: light-dark(@dark-blue, @golden); - - // h4 { - // font-weight: bold; - // text-align: center; - // line-height: 18px; - // color: light-dark(@beige, @dark-blue); - // } - // } - // .status-value { - // position: absolute; - // display: flex; - // padding: 0 6px; - // font-size: 1.5rem; - // align-items: center; - // width: 100px; - // height: 40px; - // justify-content: center; - // text-align: center; - // z-index: 2; - // color: @beige; - - // input[type='number'] { - // background: transparent; - // font-size: 1.5rem; - // width: 40px; - // height: 30px; - // text-align: center; - // border: none; - // outline: 2px solid transparent; - // color: @beige; - - // &.bar-input { - // padding: 0; - // color: @beige; - // backdrop-filter: none; - // background: transparent; - // transition: all 0.3s ease; - - // &:hover, - // &:focus { - // background: @semi-transparent-dark-blue; - // backdrop-filter: blur(9.5px); - // } - // } - // } - - // .bar-label { - // width: 40px; - // } - // } - // .progress-bar { - // position: absolute; - // appearance: none; - // width: 100px; - // height: 40px; - // border: 1px solid light-dark(@dark-blue, @golden); - // border-radius: 6px; - // z-index: 1; - // background: @dark-blue; - - // &::-webkit-progress-bar { - // border: none; - // background: @dark-blue; - // border-radius: 6px; - // } - // &::-webkit-progress-value { - // background: @gradient-hp; - // border-radius: 6px; - // } - // &.stress-color::-webkit-progress-value { - // background: @gradient-stress; - // border-radius: 6px; - // } - // &::-moz-progress-bar { - // background: @gradient-hp; - // border-radius: 6px; - // } - // &.stress-color::-moz-progress-bar { - // background: @gradient-stress; - // border-radius: 6px; - // } - // } - // } - .level-div { white-space: nowrap; display: flex; diff --git a/styles/less/sheets/actors/environment/header.less b/styles/less/sheets/actors/environment/header.less index ce7e6163..85471af4 100644 --- a/styles/less/sheets/actors/environment/header.less +++ b/styles/less/sheets/actors/environment/header.less @@ -36,15 +36,14 @@ transition: all 0.3s ease; outline: 2px solid transparent; box-shadow: none; - text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); - + text-shadow: 0 0 4px @color-text-shadow-contrast, 0 0 8px @color-text-shadow-contrast, 0 0 14px @color-text-shadow-contrast; padding-left: 0; height: 2.625rem; &:hover[type='text'], &:focus[type='text'] { box-shadow: none; - outline: 2px solid light-dark(@dark-blue, @golden); + outline: 2px solid @color-border; } } @@ -98,7 +97,7 @@ display: flex; width: 50px; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; padding: 0 6px; diff --git a/styles/less/sheets/actors/party/header.less b/styles/less/sheets/actors/party/header.less index 18d69834..58e3bc31 100644 --- a/styles/less/sheets/actors/party/header.less +++ b/styles/less/sheets/actors/party/header.less @@ -20,11 +20,12 @@ input.item-name[type='text'] { backdrop-filter: none; border: none; + color: @color-text-emphatic; font-family: @font-title; font-size: var(--font-size-32); outline: 2px solid transparent; box-shadow: unset; - text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); + text-shadow: 0 0 4px @color-text-shadow-contrast, 0 0 8px @color-text-shadow-contrast, 0 0 14px @color-text-shadow-contrast; text-align: center; transition: all 0.3s ease; @@ -33,7 +34,7 @@ &:hover[type='text'], &:focus[type='text'] { - outline: 2px solid light-dark(@dark-blue, @golden); + outline: 2px solid @color-border; } } diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index 380f98b1..dc464291 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -48,7 +48,7 @@ border-radius: 50%; width: 24px; height: 24px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; object-fit: cover; } } @@ -81,7 +81,7 @@ background-color: light-dark(var(--color-light-1), @dark-blue); color: @color-text-emphatic; padding: 4px 6px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 3px; align-items: baseline; width: fit-content; @@ -134,7 +134,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 3px 6px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 3px; align-items: center; width: fit-content; @@ -214,7 +214,7 @@ background-color: light-dark(@dark-blue-10, @dark-blue); color: @color-text-emphatic; padding: 2px 5px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 0 6px 6px 0; width: fit-content; min-height: 22px; @@ -232,7 +232,7 @@ .slot { width: 16px; height: 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; background: light-dark(@dark-blue-10, @golden-10); border-radius: 3px; transition: all 0.3s ease; @@ -248,7 +248,7 @@ .traits { background-color: light-dark(@dark-blue-10, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; display: grid; grid-template-columns: 1fr 1fr; diff --git a/styles/less/ui/effects-display/sheet.less b/styles/less/ui/effects-display/sheet.less index 17d9889f..80ad0d65 100644 --- a/styles/less/ui/effects-display/sheet.less +++ b/styles/less/ui/effects-display/sheet.less @@ -20,7 +20,7 @@ .effect-container { position: relative; pointer-events: all; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 3px; img { diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index 90da7ed3..1387f444 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -266,7 +266,7 @@ align-items: center; background-color: light-dark(@dark-15, @dark-golden-80); color: @color-text-emphatic; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 3px; min-height: 30px; font-weight: bold; diff --git a/styles/less/ui/settings/homebrew-settings/domains.less b/styles/less/ui/settings/homebrew-settings/domains.less index 406294ac..6314cc66 100644 --- a/styles/less/ui/settings/homebrew-settings/domains.less +++ b/styles/less/ui/settings/homebrew-settings/domains.less @@ -60,7 +60,7 @@ position: relative; display: flex; justify-content: center; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; &.selectable { @@ -76,7 +76,7 @@ .domain-label { position: absolute; top: 4px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; padding: 0 2px; color: light-dark(@dark, @beige); diff --git a/styles/less/ui/settings/homebrew-settings/types.less b/styles/less/ui/settings/homebrew-settings/types.less index d09431f7..1d568853 100644 --- a/styles/less/ui/settings/homebrew-settings/types.less +++ b/styles/less/ui/settings/homebrew-settings/types.less @@ -21,7 +21,7 @@ border: 1px solid; border-radius: 6px; padding: 0 8px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); cursor: pointer; diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index dd626358..bb219ebb 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -51,7 +51,7 @@ --dark-blue-50: #18162e50; --dark-blue-60: #18162e60; --dark-blue-90: #18162e90; - --dark-blue-d0: #18162ed0; + --dark-blue-c0: #18162ec0; --semi-transparent-dark-blue: rgba(24, 22, 46, 0.33); --dark: #222222; @@ -71,6 +71,7 @@ --beige-filter: brightness(0) saturate(100%) invert(87%) sepia(25%) saturate(164%) hue-rotate(339deg) brightness(106%) contrast(87%); --bright-beige-filter: brightness(0) saturate(100%) invert(87%) sepia(15%) saturate(343%) hue-rotate(333deg) brightness(110%) contrast(87%); + --light-white: rgba(255, 255, 255, 0.3); --soft-white-shadow: rgba(255, 255, 255, 0.05); --light-black: rgba(0, 0, 0, 0.3); @@ -85,13 +86,27 @@ --primary-color-fear: rgba(9, 71, 179, 0.75); } +/** + * Override core foundry color variables in theme scopes, and add some new theme specific variants. + * These are intended to be more representative of its use case, so avoid naming these after the color. + * Some foundry variables are heavily scoped and we need to redefine them (https://github.com/foundryvtt/foundryvtt/issues/12893) + * Convention for the redefined ones is --dh-{element}-color-{thing} + */ @scope (.theme-light) to (.themed) { .dh-style :scope, :scope.dh-style, .dh-style, .duality { + --color-border: @dark-blue; + --color-fieldset-border: @dark-blue; --color-text-emphatic: @dark-blue; --color-text-subtle: #555; + + --dh-color-text-shadow-contrast: #ffffffa0; + --dh-input-color-border: @dark; + --dh-input-color-text: @dark; + --dh-trait-color-bg: #b1afb6; + --dh-trait-color-border: #8e8d96; } } @scope (.theme-dark) to (.themed) { @@ -99,8 +114,16 @@ :scope.dh-style, .dh-style, .duality { + --color-border: @golden; + --color-fieldset-border: @golden; --color-text-emphatic: @golden; --color-text-subtle: #a29086; + + --dh-color-text-shadow-contrast: @dark-80; + --dh-input-color-border: @beige; + --dh-input-color-text: @beige; + --dh-trait-color-bg: #50433F; + --dh-trait-color-border: #927952; } } @@ -156,7 +179,7 @@ @dark-blue-50: var(--dark-blue-50, #18162e50); @dark-blue-60: var(--dark-blue-60, #18162e60); @dark-blue-90: var(--dark-blue-90, #18162e90); -@dark-blue-d0: var(--dark-blue-d0, #18162ed0); +@dark-blue-c0: var(--dark-blue-c0, #18162ec0); @semi-transparent-dark-blue: var(--semi-transparent-dark-blue, rgba(24, 22, 46, 0.33)); @dark: var(--dark, #222222); @@ -178,6 +201,7 @@ @soft-white-shadow: var(--soft-white-shadow, rgba(255, 255, 255, 0.05)); +@light-white: var(--light-white, rgba(255, 255, 255, 0.3)); @light-black: var(--light-black, rgba(0, 0, 0, 0.3)); @soft-shadow: var(--soft-shadow, rgba(0, 0, 0, 0.05)); @@ -211,6 +235,13 @@ } // LESS variable versions of core foundry color variables +@color-border: var(--color-border); +@color-fieldset-border: var(--color-fieldset-border); @color-text-emphatic: var(--color-text-emphatic); @color-text-primary: var(--color-text-primary); @color-text-subtle: var(--color-text-subtle); +@color-text-shadow-contrast: var(--dh-color-text-shadow-contrast); +@input-color-border: var(--dh-input-color-border); +@input-color-text: var(--dh-input-color-text); +@trait-color-bg: var(--dh-trait-color-bg); +@trait-color-border: var(--dh-trait-color-border); \ No newline at end of file diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index b37bfc06..f2d31232 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -50,14 +50,15 @@ */ .dh-typography() { h1 { + --dh-input-color-text: @color-text-emphatic; font-family: @font-title; margin: 0; border: none; font-weight: normal; - } - - h1 input[type='text'] { - font-family: @font-title; + input[type='text'], + .input[contenteditable] { + font-family: @font-title; + } } h2, diff --git a/styles/less/ux/tooltip/armorManagement.less b/styles/less/ux/tooltip/armorManagement.less index ca26e2e8..497df4f5 100644 --- a/styles/less/ux/tooltip/armorManagement.less +++ b/styles/less/ux/tooltip/armorManagement.less @@ -83,7 +83,7 @@ appearance: none; width: 100%; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; @@ -118,7 +118,7 @@ flex-wrap: wrap; gap: 4px; padding: 5px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; diff --git a/styles/less/ux/tooltip/resource-management.less b/styles/less/ux/tooltip/resource-management.less index 5daccd32..6e7e7851 100644 --- a/styles/less/ux/tooltip/resource-management.less +++ b/styles/less/ux/tooltip/resource-management.less @@ -11,7 +11,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less index 541b3160..f02499e2 100644 --- a/styles/less/ux/tooltip/tooltip.less +++ b/styles/less/ux/tooltip/tooltip.less @@ -238,7 +238,7 @@ aside[role='tooltip'].locked-tooltip:has(div.daggerheart.dh-style.tooltip.card-s .tooltip-chip { font-size: var(--font-size-18); padding: 2px 4px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url(../assets/parchments/dh-parchment-dark.png); From ac72012387e02cf8878b9ce7e88a015be46cbefe Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 27 May 2026 16:20:53 -0400 Subject: [PATCH 46/73] Fix setting dialogs created from overriden light sheet actors (#1939) --- .../character-settings/sheet.less | 18 ++++++++++-------- styles/less/utils/mixin.less | 8 ++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/styles/less/sheets-settings/character-settings/sheet.less b/styles/less/sheets-settings/character-settings/sheet.less index eab29436..37906712 100644 --- a/styles/less/sheets-settings/character-settings/sheet.less +++ b/styles/less/sheets-settings/character-settings/sheet.less @@ -1,17 +1,19 @@ @import '../../utils/colors.less'; -.theme-light .application.daggerheart.dh-style.dialog { - .tab.details { - .traits-inner-container .trait-container { - background: url('../assets/svg/trait-shield-light.svg') no-repeat; +.appTheme({}, { + &.dialog.character-settings { + .tab.details { + .traits-inner-container .trait-container { + background: url('../assets/svg/trait-shield-light.svg') no-repeat; - div { - filter: drop-shadow(0 0 3px @beige); - text-shadow: 0 0 3px @beige; + div { + filter: drop-shadow(0 0 3px @beige); + text-shadow: 0 0 3px @beige; + } } } } -} +}); .application.daggerheart.dh-style.dialog { .tab.details { diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index f2d31232..237a5acb 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -5,16 +5,16 @@ */ .appTheme(@darkRules, @lightRules) { // Dark theme selectors - .themed.theme-dark .application.daggerheart.sheet.dh-style, - .themed.theme-dark.application.daggerheart.sheet.dh-style, + .themed.theme-dark .application.daggerheart.dh-style, + .themed.theme-dark.application.daggerheart.dh-style, body.theme-dark .application.daggerheart, body.theme-dark.application.daggerheart { @darkRules(); } // Light theme selectors - .themed.theme-light .application.daggerheart.sheet.dh-style, - .themed.theme-light.application.daggerheart.sheet.dh-style, + .themed.theme-light .application.daggerheart.dh-style, + .themed.theme-light.application.daggerheart.dh-style, body.theme-light .application.daggerheart, body.theme-light.application.daggerheart { @lightRules(); From ddf4747310cdb39de9ca95738aa6084e0f1a7f89 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 27 May 2026 22:25:00 +0200 Subject: [PATCH 47/73] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index bbee2c09..2acd7570 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.2.6", + "version": "2.2.7", "compatibility": { "minimum": "14.361", "verified": "14.363", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.6/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.7/system.zip", "authors": [ { "name": "WBHarry" From f1a530f57f71f18c40b34e3b345cad40b1cda5c0 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 29 May 2026 12:19:08 +0200 Subject: [PATCH 48/73] [Feature] Full Rerolls (#1928) * Initial * Removed damage dialogs * Fixed DamageReroll * Fixed d20 modifiers * Fixed * Fixed DiceSoNice multiple damageType reroll * Added triggerChatRollFx * Fixed dice.denomination being lost on damage reroll --- lang/en.json | 7 +- module/applications/dialogs/_module.mjs | 1 - .../applications/dialogs/groupRollDialog.mjs | 2 - .../dialogs/rerollDamageDialog.mjs | 280 ------------------ module/applications/dialogs/rerollDialog.mjs | 279 ----------------- .../dialogs/resourceDiceDialog.mjs | 4 +- module/applications/dialogs/tagTeamDialog.mjs | 2 - module/applications/ui/chatLog.mjs | 18 +- module/data/chat-message/actorRoll.mjs | 31 ++ module/data/fields/action/damageField.mjs | 3 - module/data/fields/action/summonField.mjs | 6 +- module/dice/d20Roll.mjs | 12 + module/dice/damageRoll.mjs | 42 +-- module/dice/dhRoll.mjs | 6 +- module/dice/die/dualityDie.mjs | 22 +- module/dice/dualityRoll.mjs | 46 ++- module/dice/helpers.mjs | 17 ++ module/helpers/utils.mjs | 15 + styles/less/dialog/index.less | 2 - styles/less/dialog/reroll-dialog/sheet.less | 125 -------- .../dialogs/rerollDialog/damage/main.hbs | 35 --- templates/dialogs/rerollDialog/footer.hbs | 4 - templates/dialogs/rerollDialog/main.hbs | 35 --- 23 files changed, 164 insertions(+), 830 deletions(-) delete mode 100644 module/applications/dialogs/rerollDamageDialog.mjs delete mode 100644 module/applications/dialogs/rerollDialog.mjs create mode 100644 module/dice/helpers.mjs delete mode 100644 styles/less/dialog/reroll-dialog/sheet.less delete mode 100644 templates/dialogs/rerollDialog/damage/main.hbs delete mode 100644 templates/dialogs/rerollDialog/footer.hbs delete mode 100644 templates/dialogs/rerollDialog/main.hbs diff --git a/lang/en.json b/lang/en.json index a06c46c2..f1841e09 100755 --- a/lang/en.json +++ b/lang/en.json @@ -712,12 +712,6 @@ "ReactionRoll": { "title": "Reaction Roll: {trait}" }, - "RerollDialog": { - "title": "Reroll", - "damageTitle": "Reroll Damage", - "deselectDiceNotification": "Deselect one of the selected dice first", - "acceptCurrentRolls": "Accept Current Rolls" - }, "ResourceDice": { "title": "{name} Resource", "rerollDice": "Reroll Dice" @@ -3097,6 +3091,7 @@ } }, "ChatLog": { + "rerollActionRoll": "Reroll Action", "rerollDamage": "Reroll Damage", "assignTagRoll": "Assign as Tag Roll" }, diff --git a/module/applications/dialogs/_module.mjs b/module/applications/dialogs/_module.mjs index c866f1cd..fc5169b2 100644 --- a/module/applications/dialogs/_module.mjs +++ b/module/applications/dialogs/_module.mjs @@ -10,7 +10,6 @@ export { default as ImageSelectDialog } from './imageSelectDialog.mjs'; export { default as ItemTransferDialog } from './itemTransfer.mjs'; export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs'; export { default as OwnershipSelection } from './ownershipSelection.mjs'; -export { default as RerollDamageDialog } from './rerollDamageDialog.mjs'; export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs'; export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs'; export { default as TagTeamDialog } from './tagTeamDialog.mjs'; diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index bd45fe91..52baf537 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -358,8 +358,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat }); if (!result) return; - // todo: move logic to actor.rollTrait() or actor.diceRoll() - if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); const rollData = result.messageRoll.toJSON(); delete rollData.options.messageRoll; diff --git a/module/applications/dialogs/rerollDamageDialog.mjs b/module/applications/dialogs/rerollDamageDialog.mjs deleted file mode 100644 index b821bd24..00000000 --- a/module/applications/dialogs/rerollDamageDialog.mjs +++ /dev/null @@ -1,280 +0,0 @@ -const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; - -export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(message, options = {}) { - super(options); - - this.message = message; - this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => { - const type = message.system.damage[typeKey]; - acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => { - const part = type.parts[partKey]; - acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => { - const dice = part.dice[diceKey]; - const activeResults = dice.results.filter(x => x.active); - acc[diceKey] = { - dice: dice.dice, - selectedResults: activeResults.length, - maxSelected: activeResults.length, - results: activeResults.map(x => ({ ...x, selected: true })) - }; - - return acc; - }, {}); - - return acc; - }, {}); - - return acc; - }, {}); - } - - static DEFAULT_OPTIONS = { - id: 'reroll-dialog', - classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'], - window: { - icon: 'fa-solid fa-dice' - }, - actions: { - toggleResult: RerollDamageDialog.#toggleResult, - selectRoll: RerollDamageDialog.#selectRoll, - doReroll: RerollDamageDialog.#doReroll, - save: RerollDamageDialog.#save - } - }; - - /** @override */ - static PARTS = { - main: { - id: 'main', - template: 'systems/daggerheart/templates/dialogs/rerollDialog/damage/main.hbs' - }, - footer: { - id: 'footer', - template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs' - } - }; - - get title() { - return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.damageTitle'); - } - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - htmlElement.querySelectorAll('.to-reroll-input').forEach(element => { - element.addEventListener('change', this.toggleDice.bind(this)); - }); - } - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.damage = this.damage; - context.disabledReroll = !this.getRerollDice().length; - context.saveDisabled = !this.isSelectionDone(); - - return context; - } - - static async #save() { - const update = { - 'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => { - const type = this.damage[typeKey]; - let typeTotal = 0; - const messageType = this.message.system.damage[typeKey]; - const parts = Object.keys(type).map(partKey => { - const part = type[partKey]; - const messagePart = messageType.parts[partKey]; - let partTotal = messagePart.modifierTotal; - const dice = Object.keys(part).map(diceKey => { - const dice = part[diceKey]; - const total = dice.results.reduce((acc, result) => { - if (result.active) acc += result.result; - return acc; - }, 0); - partTotal += total; - const messageDice = messagePart.dice[diceKey]; - return { - ...messageDice, - total: total, - results: dice.results.map(x => ({ - ...x, - hasRerolls: dice.results.length > 1 - })) - }; - }); - - typeTotal += partTotal; - return { - ...messagePart, - total: partTotal, - dice: dice - }; - }); - - acc[typeKey] = { - ...messageType, - total: typeTotal, - parts: parts - }; - - return acc; - }, {}) - }; - - await this.message.update(update); - await this.close(); - } - - getRerollDice() { - const rerollDice = []; - Object.keys(this.damage).forEach(typeKey => { - const type = this.damage[typeKey]; - Object.keys(type).forEach(partKey => { - const part = type[partKey]; - Object.keys(part).forEach(diceKey => { - const dice = part[diceKey]; - Object.keys(dice.results).forEach(resultKey => { - const result = dice.results[resultKey]; - if (result.toReroll) { - rerollDice.push({ - ...result, - dice: dice.dice, - type: typeKey, - part: partKey, - dice: diceKey, - result: resultKey - }); - } - }); - }); - }); - }); - - return rerollDice; - } - - isSelectionDone() { - const diceFinishedData = []; - Object.keys(this.damage).forEach(typeKey => { - const type = this.damage[typeKey]; - Object.keys(type).forEach(partKey => { - const part = type[partKey]; - Object.keys(part).forEach(diceKey => { - const dice = part[diceKey]; - const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0); - diceFinishedData.push(selected === dice.maxSelected); - }); - }); - }); - - return diceFinishedData.every(x => x); - } - - toggleDice(event) { - const target = event.target; - const { type, part, dice } = target.dataset; - const toggleDice = this.damage[type][part][dice]; - - const existingDiceRerolls = this.getRerollDice().filter( - x => x.type === type && x.part === part && x.dice === dice - ); - - const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length; - - toggleDice.toReroll = !allRerolled; - toggleDice.results.forEach(result => { - if (result.active) { - result.toReroll = !allRerolled; - } - }); - - this.render(); - } - - static #toggleResult(event) { - event.stopPropagation(); - - const target = event.target.closest('.to-reroll-result'); - const { type, part, dice, result } = target.dataset; - const toggleDice = this.damage[type][part][dice]; - const toggleResult = toggleDice.results[result]; - toggleResult.toReroll = !toggleResult.toReroll; - - const existingDiceRerolls = this.getRerollDice().filter( - x => x.type === type && x.part === part && x.dice === dice - ); - - const allToReroll = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length; - toggleDice.toReroll = allToReroll; - - this.render(); - } - - static async #selectRoll(_, button) { - const { type, part, dice, result } = button.dataset; - - const diceVal = this.damage[type][part][dice]; - const diceResult = diceVal.results[result]; - if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) { - return ui.notifications.warn( - game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification') - ); - } - - if (diceResult.active) { - diceVal.toReroll = false; - diceResult.toReroll = false; - } - - diceVal.selectedResults += diceResult.active ? -1 : 1; - diceResult.active = !diceResult.active; - - this.render(); - } - - static async #doReroll() { - const toReroll = this.getRerollDice().map(x => { - const { type, part, dice, result } = x; - const diceData = this.damage[type][part][dice].results[result]; - return { - ...diceData, - dice: this.damage[type][part][dice].dice, - typeKey: type, - partKey: part, - diceKey: dice, - resultsIndex: result - }; - }); - - const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate(); - - if (game.modules.get('dice-so-nice')?.active) { - const diceSoNiceRoll = { - _evaluated: true, - dice: roll.dice, - options: { appearance: {} } - }; - - await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); - } - - toReroll.forEach((data, index) => { - const { typeKey, partKey, diceKey, resultsIndex } = data; - const rerolledDice = roll.dice[index]; - - const dice = this.damage[typeKey][partKey][diceKey]; - dice.toReroll = false; - dice.results[resultsIndex].active = false; - dice.results[resultsIndex].discarded = true; - dice.results[resultsIndex].toReroll = false; - dice.results.splice(dice.results.length, 0, { - ...rerolledDice.results[0], - toReroll: false, - selected: true - }); - }); - - this.render(); - } -} diff --git a/module/applications/dialogs/rerollDialog.mjs b/module/applications/dialogs/rerollDialog.mjs deleted file mode 100644 index cae4e53a..00000000 --- a/module/applications/dialogs/rerollDialog.mjs +++ /dev/null @@ -1,279 +0,0 @@ -const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; - -export default class RerollDialog extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(message, options = {}) { - super(options); - - this.message = message; - this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => { - const type = message.system.damage[typeKey]; - acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => { - const part = type.parts[partKey]; - acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => { - const dice = part.dice[diceKey]; - const activeResults = dice.results.filter(x => x.active); - acc[diceKey] = { - dice: dice.dice, - selectedResults: activeResults.length, - maxSelected: activeResults.length, - results: activeResults.map(x => ({ ...x, selected: true })) - }; - - return acc; - }, {}); - - return acc; - }, {}); - - return acc; - }, {}); - } - - static DEFAULT_OPTIONS = { - id: 'reroll-dialog', - classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'], - window: { - icon: 'fa-solid fa-dice' - }, - actions: { - toggleResult: RerollDialog.#toggleResult, - selectRoll: RerollDialog.#selectRoll, - doReroll: RerollDialog.#doReroll, - save: RerollDialog.#save - } - }; - - /** @override */ - static PARTS = { - main: { - id: 'main', - template: 'systems/daggerheart/templates/dialogs/rerollDialog/main.hbs' - }, - footer: { - id: 'footer', - template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs' - } - }; - - get title() { - return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.title'); - } - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - htmlElement.querySelectorAll('.to-reroll-input').forEach(element => { - element.addEventListener('change', this.toggleDice.bind(this)); - }); - } - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.damage = this.damage; - context.disabledReroll = !this.getRerollDice().length; - context.saveDisabled = !this.isSelectionDone(); - - return context; - } - - static async #save() { - const update = { - 'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => { - const type = this.damage[typeKey]; - let typeTotal = 0; - const messageType = this.message.system.damage[typeKey]; - const parts = Object.keys(type).map(partKey => { - const part = type[partKey]; - const messagePart = messageType.parts[partKey]; - let partTotal = messagePart.modifierTotal; - const dice = Object.keys(part).map(diceKey => { - const dice = part[diceKey]; - const total = dice.results.reduce((acc, result) => { - if (result.active) acc += result.result; - return acc; - }, 0); - partTotal += total; - const messageDice = messagePart.dice[diceKey]; - return { - ...messageDice, - total: total, - results: dice.results.map(x => ({ - ...x, - hasRerolls: dice.results.length > 1 - })) - }; - }); - - typeTotal += partTotal; - return { - ...messagePart, - total: partTotal, - dice: dice - }; - }); - - acc[typeKey] = { - ...messageType, - total: typeTotal, - parts: parts - }; - - return acc; - }, {}) - }; - await this.message.update(update); - await this.close(); - } - - getRerollDice() { - const rerollDice = []; - Object.keys(this.damage).forEach(typeKey => { - const type = this.damage[typeKey]; - Object.keys(type).forEach(partKey => { - const part = type[partKey]; - Object.keys(part).forEach(diceKey => { - const dice = part[diceKey]; - Object.keys(dice.results).forEach(resultKey => { - const result = dice.results[resultKey]; - if (result.toReroll) { - rerollDice.push({ - ...result, - dice: dice.dice, - type: typeKey, - part: partKey, - dice: diceKey, - result: resultKey - }); - } - }); - }); - }); - }); - - return rerollDice; - } - - isSelectionDone() { - const diceFinishedData = []; - Object.keys(this.damage).forEach(typeKey => { - const type = this.damage[typeKey]; - Object.keys(type).forEach(partKey => { - const part = type[partKey]; - Object.keys(part).forEach(diceKey => { - const dice = part[diceKey]; - const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0); - diceFinishedData.push(selected === dice.maxSelected); - }); - }); - }); - - return diceFinishedData.every(x => x); - } - - toggleDice(event) { - const target = event.target; - const { type, part, dice } = target.dataset; - const toggleDice = this.damage[type][part][dice]; - - const existingDiceRerolls = this.getRerollDice().filter( - x => x.type === type && x.part === part && x.dice === dice - ); - - const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length; - - toggleDice.toReroll = !allRerolled; - toggleDice.results.forEach(result => { - if (result.active) { - result.toReroll = !allRerolled; - } - }); - - this.render(); - } - - static #toggleResult(event) { - event.stopPropagation(); - - const target = event.target.closest('.to-reroll-result'); - const { type, part, dice, result } = target.dataset; - const toggleDice = this.damage[type][part][dice]; - const toggleResult = toggleDice.results[result]; - toggleResult.toReroll = !toggleResult.toReroll; - - const existingDiceRerolls = this.getRerollDice().filter( - x => x.type === type && x.part === part && x.dice === dice - ); - - const allToReroll = existingDiceRerolls.length === toggleDice.results.length; - toggleDice.toReroll = allToReroll; - - this.render(); - } - - static async #selectRoll(_, button) { - const { type, part, dice, result } = button.dataset; - - const diceVal = this.damage[type][part][dice]; - const diceResult = diceVal.results[result]; - if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) { - return ui.notifications.warn( - game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification') - ); - } - - if (diceResult.active) { - diceVal.toReroll = false; - diceResult.toReroll = false; - } - - diceVal.selectedResults += diceResult.active ? -1 : 1; - diceResult.active = !diceResult.active; - - this.render(); - } - - static async #doReroll() { - const toReroll = this.getRerollDice().map(x => { - const { type, part, dice, result } = x; - const diceData = this.damage[type][part][dice].results[result]; - return { - ...diceData, - dice: this.damage[type][part][dice].dice, - typeKey: type, - partKey: part, - diceKey: dice, - resultsIndex: result - }; - }); - - const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate(); - - if (game.modules.get('dice-so-nice')?.active) { - const diceSoNiceRoll = { - _evaluated: true, - dice: roll.dice, - options: { appearance: {} } - }; - - await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); - } - - toReroll.forEach((data, index) => { - const { typeKey, partKey, diceKey, resultsIndex } = data; - const rerolledDice = roll.dice[index]; - - const dice = this.damage[typeKey][partKey][diceKey]; - dice.toReroll = false; - dice.results[resultsIndex].active = false; - dice.results[resultsIndex].discarded = true; - dice.results[resultsIndex].toReroll = false; - dice.results.splice(dice.results.length, 0, { - ...rerolledDice.results[0], - toReroll: false, - selected: true - }); - }); - - this.render(); - } -} diff --git a/module/applications/dialogs/resourceDiceDialog.mjs b/module/applications/dialogs/resourceDiceDialog.mjs index 32e1e5d8..8394538c 100644 --- a/module/applications/dialogs/resourceDiceDialog.mjs +++ b/module/applications/dialogs/resourceDiceDialog.mjs @@ -1,4 +1,4 @@ -import { itemAbleRollParse } from '../../helpers/utils.mjs'; +import { itemAbleRollParse, triggerChatRollFx } from '../../helpers/utils.mjs'; const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; @@ -69,7 +69,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item); const diceFormula = `${max}${this.item.system.resource.dieFaces}`; const roll = await new Roll(diceFormula).evaluate(); - if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true); + await triggerChatRollFx([roll]); this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false })); this.resetUsed = true; diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index ba76831f..4e63d93b 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -434,8 +434,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio if (!result) return; - if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); - const rollData = result.messageRoll.toJSON(); delete rollData.options.messageRoll; this.updatePartyData( diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 34b25591..7036a5df 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -103,6 +103,19 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo _getEntryContextOptions() { return [ ...super._getEntryContextOptions(), + { + label: 'DAGGERHEART.UI.ChatLog.rerollActionRoll', + icon: '', + visible: li => { + const message = game.messages.get(li.dataset.messageId); + return message.system.hasRoll && (game.user.isGM || message.isAuthor); + }, + callback: async li => { + const message = game.messages.get(li.dataset.messageId); + const reroll = await message.rolls[0].reroll({ liveRoll: true }); + message.update({ rolls: [reroll] }); + } + }, { label: 'DAGGERHEART.UI.ChatLog.rerollDamage', icon: '', @@ -113,9 +126,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo : false; return (game.user.isGM || message.isAuthor) && hasRolledDamage; }, - callback: li => { + callback: async li => { const message = game.messages.get(li.dataset.messageId); - new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true }); + const update = await message.system.getRerolledDamage(); + message.update(update); } } ]; diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index eaa1cdc2..ccfe25ea 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -1,3 +1,5 @@ +import { triggerChatRollFx } from '../../helpers/utils.mjs'; + const fields = foundry.data.fields; const targetsField = () => @@ -130,6 +132,35 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { }); } + /* TODO: Change how damage data is stored somehow to enable better rerolling */ + async getRerolledDamage() { + if (!this.damage) return; + + const rerolls = []; + const update = { system: { damage: {} } }; + for (const partKey in this.damage) { + const part = this.damage[partKey]; + const testRoll = Roll.fromData(part.parts[0].roll); + const rerolled = await testRoll.reroll(); + rerolls.push(rerolled); + + if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] }; + const partData = update.system.damage[partKey].parts[0]; + update.system.damage[partKey].total = rerolled.total; + partData.modifierTotal = rerolled.terms.reduce((acc, x) => { + if (x.isDeterministic && !x.operator) acc += x.total; + return acc; + }, 0); + partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination })); + partData.total = rerolled.total; + partData.roll = rerolled.toJSON(); + } + + await triggerChatRollFx(rerolls); + + return update; + } + registerTargetHook() { if (!this.parent.isAuthor || !this.hasTarget) return; if (this.targetMode && this.parent.targetHook !== null) { diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 30a5ad7c..9b21d3ba 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -72,9 +72,6 @@ export default class DamageField extends fields.SchemaField { damageConfig.source.message = messageId; damageConfig.directDamage = !!damageConfig.source?.message; - // if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active) - // await game.dice3d.waitFor3DAnimationByMessageID(damageConfig.source.message); - const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig); if (!damageResult) return false; if (damageResult.actionChatMessageHandled) config.actionChatMessageHandled = true; diff --git a/module/data/fields/action/summonField.mjs b/module/data/fields/action/summonField.mjs index ec7881f7..a2275fa5 100644 --- a/module/data/fields/action/summonField.mjs +++ b/module/data/fields/action/summonField.mjs @@ -1,4 +1,4 @@ -import { itemAbleRollParse } from '../../../helpers/utils.mjs'; +import { itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs'; import FormulaField from '../formulaField.mjs'; const fields = foundry.data.fields; @@ -40,7 +40,7 @@ export default class DHSummonField extends fields.ArrayField { const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item)); await roll.evaluate(); const count = roll.total; - if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) rolls.push(roll); + if (!roll.isDeterministic) rolls.push(roll); const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); /* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */ @@ -56,7 +56,7 @@ export default class DHSummonField extends fields.ArrayField { } } - if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true))); + if (rolls.length) await triggerChatRollFx(rolls); this.actor.sheet?.minimize(); DHSummonField.handleSummon(summonData, this.actor); diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index 509f5d69..b1d3bd0b 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -1,4 +1,5 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; +import { triggerChatRollFx } from '../helpers/utils.mjs'; import DHRoll from './dhRoll.mjs'; export default class D20Roll extends DHRoll { @@ -224,4 +225,15 @@ export default class D20Roll extends DHRoll { resetFormula() { return (this._formula = this.constructor.getFormula(this.terms)); } + + async reroll(options) { + const result = await super.reroll(options); + if (this instanceof game.system.api.dice.DualityRoll) return result; + + if (options?.liveRoll) { + await triggerChatRollFx([result]); + } + + return result; + } } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 98fd8401..ef810ed7 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -1,5 +1,5 @@ import DamageDialog from '../applications/dialogs/damageDialog.mjs'; -import { parseRallyDice } from '../helpers/utils.mjs'; +import { parseRallyDice, triggerChatRollFx } from '../helpers/utils.mjs'; import DHRoll from './dhRoll.mjs'; export default class DamageRoll extends DHRoll { @@ -18,7 +18,12 @@ export default class DamageRoll extends DHRoll { if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate(); roll._evaluated = true; - const parts = config.roll.map(r => this.postEvaluate(r)); + + const parts = []; + for (const roll of config.roll) { + parts.push(this.postEvaluate(roll)); + roll.roll = JSON.stringify(roll.roll.toJSON()); + } config.damage = this.unifyDamageRoll(parts); } @@ -38,25 +43,24 @@ export default class DamageRoll extends DHRoll { const chatMessage = config.source?.message ? ui.chat.collection.get(config.source.message) : getDocumentClass('ChatMessage').applyMode({}, config.rollMode ?? 'public'); + + const diceRolls = []; if (game.modules.get('dice-so-nice')?.active) { - const pool = foundry.dice.terms.PoolTerm.fromRolls( - Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll)) - ), - diceRoll = Roll.fromTerms([pool]); - await game.dice3d.showForRoll( - diceRoll, - game.user, - true, - chatMessage.whisper?.length > 0 ? chatMessage.whisper : null, - chatMessage.blind - ); config.mute = true; + const pool = foundry.dice.terms.PoolTerm.fromRolls( + Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll)) + ); + diceRolls.push(Roll.fromTerms([pool])); } + + await triggerChatRollFx(diceRolls, { + whisper: chatMessage.whisper?.length > 0 ? chatMessage.whisper : null, + blind: chatMessage.blind + }); await super.buildPost(roll, config, message); + if (config.source?.message) { chatMessage.update({ 'system.damage': config.damage }); - - if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); } } @@ -319,9 +323,10 @@ export default class DamageRoll extends DHRoll { const newIndex = parsedDiceTerms[dice].results.length; await term.reroll(`/r1=${termResult.result}`); + const diceRolls = []; if (game.modules.get('dice-so-nice')?.active) { const newResult = parsedDiceTerms[dice].results[newIndex]; - const diceSoNiceRoll = { + diceRolls.push({ _evaluated: true, dice: [ new foundry.dice.terms.Die({ @@ -332,11 +337,10 @@ export default class DamageRoll extends DHRoll { }) ], options: { appearance: {} } - }; - - await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); + }); } + await triggerChatRollFx(diceRolls); await parsedRoll.evaluate(); const results = parsedRoll.dice[dice].results.map(result => ({ diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index d6975f71..02c4ab24 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -1,4 +1,5 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; +import { triggerChatRollFx } from '../helpers/utils.mjs'; export default class DHRoll extends Roll { baseTerms = []; @@ -75,9 +76,7 @@ export default class DHRoll extends Roll { } if (config.skips?.createMessage) { - if (game.modules.get('dice-so-nice')?.active) { - await game.dice3d.showForRoll(roll, game.user, true); - } + await triggerChatRollFx([roll]); } else if (!config.source?.message) { config.message = await this.toMessage(roll, config); } @@ -85,6 +84,7 @@ export default class DHRoll extends Roll { static postEvaluate(roll, config = {}) { return { + ...roll.options.roll, total: roll.total, formula: roll.formula, dice: roll.dice.map(d => ({ diff --git a/module/dice/die/dualityDie.mjs b/module/dice/die/dualityDie.mjs index 83229425..cc7ee75e 100644 --- a/module/dice/die/dualityDie.mjs +++ b/module/dice/die/dualityDie.mjs @@ -1,4 +1,4 @@ -import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; +import { updateResourcesForDualityReroll } from '../helpers.mjs'; export default class DualityDie extends foundry.dice.terms.Die { constructor(options) { @@ -12,24 +12,6 @@ export default class DualityDie extends foundry.dice.terms.Die { return roll.withHope ? 1 : roll.withFear ? -1 : 0; } - #updateResources(oldDuality, newDuality, actor) { - const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); - if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return; - - const updates = []; - const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0); - const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0); - const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0); - - if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true }); - if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true }); - if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true }); - - const resourceUpdates = new ResourceUpdateMap(actor); - resourceUpdates.addResources(updates); - resourceUpdates.updateResources(); - } - async reroll(modifier, options) { const oldDuality = this.#getDualityState(options.liveRoll.roll); await super.reroll(modifier, options); @@ -57,7 +39,7 @@ export default class DualityDie extends foundry.dice.terms.Die { if (options.liveRoll.isReaction) return; const newDuality = this.#getDualityState(options.liveRoll.roll); - this.#updateResources(oldDuality, newDuality, options.liveRoll.actor); + updateResourcesForDualityReroll(oldDuality, newDuality, options.liveRoll.actor); } } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index d58811fe..f40e9781 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -1,6 +1,8 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20Roll from './d20Roll.mjs'; import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; +import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; +import { updateResourcesForDualityReroll } from './helpers.mjs'; export default class DualityRoll extends D20Roll { _advantageNumber = 1; @@ -130,13 +132,7 @@ export default class DualityRoll extends D20Roll { } createBaseDice() { - if ( - this.dice[0] instanceof game.system.api.dice.diceTypes.HopeDie && - this.dice[1] instanceof game.system.api.dice.diceTypes.FearDie - ) { - this.terms = [this.terms[0], this.terms[1], this.terms[2]]; - return; - } + this.terms = [this.terms[0], this.terms[1], this.terms[2]]; this.terms[0] = new game.system.api.dice.diceTypes.HopeDie({ faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12 @@ -388,4 +384,40 @@ export default class DualityRoll extends D20Roll { if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id); } } + + async reroll(options) { + const oldDuality = this.withHope ? 1 : this.withFear ? -1 : 0; + const rerolled = DualityRoll.fromData((await super.reroll(options)).toJSON()); + + if (options?.liveRoll) { + if (game.modules.get('dice-so-nice')?.active) { + const diceAppearance = await getDiceSoNicePresets( + rerolled, + rerolled.dHope.denomination, + rerolled.dFear.denomination + ); + rerolled.dHope.options.appearance = diceAppearance.hope.appearance; + rerolled.dFear.options.appearance = diceAppearance.fear.appearance; + if (rerolled.dAdvantage) rerolled.dAdvantage.options.appearance = diceAppearance.advantage.appearance; + if (rerolled.dDisadvantage) + rerolled.dDisadvantage.options.appearance = diceAppearance.disadvantage.appearance; + + await game.dice3d.showForRoll(rerolled, game.user, true); + } else { + foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); + } + + if (this.options.actionType === 'reaction') return; + + const newDuality = rerolled.withHope ? 1 : rerolled.withFear ? -1 : 0; + const actor = await foundry.utils.fromUuid(this.options.source.actor); + updateResourcesForDualityReroll(oldDuality, newDuality, actor); + } + + return rerolled; + } + + fromJSON(json) { + return super.fromJSON(json); + } } diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs new file mode 100644 index 00000000..33519949 --- /dev/null +++ b/module/dice/helpers.mjs @@ -0,0 +1,17 @@ +export function updateResourcesForDualityReroll(oldDuality, newDuality, actor) { + const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return; + + const updates = []; + const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0); + const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0); + const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0); + + if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true }); + if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true }); + if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true }); + + const resourceUpdates = new ResourceUpdateMap(actor); + resourceUpdates.addResources(updates); + resourceUpdates.updateResources(); +} diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 7bc5fa25..8bc95aa0 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -864,3 +864,18 @@ export function camelize(str) { }) .replace(/\s+/g, ''); } + +/** + * Triggers DiceSoNice rolls or dice roll audio for rolls. Not used for duality rolls. + * @param { Roll[] } rolls + * @return { void } + */ +export async function triggerChatRollFx(rolls, options = { whisper: false, blind: false }) { + const { whisper, blind } = options; + if (game.modules.get('dice-so-nice')?.active) { + const rerollPromises = rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true, whisper, blind)); + await Promise.allSettled(rerollPromises); + } else { + foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); + } +} diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index 11d9635e..e8f61318 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -24,8 +24,6 @@ @import './multiclass-choice/sheet.less'; -@import './reroll-dialog/sheet.less'; - @import './tag-team-dialog/initialization.less'; @import './tag-team-dialog/sheet.less'; diff --git a/styles/less/dialog/reroll-dialog/sheet.less b/styles/less/dialog/reroll-dialog/sheet.less deleted file mode 100644 index 71c94d80..00000000 --- a/styles/less/dialog/reroll-dialog/sheet.less +++ /dev/null @@ -1,125 +0,0 @@ -.daggerheart.dialog.dh-style.views.reroll-dialog { - .window-content { - max-width: 648px; - } - - .reroll-outer-container { - h2 { - margin: 0; - } - - .dices-container { - display: flex; - flex-wrap: wrap; - gap: 8px; - } - - .dice-outer-container { - width: 300px; - - legend { - display: flex; - align-items: center; - gap: 4px; - - i { - margin-right: 4px; - } - } - - .dice-container { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; - - .result-container { - position: relative; - aspect-ratio: 1; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.375rem; - opacity: 0.8; - - &.selected { - opacity: 1; - border: 1px solid; - border-radius: 6px; - border-color: light-dark(@dark-blue, @golden); - filter: drop-shadow(0 0 3px @golden); - } - - &:before { - content: ' '; - position: absolute; - width: 100%; - height: 100%; - z-index: -1; - mask: var(--svg-die) no-repeat center; - mask-size: contain; - background: linear-gradient(139.01deg, #efe6d8 3.51%, #372e1f 96.49%); - } - - &.d4:before { - --svg-die: url(../assets/icons/dice/default/d4.svg); - } - &.d6:before { - --svg-die: url(../assets/icons/dice/default/d6.svg); - } - &.d8:before { - --svg-die: url(../assets/icons/dice/default/d8.svg); - } - &.d10:before { - --svg-die: url(../assets/icons/dice/default/d10.svg); - } - &.d12:before { - --svg-die: url('../assets/icons/dice/default/d12.svg'); - } - &.d20:before { - --svg-die: url(../assets/icons/dice/default/d20.svg); - } - - .to-reroll-result { - position: absolute; - bottom: -7px; - gap: 2px; - border: 1px solid; - border-radius: 6px; - background-image: url(../assets/parchments/dh-parchment-dark.png); - display: flex; - align-items: center; - padding: 2px 6px; - - input { - margin: 0; - height: 12px; - line-height: 0px; - position: relative; - top: 1px; - - &:before, - &:after { - line-height: 12px; - font-size: var(--font-size-12); - } - } - - i { - font-size: var(--font-size-10); - } - } - } - } - } - } - - footer { - margin-top: 8px; - display: flex; - justify-content: space-between; - - .controls { - display: flex; - gap: 8px; - } - } -} diff --git a/templates/dialogs/rerollDialog/damage/main.hbs b/templates/dialogs/rerollDialog/damage/main.hbs deleted file mode 100644 index 5b994bf6..00000000 --- a/templates/dialogs/rerollDialog/damage/main.hbs +++ /dev/null @@ -1,35 +0,0 @@ -
    - {{#each damage}} -

    {{localize (concat 'DAGGERHEART.CONFIG.HealingType.' @key '.name')}}

    - {{#each this}} -
    - {{#each this}} -
    - - - - {{this.selectedResults}}/{{this.maxSelected}} Selected - - -
    - {{#each this.results}} -
    - {{this.result}} - {{#if this.active}} - - - - - {{/if}} -
    - {{/each}} -
    -
    - {{/each}} -
    - {{/each}} - {{/each}} -
    \ No newline at end of file diff --git a/templates/dialogs/rerollDialog/footer.hbs b/templates/dialogs/rerollDialog/footer.hbs deleted file mode 100644 index 5d4ae2b2..00000000 --- a/templates/dialogs/rerollDialog/footer.hbs +++ /dev/null @@ -1,4 +0,0 @@ -
    - - -
    \ No newline at end of file diff --git a/templates/dialogs/rerollDialog/main.hbs b/templates/dialogs/rerollDialog/main.hbs deleted file mode 100644 index 6f10ce33..00000000 --- a/templates/dialogs/rerollDialog/main.hbs +++ /dev/null @@ -1,35 +0,0 @@ -
    - {{#each damage}} -

    {{localize (concat 'DAGGERHEART.CONFIG.HealingType.' @key '.name')}}

    - {{#each this}} -
    - {{#each this}} -
    - - - - {{this.selectedResults}}/{{this.results.length}} Selected - - -
    - {{#each this.results}} -
    - {{this.result}} - {{#if this.active}} - - - - - {{/if}} -
    - {{/each}} -
    -
    - {{/each}} -
    - {{/each}} - {{/each}} -
    \ No newline at end of file From 9487b07e434ba1449b7ebab9998fc32313b4985a Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 06:47:06 -0400 Subject: [PATCH 49/73] Fix tier adjustment on actions that use standard attack damage (#1942) --- .gitattributes | 2 + module/data/actor/adversary.mjs | 204 +------------------------ module/data/actor/tierAdjustment.mjs | 218 +++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 202 deletions(-) create mode 100644 .gitattributes create mode 100644 module/data/actor/tierAdjustment.mjs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..56ce8818 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=lf +*.json text eol=lf diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index d69e17ad..d6d0dcdf 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -3,8 +3,7 @@ import { ActionField } from '../fields/actionField.mjs'; import { commonActorRules } from './base.mjs'; import DhCreature from './creature.mjs'; import { bonusField } from '../fields/actorField.mjs'; -import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs'; -import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; +import { getTierAdjustedAdversary } from './tierAdjustment.mjs'; export default class DhpAdversary extends DhCreature { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary']; @@ -206,205 +205,6 @@ export default class DhpAdversary extends DhCreature { /** Returns source data for this actor adjusted to a new tier, which can be used to create a new actor. */ adjustForTier(tier) { const source = this.parent.toObject(true); - - /** @type {(2 | 3 | 4)[]} */ - const tiers = new Array(Math.abs(tier - this.tier)) - .fill(0) - .map((_, idx) => idx + Math.min(tier, this.tier) + 1); - if (tier < this.tier) tiers.reverse(); - const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard]; - const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] })); - - // Apply simple tier changes - const scale = tier > this.tier ? 1 : -1; - for (const entry of tierEntries) { - source.system.difficulty += scale * entry.difficulty; - source.system.damageThresholds.major += scale * entry.majorThreshold; - source.system.damageThresholds.severe += scale * entry.severeThreshold; - source.system.resources.hitPoints.max += scale * entry.hp; - source.system.resources.stress.max += scale * entry.stress; - source.system.attack.roll.bonus += scale * entry.attack; - } - - // Get the mean and standard deviation of expected damage in the previous and new tier - // The data we have is for attack scaling, but we reuse this for action scaling later - const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic; - const damageMeta = { - currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] }, - newDamageRange: { tier, ...expectedDamageData[tier] }, - type: 'attack' - }; - - // Update damage of base attack - try { - this.#adjustActionDamage(source.system.attack, damageMeta); - } catch (err) { - ui.notifications.warn('Failed to convert attack damage of adversary'); - console.error(err); - } - - // Update damage of each item action, making sure to also update the description if possible - const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g; - for (const item of source.items) { - // Replace damage inlines with new formulas - for (const withDescription of [item.system, ...Object.values(item.system.actions)]) { - withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => { - const { value: formula } = parseInlineParams(inner); - if (!formula || !type) return match; - - try { - const adjusted = this.#calculateAdjustedDamage(formula, { ...damageMeta, type: 'action' }); - const newFormula = [ - adjusted.diceQuantity ? `${adjusted.diceQuantity}d${adjusted.faces}` : null, - adjusted.bonus - ] - .filter(p => !!p) - .join('+'); - return match.replace(formula, newFormula); - } catch { - return match; - } - }); - } - - // Update damage in item actions - // Parse damage, and convert all formula matches in the descriptions to the new damage - for (const action of Object.values(item.system.actions)) { - try { - const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' }); - if (!result) continue; - - for (const { previousFormula, formula } of Object.values(result)) { - const oldFormulaRegexp = new RegExp( - previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?') - ); - item.system.description = item.system.description.replace(oldFormulaRegexp, formula); - action.description = action.description.replace(oldFormulaRegexp, formula); - } - } catch (err) { - ui.notifications.warn(`Failed to convert action damage for item ${item.name}`); - console.error(err); - } - } - } - - // Finally set the tier of the source data, now that everything is complete - source.system.tier = tier; - return source; - } - - /** - * Converts a damage object to a new damage range - * @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term - * @throws error if the formula is the wrong type - */ - #calculateAdjustedDamage(formula, { currentDamageRange, newDamageRange, type }) { - const terms = parseTermsFromSimpleFormula(formula); - const flatTerms = terms.filter(t => t.diceQuantity === 0); - const diceTerms = terms.filter(t => t.diceQuantity > 0); - if (flatTerms.length > 1 || diceTerms.length > 1) { - throw new Error('invalid formula for conversion'); - } - const value = { - ...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }), - bonus: flatTerms[0]?.bonus ?? 0 - }; - const previousExpected = calculateExpectedValue(value); - if (previousExpected === 0) return value; // nothing to do - - const dieSizes = [4, 6, 8, 10, 12, 20]; - const steps = newDamageRange.tier - currentDamageRange.tier; - const increasing = steps > 0; - const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation; - const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation); - - // If this was just a flat number, convert to the expected damage and exit - if (value.diceQuantity === 0) { - value.bonus = Math.round(expected); - return value; - } - - const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1; - const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 }); - - // Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die - const baseOverages = Math.floor(value.bonus / getExpectedDie()); - - // Prestep. Change number of dice for attacks, bump up/down for actions - // We never bump up to d20, though we might bump down from it - if (type === 'attack') { - const minimum = increasing ? value.diceQuantity : 0; - value.diceQuantity = Math.max(minimum, newDamageRange.tier); - } else { - const currentIdx = dieSizes.indexOf(value.faces); - value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)]; - } - - value.bonus = Math.round(expected - getBaseAverage()); - - // Attempt to handle negative values. - // If we can do it with only step downs, do so. Otherwise remove tier dice, and try again - if (value.bonus < 0) { - let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity); - const currentIdx = dieSizes.indexOf(value.faces); - - // If step downs alone don't suffice, change the flat modifier, then calculate steps required again - // If this isn't sufficient, the result will be slightly off. This is unlikely to happen - if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) { - value.diceQuantity -= increasing ? 1 : Math.abs(steps); - value.bonus = Math.round(expected - getBaseAverage()); - if (value.bonus >= 0) return value; // complete - } - - stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity); - value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)]; - value.bonus = Math.max(0, Math.round(expected - getBaseAverage())); - } - - // If value is really high, we add a number of dice based on the number of overages - // This attempts to preserve a similar amount of variance when increasing an action - const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages; - if (type !== 'attack' && increasing && overagesToRemove > 0) { - value.diceQuantity += overagesToRemove; - value.bonus = Math.round(expected - getBaseAverage()); - } - - return value; - } - - /** - * Updates damage to reflect a specific value. - * @throws if damage structure is invalid for conversion - * @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage - */ - #adjustActionDamage(action, damageMeta) { - if (!action.damage?.parts.hitPoints) return null; - - const result = {}; - for (const property of ['value', 'valueAlt']) { - const data = action.damage.parts.hitPoints[property]; - const previousFormula = data.custom.enabled - ? data.custom.formula - : [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0] - .filter(p => !!p) - .join('+'); - const value = this.#calculateAdjustedDamage(previousFormula, damageMeta); - const formula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus] - .filter(p => !!p) - .join('+'); - if (value.diceQuantity) { - data.custom.enabled = false; - data.bonus = value.bonus; - data.dice = `d${value.faces}`; - data.flatMultiplier = value.diceQuantity; - } else if (!value.diceQuantity) { - data.custom.enabled = true; - data.custom.formula = formula; - } - - result[property] = { previousFormula, formula, value }; - } - - return result; + return getTierAdjustedAdversary(source, tier); } } diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs new file mode 100644 index 00000000..785eec2b --- /dev/null +++ b/module/data/actor/tierAdjustment.mjs @@ -0,0 +1,218 @@ +import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs'; +import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; + +export function getTierAdjustedAdversary(source, tier) { + const currentTier = source.tier ?? 1; + + /** @type {(2 | 3 | 4)[]} */ + const tiers = new Array(Math.abs(tier - currentTier)) + .fill(0) + .map((_, idx) => idx + Math.min(tier, currentTier) + 1); + if (tier < currentTier) tiers.reverse(); + const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard]; + const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] })); + + // Apply simple tier changes + const scale = tier > currentTier ? 1 : -1; + for (const entry of tierEntries) { + source.system.difficulty += scale * entry.difficulty; + source.system.damageThresholds.major += scale * entry.majorThreshold; + source.system.damageThresholds.severe += scale * entry.severeThreshold; + source.system.resources.hitPoints.max += scale * entry.hp; + source.system.resources.stress.max += scale * entry.stress; + source.system.attack.roll.bonus += scale * entry.attack; + } + + // Get the mean and standard deviation of expected damage in the previous and new tier + // The data we have is for attack scaling, but we reuse this for action scaling later + const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic; + const damageMeta = { + currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] }, + newDamageRange: { tier, ...expectedDamageData[tier] } + }; + + // Store initial attack damage for abilities that have you deal a "standard attack" + const initialAttack = { + type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(), + value: getDamagePartsFormula(source.system.attack.damage?.parts.hitPoints?.value) + }; + + // Update damage of base attack. + try { + const damage = source.system.attack.damage; + if (!damage?.parts.hitPoints) throw new Error('Unexpected missing attack in adversary'); + + for (const property of ['value', 'valueAlt']) { + const data = damage.parts.hitPoints[property]; + const previousFormula = getDamagePartsFormula(data); + const { value, formula } = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); + applyAdjustedDamage(data, value, formula); + } + } catch (err) { + ui.notifications.warn('Failed to convert attack damage of adversary'); + console.error(err); + } + + // Update damage of each item action, making sure to also update the description if possible + const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g; + for (const item of source.items) { + // Replace damage inlines with new formulas. Keep a record for a specific check later + const descriptionFormulas = []; + for (const withDescription of [item.system, ...Object.values(item.system.actions)]) { + withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => { + const { value: formula } = parseInlineParams(inner); + if (!formula || !type) return match; + + try { + const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula; + descriptionFormulas.push(formula); + return match.replace(formula, newFormula); + } catch { + return match; + } + }); + } + + // Update damage in item actions and convert all formula matches in the descriptions to the new damage + for (const action of Object.values(item.system.actions)) { + if (!action.damage?.parts.hitPoints) continue; + try { + // Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead + const result = []; + for (const property of ['value', 'valueAlt']) { + const { [property]: data, type: damageType } = action.damage.parts.hitPoints; + const previousFormula = getDamagePartsFormula(data); + const isActuallyAttack = + previousFormula === initialAttack.value && + foundry.utils.equals(damageType.toSorted(), initialAttack.type) && + !descriptionFormulas.includes(previousFormula); + const type = isActuallyAttack ? 'attack' : 'action'; + const { value, formula } = calculateAdjustedDamage(previousFormula, type, damageMeta); + applyAdjustedDamage(data, value, formula); + result.push({ previousFormula, formula }); + } + + // Override text in the description with those values + for (const { previousFormula, formula } of Object.values(result)) { + const oldFormulaRegexp = new RegExp( + previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?') + ); + item.system.description = item.system.description.replace(oldFormulaRegexp, formula); + action.description = action.description.replace(oldFormulaRegexp, formula); + } + } catch (err) { + ui.notifications.warn(`Failed to convert action damage for item ${item.name}`); + console.error(err); + } + } + } + + // Finally set the tier of the source data, now that everything is complete + source.system.tier = tier; + return source; +} + +/** + * Converts a damage object to a new damage range + * @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term + * @throws error if the formula is the wrong type + */ +function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageRange }) { + const terms = parseTermsFromSimpleFormula(formula); + const flatTerms = terms.filter(t => t.diceQuantity === 0); + const diceTerms = terms.filter(t => t.diceQuantity > 0); + if (flatTerms.length > 1 || diceTerms.length > 1) { + throw new Error('invalid formula for conversion'); + } + const value = { + ...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }), + bonus: flatTerms[0]?.bonus ?? 0 + }; + const previousExpected = calculateExpectedValue(value); + if (previousExpected === 0) return value; // nothing to do + + const dieSizes = [4, 6, 8, 10, 12, 20]; + const steps = newDamageRange.tier - currentDamageRange.tier; + const increasing = steps > 0; + const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation; + const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation); + + // If this was just a flat number, convert to the expected damage and exit + if (value.diceQuantity === 0) { + value.bonus = Math.round(expected); + return value; + } + + const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1; + const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 }); + + // Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die + const baseOverages = Math.floor(value.bonus / getExpectedDie()); + + // Prestep. Change number of dice for attacks, bump up/down for actions + // We never bump up to d20, though we might bump down from it + if (type === 'attack') { + const minimum = increasing ? value.diceQuantity : 0; + value.diceQuantity = Math.max(minimum, newDamageRange.tier); + } else { + const currentIdx = dieSizes.indexOf(value.faces); + value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)]; + } + + value.bonus = Math.round(expected - getBaseAverage()); + + // Attempt to handle negative values. + // If we can do it with only step downs, do so. Otherwise remove tier dice, and try again + if (value.bonus < 0) { + let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity); + const currentIdx = dieSizes.indexOf(value.faces); + + // If step downs alone don't suffice, change the flat modifier, then calculate steps required again + // If this isn't sufficient, the result will be slightly off. This is unlikely to happen + if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) { + value.diceQuantity -= increasing ? 1 : Math.abs(steps); + value.bonus = Math.round(expected - getBaseAverage()); + if (value.bonus >= 0) return value; // complete + } + + stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity); + value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)]; + value.bonus = Math.max(0, Math.round(expected - getBaseAverage())); + } + + // If value is really high, we add a number of dice based on the number of overages + // This attempts to preserve a similar amount of variance when increasing an action + const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages; + if (type !== 'attack' && increasing && overagesToRemove > 0) { + value.diceQuantity += overagesToRemove; + value.bonus = Math.round(expected - getBaseAverage()); + } + + const newFormula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus] + .filter(p => !!p) + .join('+'); + return { value, formula: newFormula }; +} + +function getDamagePartsFormula(data) { + return data.custom.enabled + ? data.custom.formula + : [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0].filter(p => !!p).join('+'); +} + +/** + * Updates damage to reflect a specific value. + * @throws if damage structure is invalid for conversion + * @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage + */ +function applyAdjustedDamage(diceData, value, formula) { + if (value.diceQuantity) { + diceData.custom.enabled = false; + diceData.bonus = value.bonus; + diceData.dice = `d${value.faces}`; + diceData.flatMultiplier = value.diceQuantity; + } else if (!value.diceQuantity) { + diceData.custom.enabled = true; + diceData.custom.formula = formula; + } +} From a209b035c8d863ed8788cb82278e096248d12824 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 06:48:20 -0400 Subject: [PATCH 50/73] Make prosemirror button nicer (#1946) --- styles/less/global/prose-mirror.less | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index 8412235d..e4b1249f 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -40,6 +40,11 @@ ul { list-style: disc; } + } + // Fixes centering and makes it not render over scrollbar + &:hover button.toggle:enabled { + display: flex; + right: 12px; } } } From 251d7e4e13cd172245f577b0adac9de633dd8013 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 06:49:06 -0400 Subject: [PATCH 51/73] Swap order of thresholds and resources in actor editor (#1943) --- .../sheets-settings/adversary-settings/sheet.less | 2 +- .../sheets-settings/adversary-settings/details.hbs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/styles/less/sheets-settings/adversary-settings/sheet.less b/styles/less/sheets-settings/adversary-settings/sheet.less index b4b0683b..e6eb8d0b 100644 --- a/styles/less/sheets-settings/adversary-settings/sheet.less +++ b/styles/less/sheets-settings/adversary-settings/sheet.less @@ -7,7 +7,7 @@ &.attack.active { display: flex; flex-direction: column; - gap: 16px; + gap: 12px; } .fieldsets-section { diff --git a/templates/sheets-settings/adversary-settings/details.hbs b/templates/sheets-settings/adversary-settings/details.hbs index dc2fd386..3160fbb9 100644 --- a/templates/sheets-settings/adversary-settings/details.hbs +++ b/templates/sheets-settings/adversary-settings/details.hbs @@ -18,6 +18,12 @@ {{formField systemFields.motivesAndTactics value=document._source.system.motivesAndTactics label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.motivesAndTactics.label")}}
    +
    + {{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}} + {{formGroup systemFields.damageThresholds.fields.major value=document._source.system.damageThresholds.major label=(localize "DAGGERHEART.GENERAL.DamageThresholds.majorThreshold")}} + {{formGroup systemFields.damageThresholds.fields.severe value=document._source.system.damageThresholds.severe label=(localize "DAGGERHEART.GENERAL.DamageThresholds.severeThreshold")}} +
    +
    {{localize "DAGGERHEART.GENERAL.Resource.plural"}} @@ -26,10 +32,4 @@ {{/each}}
    - -
    - {{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}} - {{formGroup systemFields.damageThresholds.fields.major value=document._source.system.damageThresholds.major label=(localize "DAGGERHEART.GENERAL.DamageThresholds.majorThreshold")}} - {{formGroup systemFields.damageThresholds.fields.severe value=document._source.system.damageThresholds.severe label=(localize "DAGGERHEART.GENERAL.DamageThresholds.severeThreshold")}} -
    From 493998cc957a1c5f0a5153c28138aa8ff4db1a22 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 06:51:39 -0400 Subject: [PATCH 52/73] Preload class and subclass features for description (#1940) Co-authored-by: WBHarry --- module/data/item/class.mjs | 13 ++++++++---- module/data/item/subclass.mjs | 8 +++++-- module/helpers/utils.mjs | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 7014e011..470a1e3c 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import ItemLinkFields from '../fields/itemLinkFields.mjs'; -import { addLinkedItemsDiff, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs'; +import { addLinkedItemsDiff, fromUuids, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs'; export default class DHClass extends BaseDataItem { /** @inheritDoc */ @@ -73,15 +73,16 @@ export default class DHClass extends BaseDataItem { const uuids = [this.parent.uuid, this.parent._stats?.compendiumSource].filter(u => !!u); const subclasses = game.items.filter(x => x.type === 'subclass' && uuids.includes(x.system.linkedClass)); for (const pack of game.packs) { + const packIds = []; const indexes = await pack.getIndex({ fields: ['system.linkedClass'] }); for (const index of indexes) { if (index.type !== 'subclass') continue; if (!uuids.includes(index.system?.linkedClass)) continue; if (subclasses.find(x => x.uuid === index.uuid)) continue; - - const subclass = await foundry.utils.fromUuid(index.uuid); - subclasses.push(subclass); + packIds.push(index._id); } + + if (packIds.length > 0) subclasses.push(...(await pack.getDocuments({ _id__in: packIds }))); } return subclasses; @@ -216,6 +217,10 @@ export default class DHClass extends BaseDataItem { classItems.push(contentLink.outerHTML); } + // Preload all class features for acquisition from the cache + // todo: make feature acquisition async and replace feature helpers for methods + await fromUuids(this._source.features.map(f => f.item)); + const hopeFeatures = await getFeaturesHTMLData(this.hopeFeatures); const classFeatures = await getFeaturesHTMLData(this.classFeatures); diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index ecf72de3..55b078c2 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -1,5 +1,4 @@ -import { getFeaturesHTMLData } from '../../helpers/utils.mjs'; -import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs'; import ItemLinkFields from '../fields/itemLinkFields.mjs'; import BaseDataItem from './base.mjs'; @@ -91,6 +90,11 @@ export default class DHSubclass extends BaseDataItem { const spellcastTrait = this.spellcastingTrait ? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label) : null; + + // Preload all class features for acquisition from the cache + // todo: make feature acquisition async and replace feature helpers for methods + await fromUuids(this._source.features.map(f => f.item)); + const foundationFeatures = await getFeaturesHTMLData(this.foundationFeatures); const specializationFeatures = await getFeaturesHTMLData(this.specializationFeatures); const masteryFeatures = await getFeaturesHTMLData(this.masteryFeatures); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 8bc95aa0..2f20175b 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -865,6 +865,45 @@ export function camelize(str) { .replace(/\s+/g, ''); } +/** Bulk load a list of documents using uuids. Returns the documents in the same order */ +export async function fromUuids(uuids) { + // Set up base entries. Each step works on a sublist of these objects + const entries = uuids.map(uuid => ({ + uuid, + parsed: foundry.utils.parseUuid(uuid), + value: foundry.utils.fromUuidSync(uuid) + })); + + // Handle missing uuids for embedded documents first + // A value may be index data, so we check if its a document + const packEmbeddedEntries = entries.filter( + e => + !(e.value instanceof Document) && + e.parsed.collection instanceof foundry.documents.collections.CompendiumCollection && + e.parsed.embedded.length > 0 + ); + await Promise.all( + packEmbeddedEntries.map(async e => { + e.value = await fromUuid(e.uuid); + return true; + }) + ); + + // Handle missing top level pack stuff, by batching per pack + const missingTopLevel = entries.filter(e => !(e.value instanceof Document) && e.value?.pack); + for (const packGroup of Object.values(Object.groupBy(missingTopLevel, e => e.value.pack))) { + const pack = game.packs.get(packGroup[0].value.pack); + if (!pack) continue; + + const ids = packGroup.map(p => p.parsed.id); + const documents = await pack.getDocuments({ _id__in: ids }); + for (const p of packGroup) { + p.value = documents.find(d => d.id === p.parsed.id) ?? p.value; + } + } + + return entries.map(e => e.value); +} /** * Triggers DiceSoNice rolls or dice roll audio for rolls. Not used for duality rolls. * @param { Roll[] } rolls From 2bc1c04c932d91f9d66bc65321813a3756270c23 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 30 May 2026 12:56:42 +0200 Subject: [PATCH 53/73] Fixed an issue where hope/fear dice size could no longer be changed in the roll dialog --- module/dice/dualityRoll.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index f40e9781..1d2d556a 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -135,11 +135,11 @@ export default class DualityRoll extends D20Roll { this.terms = [this.terms[0], this.terms[1], this.terms[2]]; this.terms[0] = new game.system.api.dice.diceTypes.HopeDie({ - faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12 + faces: this.terms[0]?.faces ?? this.data.rules.dualityRoll?.defaultHopeDice ?? 12 }); this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' }); this.terms[2] = new game.system.api.dice.diceTypes.FearDie({ - faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12 + faces: this.terms[2]?.faces ?? this.data.rules.dualityRoll?.defaultFearDice ?? 12 }); } From 61db7ca37151fb95feec4df406c19a2b8f2aedf2 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 19:00:12 -0400 Subject: [PATCH 54/73] Fix tag team roll results where one of them has stress (#1948) --- .../applications/dialogs/groupRollDialog.mjs | 14 +- module/applications/dialogs/tagTeamDialog.mjs | 173 +++++++++--------- 2 files changed, 95 insertions(+), 92 deletions(-) diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index 52baf537..dd504b4b 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -106,7 +106,12 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat const context = await super._prepareContext(_options); context.isGM = game.user.isGM; - context.isEditable = this.getIsEditable(); + context.isEditable = + game.user.isGM || + this.party.system.partyMembers.some(actor => { + const selected = Boolean(this.party.system.groupRoll.participants[actor.id]); + return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); + }); context.fields = this.party.system.schema.fields.groupRoll.fields; context.data = this.party.system.groupRoll; context.traitOptions = CONFIG.DH.ACTOR.abilities; @@ -265,13 +270,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat ]; } - getIsEditable() { - return this.party.system.partyMembers.some(actor => { - const selected = Boolean(this.party.system.groupRoll.participants[actor.id]); - return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); - }); - } - groupRollRefresh = ({ refreshType, action, parts }) => { if (refreshType !== RefreshType.GroupRoll) return; diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 4e63d93b..3dc6b0fc 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -116,7 +116,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.isEditable = this.getIsEditable(); + context.isEditable = + game.user.isGM || + this.party.system.partyMembers.some(actor => { + const selected = Boolean(this.party.system.tagTeam.members[actor.id]); + return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); + }); context.fields = this.party.system.schema.fields.tagTeam.fields; context.data = this.party.system.tagTeam; context.rollTypes = CONFIG.DH.GENERAL.tagTeamRollTypes; @@ -179,57 +184,56 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } if (Object.keys(this.party.system.tagTeam.members).includes(partId)) { - const data = this.party.system.tagTeam.members[partId]; - const actor = game.actors.get(partId); - - const rollOptions = []; - const damageRollOptions = []; - for (const item of actor.items) { - if (item.system.metadata.hasActions) { - const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])]; - for (const action of actions) { - if (action.hasRoll) { - const actionItem = { - value: action.uuid, - label: action.name, - group: item.name, - baseAction: action.baseAction - }; - - if (action.hasDamage) damageRollOptions.push(actionItem); - else rollOptions.push(actionItem); - } - } - } - } - - const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); - const critSelected = !selectedRoll - ? undefined - : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false); - - const damage = data.rollData?.options?.damage; - partContext.hasDamage |= Boolean(damage); - const critHitPointsDamage = await this.getCriticalDamage(damage); - - partContext.members[partId] = { - ...data, - roll: data.roll, - isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER), - key: partId, - readyToRoll: Boolean(data.rollChoice), - hasRolled: Boolean(data.rollData), - rollOptions, - damageRollOptions, - damage: damage, - critDamage: critHitPointsDamage, - useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical) - }; + const data = await this.#prepareMemberContext(partId); + partContext.hasDamage |= Boolean(data?.damage); + partContext.members[partId] = data; } return partContext; } + async #prepareMemberContext(partId) { + const data = this.party.system.tagTeam.members[partId] ?? {}; + const actor = game.actors.get(partId); + if (!actor) console.error(`Failed to get actor ${partId}`); + + const rollOptions = []; + const damageRollOptions = []; + for (const item of actor?.items ?? []) { + if (!item.system.metadata.hasActions) continue; + const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])]; + for (const action of actions) { + if (action.hasRoll) { + const collection = action.hasDamage ? damageRollOptions : rollOptions; + collection.push({ + value: action.uuid, + label: action.name, + group: item.name, + baseAction: action.baseAction + }); + } + } + } + + const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); + const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false); + const damage = data.rollData?.options?.damage; + + return { + ...data, + roll: data.roll, + isEditable: actor?.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER), + key: partId, + readyToRoll: Boolean(data.rollChoice), + hasRolled: Boolean(data.rollData), + rollOptions, + damageRollOptions, + damage: damage, + critDamage: await this.getCriticalDamage(damage), + useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical) + }; + } + getUpdatingParts(target) { const { initialization, rollSelection, result } = this.constructor.PARTS; const isInitialization = this.tabGroups.application === initialization.id; @@ -273,13 +277,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ); } - getIsEditable() { - return this.party.system.partyMembers.some(actor => { - const selected = Boolean(this.party.system.tagTeam.members[actor.id]); - return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); - }); - } - tagTeamRefresh = ({ refreshType, action, parts }) => { if (refreshType !== RefreshType.TagTeamRoll) return; @@ -649,42 +646,50 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } async getJoinedRoll({ overrideIsCritical, displayVersion } = {}) { - const memberValues = Object.values(this.party.system.tagTeam.members); - const selectedRoll = memberValues.find(x => x.selected); - let baseMainRoll = selectedRoll ?? memberValues[0]; - let baseSecondaryRoll = selectedRoll - ? memberValues.find(x => !x.selected) - : memberValues.length > 1 - ? memberValues[1] - : null; + try { + const memberValues = Object.values(this.party.system.tagTeam.members); + const selectedRoll = memberValues.find(x => x.selected); + const baseMainRoll = selectedRoll ?? memberValues[0]; + const baseSecondaryRoll = selectedRoll + ? memberValues.find(x => !x.selected) + : memberValues.length > 1 + ? memberValues[1] + : null; - if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null; + if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null; - const mainRoll = new MemberData(baseMainRoll.toObject()); - const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData; - const systemData = mainRoll.rollData.options; - const isCritical = overrideIsCritical ?? systemData.roll.isCritical; - if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage); + const mainRoll = new MemberData(baseMainRoll.toObject()); + const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData; + const systemData = mainRoll.rollData.options; + const isCritical = overrideIsCritical ?? systemData.roll.isCritical; + if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage); - if (secondaryRollData?.options.hasDamage) { - const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical) - ? await this.getCriticalDamage(secondaryRollData.options.damage) - : secondaryRollData.options.damage; - if (systemData.damage) { - for (const key in secondaryDamage) { - const damage = secondaryDamage[key]; - systemData.damage[key].formula = [systemData.damage[key].formula, damage.formula] - .filter(x => x) - .join(' + '); - systemData.damage[key].total += damage.total; - systemData.damage[key].parts.push(...damage.parts); + if (secondaryRollData?.options.hasDamage) { + const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical) + ? await this.getCriticalDamage(secondaryRollData.options.damage) + : secondaryRollData.options.damage; + if (systemData.damage) { + for (const [key, damage] of Object.entries(secondaryDamage ?? {})) { + if (key in systemData.damage) { + systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula] + .filter(x => x) + .join(' + '); + systemData.damage[key].total += damage.total; + systemData.damage[key].parts.push(...damage.parts); + } else { + systemData.damage[key] = damage; + } + } + } else { + systemData.damage = secondaryDamage; } - } else { - systemData.damage = secondaryDamage; } - } - return mainRoll; + return mainRoll; + } catch (err) { + console.error(err); + return null; + } } static async #onCancelRoll(_event, _button, options = { confirm: true }) { From d3141059acfef63db29b7f22060977a5f362a551 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 19:02:51 -0400 Subject: [PATCH 55/73] Create index files for actor sheet styles (#1945) --- .../adversary/{actions.less => features.less} | 0 .../less/sheets/actors/adversary/index.less | 5 +++ .../less/sheets/actors/character/index.less | 8 +++++ .../less/sheets/actors/companion/index.less | 4 +++ .../{actions.less => features.less} | 0 .../less/sheets/actors/environment/index.less | 4 +++ styles/less/sheets/actors/party/index.less | 4 +++ styles/less/sheets/index.less | 34 +++---------------- 8 files changed, 30 insertions(+), 29 deletions(-) rename styles/less/sheets/actors/adversary/{actions.less => features.less} (100%) create mode 100644 styles/less/sheets/actors/adversary/index.less create mode 100644 styles/less/sheets/actors/character/index.less create mode 100644 styles/less/sheets/actors/companion/index.less rename styles/less/sheets/actors/environment/{actions.less => features.less} (100%) create mode 100644 styles/less/sheets/actors/environment/index.less create mode 100644 styles/less/sheets/actors/party/index.less diff --git a/styles/less/sheets/actors/adversary/actions.less b/styles/less/sheets/actors/adversary/features.less similarity index 100% rename from styles/less/sheets/actors/adversary/actions.less rename to styles/less/sheets/actors/adversary/features.less diff --git a/styles/less/sheets/actors/adversary/index.less b/styles/less/sheets/actors/adversary/index.less new file mode 100644 index 00000000..a1ab41c7 --- /dev/null +++ b/styles/less/sheets/actors/adversary/index.less @@ -0,0 +1,5 @@ +@import './features.less'; +@import './header.less'; +@import './sheet.less'; +@import './sidebar.less'; +@import './effects.less'; diff --git a/styles/less/sheets/actors/character/index.less b/styles/less/sheets/actors/character/index.less new file mode 100644 index 00000000..edefe0a1 --- /dev/null +++ b/styles/less/sheets/actors/character/index.less @@ -0,0 +1,8 @@ +@import './biography.less'; +@import './effects.less'; +@import './features.less'; +@import './header.less'; +@import './inventory.less'; +@import './loadout.less'; +@import './sheet.less'; +@import './sidebar.less'; diff --git a/styles/less/sheets/actors/companion/index.less b/styles/less/sheets/actors/companion/index.less new file mode 100644 index 00000000..c4931814 --- /dev/null +++ b/styles/less/sheets/actors/companion/index.less @@ -0,0 +1,4 @@ +@import './details.less'; +@import './header.less'; +@import './sheet.less'; +@import './effects.less'; diff --git a/styles/less/sheets/actors/environment/actions.less b/styles/less/sheets/actors/environment/features.less similarity index 100% rename from styles/less/sheets/actors/environment/actions.less rename to styles/less/sheets/actors/environment/features.less diff --git a/styles/less/sheets/actors/environment/index.less b/styles/less/sheets/actors/environment/index.less new file mode 100644 index 00000000..211c8e60 --- /dev/null +++ b/styles/less/sheets/actors/environment/index.less @@ -0,0 +1,4 @@ +@import './features.less'; +@import './header.less'; +@import './potentialAdversaries.less'; +@import './sheet.less'; diff --git a/styles/less/sheets/actors/party/index.less b/styles/less/sheets/actors/party/index.less new file mode 100644 index 00000000..56f7a457 --- /dev/null +++ b/styles/less/sheets/actors/party/index.less @@ -0,0 +1,4 @@ +@import './header.less'; +@import './party-members.less'; +@import './sheet.less'; +@import './inventory.less'; diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index 7d595614..ca1bc840 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -2,35 +2,11 @@ @import './actors/actor-sheet-shared.less'; -@import './actors/adversary/actions.less'; -@import './actors/adversary/header.less'; -@import './actors/adversary/sheet.less'; -@import './actors/adversary/sidebar.less'; -@import './actors/adversary/effects.less'; - -@import './actors/character/biography.less'; -@import './actors/character/effects.less'; -@import './actors/character/features.less'; -@import './actors/character/header.less'; -@import './actors/character/inventory.less'; -@import './actors/character/loadout.less'; -@import './actors/character/sheet.less'; -@import './actors/character/sidebar.less'; - -@import './actors/companion/details.less'; -@import './actors/companion/header.less'; -@import './actors/companion/sheet.less'; -@import './actors/companion/effects.less'; - -@import './actors/environment/actions.less'; -@import './actors/environment/header.less'; -@import './actors/environment/potentialAdversaries.less'; -@import './actors/environment/sheet.less'; - -@import './actors/party/header.less'; -@import './actors/party/party-members.less'; -@import './actors/party/sheet.less'; -@import './actors/party/inventory.less'; +@import './actors/adversary/index.less'; +@import './actors/character/index.less'; +@import './actors/companion/index.less'; +@import './actors/environment/index.less'; +@import './actors/party/index.less'; @import './items/beastform.less'; @import './items/class.less'; From c23ac61ee53994b268a48db1c9172bf821684e75 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 31 May 2026 03:05:13 +0200 Subject: [PATCH 56/73] Corrected the data path for showing the difficulty marker in roll chat messages (#1950) --- templates/ui/chat/parts/roll-part.hbs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index 14e3eaa6..cfee735f 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -12,13 +12,9 @@ {{/if}}
    - {{#if roll.difficulty}} - - {{!-- {{#if canViewSecret}} --}} - difficulty {{roll.difficulty}} - {{!-- {{else}} - {{localize (ifThen roll.success "DAGGERHEART.GENERAL.success" "DAGGERHEART.GENERAL.failure")}} - {{/if}} --}} + {{#if roll.options.roll.difficulty}} + + {{localize "DAGGERHEART.GENERAL.difficulty"}} {{roll.options.roll.difficulty}} {{/if}} From 53f15a7fdec1edb6de4a54c29029c33e0890e100 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 31 May 2026 03:11:43 +0200 Subject: [PATCH 57/73] [Feature] NPC Actors (#1949) --- assets/icons/documents/actors/drama-masks.svg | 1 + daggerheart.mjs | 5 + lang/en.json | 6 + .../applications/sheets-configs/_module.mjs | 1 + .../sheets-configs/npc-settings.mjs | 85 +++++++++++ module/applications/sheets/actors/_module.mjs | 1 + module/applications/sheets/actors/npc.mjs | 136 ++++++++++++++++++ module/data/actor/_module.mjs | 4 +- module/data/actor/npc.mjs | 43 ++++++ module/documents/actor.mjs | 8 ++ styles/less/global/tab-navigation.less | 7 +- .../less/sheets/actors/adversary/header.less | 8 +- .../less/sheets/actors/companion/header.less | 4 +- .../sheets/actors/environment/header.less | 4 +- styles/less/sheets/actors/npc/features.less | 18 +++ styles/less/sheets/actors/npc/header.less | 83 +++++++++++ styles/less/sheets/actors/npc/index.less | 3 + styles/less/sheets/actors/npc/sheet.less | 10 ++ styles/less/sheets/index.less | 1 + system.json | 5 +- .../sheets-settings/npc-settings/details.hbs | 13 ++ .../sheets-settings/npc-settings/features.hbs | 29 ++++ .../sheets-settings/npc-settings/header.hbs | 3 + templates/sheets/actors/adversary/header.hbs | 5 +- templates/sheets/actors/companion/header.hbs | 9 +- .../sheets/actors/environment/header.hbs | 9 +- templates/sheets/actors/npc/features.hbs | 14 ++ templates/sheets/actors/npc/header.hbs | 40 ++++++ templates/sheets/actors/npc/navigation.hbs | 7 + templates/sheets/actors/npc/notes.hbs | 11 ++ .../sheets/global/tabs/tab-navigation.hbs | 2 +- .../ui/sidebar/actor-document-partial.hbs | 2 + 32 files changed, 548 insertions(+), 29 deletions(-) create mode 100644 assets/icons/documents/actors/drama-masks.svg create mode 100644 module/applications/sheets-configs/npc-settings.mjs create mode 100644 module/applications/sheets/actors/npc.mjs create mode 100644 module/data/actor/npc.mjs create mode 100644 styles/less/sheets/actors/npc/features.less create mode 100644 styles/less/sheets/actors/npc/header.less create mode 100644 styles/less/sheets/actors/npc/index.less create mode 100644 styles/less/sheets/actors/npc/sheet.less create mode 100644 templates/sheets-settings/npc-settings/details.hbs create mode 100644 templates/sheets-settings/npc-settings/features.hbs create mode 100644 templates/sheets-settings/npc-settings/header.hbs create mode 100644 templates/sheets/actors/npc/features.hbs create mode 100644 templates/sheets/actors/npc/header.hbs create mode 100644 templates/sheets/actors/npc/navigation.hbs create mode 100644 templates/sheets/actors/npc/notes.hbs diff --git a/assets/icons/documents/actors/drama-masks.svg b/assets/icons/documents/actors/drama-masks.svg new file mode 100644 index 00000000..84307da0 --- /dev/null +++ b/assets/icons/documents/actors/drama-masks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/daggerheart.mjs b/daggerheart.mjs index 363430be..23977628 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -196,6 +196,11 @@ Hooks.once('init', () => { makeDefault: true, label: sheetLabel('TYPES.Actor.environment') }); + Actors.registerSheet(SYSTEM.id, applications.sheets.actors.NPC, { + types: ['npc'], + makeDefault: true, + label: sheetLabel('TYPES.Actor.npc') + }); Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, { types: ['party'], makeDefault: true, diff --git a/lang/en.json b/lang/en.json index f1841e09..9ce515d9 100755 --- a/lang/en.json +++ b/lang/en.json @@ -23,6 +23,7 @@ "companion": "Companion", "adversary": "Adversary", "environment": "Environment", + "npc": "NPC", "party": "Party" } }, @@ -333,6 +334,11 @@ }, "newAdversary": "New Adversary" }, + "NPC": { + "FIELDS": { + "motives": { "label": "Motives" } + } + }, "Party": { "Subtitle": { "character": "{community} {ancestry} | {subclass} {class}", diff --git a/module/applications/sheets-configs/_module.mjs b/module/applications/sheets-configs/_module.mjs index 4b83a042..9528a424 100644 --- a/module/applications/sheets-configs/_module.mjs +++ b/module/applications/sheets-configs/_module.mjs @@ -2,6 +2,7 @@ export { default as ActionConfig } from './action-config.mjs'; export { default as ActionSettingsConfig } from './action-settings-config.mjs'; export { default as CharacterSettings } from './character-settings.mjs'; export { default as AdversarySettings } from './adversary-settings.mjs'; +export { default as NPCSettings } from './npc-settings.mjs'; export { default as CompanionSettings } from './companion-settings.mjs'; export { default as SettingFeatureConfig } from './setting-feature-config.mjs'; export { default as EnvironmentSettings } from './environment-settings.mjs'; diff --git a/module/applications/sheets-configs/npc-settings.mjs b/module/applications/sheets-configs/npc-settings.mjs new file mode 100644 index 00000000..c187877c --- /dev/null +++ b/module/applications/sheets-configs/npc-settings.mjs @@ -0,0 +1,85 @@ +import DHBaseActorSettings from '../sheets/api/actor-setting.mjs'; + +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + +export default class DHNPCSettings extends DHBaseActorSettings { + /**@inheritdoc */ + static DEFAULT_OPTIONS = { + classes: ['npc-settings'], + position: { width: 455, height: 'auto' }, + actions: {}, + dragDrop: [ + { dragSelector: null, dropSelector: '.tab.features' }, + { dragSelector: '.feature-item', dropSelector: null } + ] + }; + + /**@override */ + static PARTS = { + header: { + id: 'header', + template: 'systems/daggerheart/templates/sheets-settings/npc-settings/header.hbs' + }, + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + details: { + id: 'details', + template: 'systems/daggerheart/templates/sheets-settings/npc-settings/details.hbs' + }, + features: { + id: 'features', + template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs' + } + }; + + /** @override */ + static TABS = { + primary: { + tabs: [{ id: 'details' }, { id: 'features' }], + initial: 'details', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' + } + }; + + async _prepareContext(options) { + const context = await super._prepareContext(options); + + const featureForms = ['passive', 'action', 'reaction']; + context.features = context.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); + + return context; + } + + /* -------------------------------------------- */ + + async _onDragStart(event) { + const featureItem = event.currentTarget.closest('.feature-item'); + + if (featureItem) { + const feature = this.actor.items.get(featureItem.id); + const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true }; + event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); + event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); + } + } + + async _onDrop(event) { + event.stopPropagation(); + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); + + const item = await fromUuid(data.uuid); + if (item?.type === 'feature') { + if (data.fromInternal && item.parent?.uuid === this.actor.uuid) { + return; + } + + const itemData = item.toObject(); + delete itemData._id; + + await this.actor.createEmbeddedDocuments('Item', [itemData]); + } + } +} diff --git a/module/applications/sheets/actors/_module.mjs b/module/applications/sheets/actors/_module.mjs index c4ea2d94..1a2bebfb 100644 --- a/module/applications/sheets/actors/_module.mjs +++ b/module/applications/sheets/actors/_module.mjs @@ -2,4 +2,5 @@ export { default as Adversary } from './adversary.mjs'; export { default as Character } from './character.mjs'; export { default as Companion } from './companion.mjs'; export { default as Environment } from './environment.mjs'; +export { default as NPC } from './npc.mjs'; export { default as Party } from './party.mjs'; diff --git a/module/applications/sheets/actors/npc.mjs b/module/applications/sheets/actors/npc.mjs new file mode 100644 index 00000000..8c9048c2 --- /dev/null +++ b/module/applications/sheets/actors/npc.mjs @@ -0,0 +1,136 @@ +import DHBaseActorSheet from '../api/base-actor.mjs'; + +export default class NPCSheet extends DHBaseActorSheet { + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + classes: ['npc'], + position: { width: 660, height: 600 }, + window: { resizable: true }, + actions: {}, + window: { + resizable: true, + controls: [ + { + icon: 'fa-solid fa-signature', + label: 'DAGGERHEART.UI.Tooltip.configureAttribution', + action: 'editAttribution' + } + ] + }, + dragDrop: [ + { + dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', + dropSelector: null + } + ] + }; + + static PARTS = { + header: { template: 'systems/daggerheart/templates/sheets/actors/npc/header.hbs' }, + tabs: { template: 'systems/daggerheart/templates/sheets/actors/npc/navigation.hbs' }, + features: { + template: 'systems/daggerheart/templates/sheets/actors/npc/features.hbs', + scrollable: ['.feature-section'] + }, + notes: { + template: 'systems/daggerheart/templates/sheets/actors/npc/notes.hbs' + } + }; + + /** @inheritdoc */ + static TABS = { + primary: { + tabs: [{ id: 'notes' }, { id: 'features' }], + initial: 'notes', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' + } + }; + + /** @inheritdoc */ + _prepareTabs(group) { + const result = super._prepareTabs(group); + if (group === 'primary') { + result.features.empty = this.document.system.features.length === 0; + } + return result; + } + + /** @inheritdoc */ + async _preparePartContext(partId, context, options) { + context = await super._preparePartContext(partId, context, options); + switch (partId) { + case 'header': + await this._prepareHeaderContext(context, options); + break; + case 'features': + await this._prepareFeaturesContext(context, options); + break; + case 'notes': + await this._prepareNotesContext(context, options); + break; + } + + return context; + } + + /** + * Prepare render context for the Header part. + * @param {ApplicationRenderContext} context + * @param {ApplicationRenderOptions} options + * @returns {Promise} + * @protected + */ + async _prepareHeaderContext(context, _options) { + const { system } = this.document; + const { TextEditor } = foundry.applications.ux; + + context.description = await TextEditor.implementation.enrichHTML(system.description, { + secrets: this.document.isOwner, + relativeTo: this.document + }); + } + + /** + * Prepare render context for the Features part. + * @param {ApplicationRenderContext} context + * @param {ApplicationRenderOptions} options + * @returns {Promise} + * @protected + */ + async _prepareFeaturesContext(context, _options) { + const featureForms = ['passive', 'action', 'reaction']; + context.features = this.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); + } + + /** + * Prepare render context for the Biography part. + * @param {ApplicationRenderContext} context + * @param {ApplicationRenderOptions} options + * @returns {Promise} + * @protected + */ + async _prepareNotesContext(context, _options) { + const { system } = this.document; + const { TextEditor } = foundry.applications.ux; + + const paths = { + notes: 'notes' + }; + + for (const [key, path] of Object.entries(paths)) { + const value = foundry.utils.getProperty(system, path); + context[key] = { + field: system.schema.getField(path), + value, + enriched: await TextEditor.implementation.enrichHTML(value, { + secrets: this.document.isOwner, + relativeTo: this.document + }) + }; + } + } +} diff --git a/module/data/actor/_module.mjs b/module/data/actor/_module.mjs index 99577620..1fe1ef3f 100644 --- a/module/data/actor/_module.mjs +++ b/module/data/actor/_module.mjs @@ -1,15 +1,17 @@ import DhCharacter from './character.mjs'; import DhCompanion from './companion.mjs'; import DhAdversary from './adversary.mjs'; +import DhNPC from './npc.mjs'; import DhEnvironment from './environment.mjs'; import DhParty from './party.mjs'; -export { DhCharacter, DhCompanion, DhAdversary, DhEnvironment, DhParty }; +export { DhCharacter, DhCompanion, DhAdversary, DhNPC, DhEnvironment, DhParty }; export const config = { character: DhCharacter, companion: DhCompanion, adversary: DhAdversary, + npc: DhNPC, environment: DhEnvironment, party: DhParty }; diff --git a/module/data/actor/npc.mjs b/module/data/actor/npc.mjs new file mode 100644 index 00000000..2ccaf926 --- /dev/null +++ b/module/data/actor/npc.mjs @@ -0,0 +1,43 @@ +import DHNPCSettings from '../../applications/sheets-configs/npc-settings.mjs'; +import BaseDataActor from './base.mjs'; + +export default class DhpNPC extends BaseDataActor { + static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.NPC']; + + static get metadata() { + return foundry.utils.mergeObject(super.metadata, { + label: 'TYPES.Actor.npc', + type: 'npc', + settingSheet: DHNPCSettings, + hasResistances: false, + hasAttribution: true + }); + } + + static defineSchema() { + const fields = foundry.data.fields; + return { + ...super.defineSchema(), + difficulty: new fields.NumberField({ + nullable: true, + initial: null, + integer: true, + label: 'DAGGERHEART.GENERAL.difficulty' + }), + description: new fields.HTMLField({ label: 'DAGGERHEART.GENERAL.description' }), + motives: new fields.StringField(), + notes: new fields.HTMLField() + }; + } + + /**@inheritdoc */ + static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/actors/drama-masks.svg'; + + get features() { + return this.parent.items.filter(x => x.type === 'feature'); + } + + isItemValid(source) { + return super.isItemValid(source) || source.type === 'feature'; + } +} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index e4c11a5c..6c462d98 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -109,6 +109,14 @@ export default class DhpActor extends Actor { }); } + if (this.type === 'npc') { + Object.assign(update, { + prototypeToken: { + disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY + } + }); + } + this.updateSource(update); } diff --git a/styles/less/global/tab-navigation.less b/styles/less/global/tab-navigation.less index 038a9749..3d143b4c 100755 --- a/styles/less/global/tab-navigation.less +++ b/styles/less/global/tab-navigation.less @@ -3,8 +3,7 @@ .daggerheart.dh-style { .tab-navigation { - margin: 5px 0; - height: 40px; + margin: 5px 0 10px 0; width: 100%; .navigation-container { @@ -21,6 +20,10 @@ a { color: @color-text-emphatic; + + &.empty:not(.active) { + opacity: 0.4; + } } } } diff --git a/styles/less/sheets/actors/adversary/header.less b/styles/less/sheets/actors/adversary/header.less index 8bd3fcee..1e5e4fa5 100644 --- a/styles/less/sheets/actors/adversary/header.less +++ b/styles/less/sheets/actors/adversary/header.less @@ -35,7 +35,7 @@ .tags { display: flex; gap: 10px; - padding-bottom: 16px; + padding-bottom: 8px; .tag { display: flex; @@ -67,11 +67,5 @@ gap: 12px; padding: 16px 0; } - - .adversary-navigation { - display: flex; - gap: 8px; - align-items: center; - } } } diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index b4df96bf..aca789a6 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -148,10 +148,8 @@ } .companion-navigation { - display: flex; - gap: 8px; - align-items: baseline; width: 100%; + padding: 0 10px; } } } diff --git a/styles/less/sheets/actors/environment/header.less b/styles/less/sheets/actors/environment/header.less index 85471af4..da6954e0 100644 --- a/styles/less/sheets/actors/environment/header.less +++ b/styles/less/sheets/actors/environment/header.less @@ -138,10 +138,8 @@ } .environment-navigation { - display: flex; - gap: 20px; - align-items: baseline; padding: 0 20px; + .tab-navigation { margin-top: 0; } diff --git a/styles/less/sheets/actors/npc/features.less b/styles/less/sheets/actors/npc/features.less new file mode 100644 index 00000000..107b5a06 --- /dev/null +++ b/styles/less/sheets/actors/npc/features.less @@ -0,0 +1,18 @@ +.application.sheet.daggerheart.actor.dh-style.npc { + .tab.features { + &.active { + overflow: hidden; + display: flex; + flex-direction: column; + } + + .feature-section { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + mask-image: linear-gradient(0deg, transparent 0%, black 5%); + padding-bottom: 20px; + } + } +} diff --git a/styles/less/sheets/actors/npc/header.less b/styles/less/sheets/actors/npc/header.less new file mode 100644 index 00000000..d49d763c --- /dev/null +++ b/styles/less/sheets/actors/npc/header.less @@ -0,0 +1,83 @@ +.application.sheet.daggerheart.actor.dh-style.npc { + .npc-header-sheet { + width: 100%; + display: flex; + + .portrait { + cursor: pointer; + width: 275px; + + img { + height: 275px; + } + } + + .tags { + display: flex; + gap: 10px; + padding-bottom: 8px; + + .tag { + display: flex; + flex-direction: row; + gap: 4px; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: var(--font-size-12); + font: @font-body; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: var(--font-size-12); + } + } + + .info-section { + flex: 1; + padding: 0 15px; + padding-top: var(--header-height); + display: flex; + flex-direction: column; + + .name-row { + display: flex; + gap: 5px; + align-items: center; + justify-content: space-between; + padding: 8px 0; + + h1 { + display: flex; + flex: 1; + padding: 6px 0 0 0; + font-size: var(--font-size-32); + text-align: start; + border: 1px solid transparent; + outline: 2px solid transparent; + transition: all 0.3s ease; + word-break: break-word; + + &:hover { + outline: 2px solid light-dark(@dark, @golden); + } + } + } + + .npc-info { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px 0; + } + } + } +} \ No newline at end of file diff --git a/styles/less/sheets/actors/npc/index.less b/styles/less/sheets/actors/npc/index.less new file mode 100644 index 00000000..2d7d54e3 --- /dev/null +++ b/styles/less/sheets/actors/npc/index.less @@ -0,0 +1,3 @@ +@import './sheet.less'; +@import './header.less'; +@import './features.less'; \ No newline at end of file diff --git a/styles/less/sheets/actors/npc/sheet.less b/styles/less/sheets/actors/npc/sheet.less new file mode 100644 index 00000000..8ba3b7a9 --- /dev/null +++ b/styles/less/sheets/actors/npc/sheet.less @@ -0,0 +1,10 @@ +.application.sheet.daggerheart.actor.dh-style.npc { + .window-content { + display: grid; + grid-template-rows: auto auto 1fr; + } + + .npc-navigation { + padding: 0 15px; + } +} \ No newline at end of file diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index ca1bc840..4312f755 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -6,6 +6,7 @@ @import './actors/character/index.less'; @import './actors/companion/index.less'; @import './actors/environment/index.less'; +@import './actors/npc/index.less'; @import './actors/party/index.less'; @import './items/beastform.less'; diff --git a/system.json b/system.json index 2acd7570..89320768 100644 --- a/system.json +++ b/system.json @@ -244,11 +244,14 @@ "adversary": { "htmlFields": ["notes", "description"] }, + "npc": { + "htmlFields": ["notes"] + }, "environment": { "htmlFields": ["notes", "description"] }, "party": { - "htmlFields": ["notes"] + "htmlFields": ["notes", "description"] } }, "Item": { diff --git a/templates/sheets-settings/npc-settings/details.hbs b/templates/sheets-settings/npc-settings/details.hbs new file mode 100644 index 00000000..0e18b488 --- /dev/null +++ b/templates/sheets-settings/npc-settings/details.hbs @@ -0,0 +1,13 @@ +
    +
    + {{localize "DAGGERHEART.GENERAL.description"}} + {{formInput systemFields.description value=document._source.system.description}} +
    + + {{formGroup systemFields.motives value=document._source.system.motives}} + {{formGroup systemFields.difficulty value=document._source.system.difficulty localize=true}} +
    diff --git a/templates/sheets-settings/npc-settings/features.hbs b/templates/sheets-settings/npc-settings/features.hbs new file mode 100644 index 00000000..2f2f5f47 --- /dev/null +++ b/templates/sheets-settings/npc-settings/features.hbs @@ -0,0 +1,29 @@ +
    + +
    + {{localize tabs.features.label}} +
      + {{#each @root.features as |feature|}} +
    • + +
      + {{feature.name}} +
      +
      + + +
      +
    • + {{/each}} +
    +
    + {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
    +
    +
    \ No newline at end of file diff --git a/templates/sheets-settings/npc-settings/header.hbs b/templates/sheets-settings/npc-settings/header.hbs new file mode 100644 index 00000000..c9cb60fe --- /dev/null +++ b/templates/sheets-settings/npc-settings/header.hbs @@ -0,0 +1,3 @@ +
    +

    {{document.name}}

    +
    \ No newline at end of file diff --git a/templates/sheets/actors/adversary/header.hbs b/templates/sheets/actors/adversary/header.hbs index fba96980..5adc235a 100644 --- a/templates/sheets/actors/adversary/header.hbs +++ b/templates/sheets/actors/adversary/header.hbs @@ -44,10 +44,9 @@
    -
    - {{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} + {{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} -
    + {{/ 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} \ No newline at end of file diff --git a/templates/sheets/actors/companion/header.hbs b/templates/sheets/actors/companion/header.hbs index d10c0640..9c324709 100644 --- a/templates/sheets/actors/companion/header.hbs +++ b/templates/sheets/actors/companion/header.hbs @@ -50,9 +50,10 @@
    - {{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} - + {{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} + + {{/ 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
    \ No newline at end of file diff --git a/templates/sheets/actors/environment/header.hbs b/templates/sheets/actors/environment/header.hbs index 2c6bbb5a..1b4073c7 100644 --- a/templates/sheets/actors/environment/header.hbs +++ b/templates/sheets/actors/environment/header.hbs @@ -44,9 +44,10 @@
    - {{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} - + {{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} + + {{/ 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
    \ No newline at end of file diff --git a/templates/sheets/actors/npc/features.hbs b/templates/sheets/actors/npc/features.hbs new file mode 100644 index 00000000..3b495e74 --- /dev/null +++ b/templates/sheets/actors/npc/features.hbs @@ -0,0 +1,14 @@ +
    +
    + {{> 'daggerheart.inventory-items' + title=tabs.features.label + type='feature' + collection=@root.features + hideContextMenu=true + hideModifyControls=true + canCreate=@root.editable + showActions=@root.editable + }} +
    +
    \ No newline at end of file diff --git a/templates/sheets/actors/npc/header.hbs b/templates/sheets/actors/npc/header.hbs new file mode 100644 index 00000000..8dc345dc --- /dev/null +++ b/templates/sheets/actors/npc/header.hbs @@ -0,0 +1,40 @@ +
    +
    + {{source.name}} +
    +
    + + +
    +

    {{source.name}}

    +
    + + {{#if source.system.difficulty}} +
    +
    + {{localize "DAGGERHEART.GENERAL.difficulty"}} + {{source.system.difficulty}} +
    +
    + {{/if}} + + + +
    + + {{{description}}} + +
    + {{localize 'DAGGERHEART.ACTORS.NPC.FIELDS.motives.label'}}: + {{source.system.motives}} +
    +
    +
    +
    \ No newline at end of file diff --git a/templates/sheets/actors/npc/navigation.hbs b/templates/sheets/actors/npc/navigation.hbs new file mode 100644 index 00000000..ae684f0d --- /dev/null +++ b/templates/sheets/actors/npc/navigation.hbs @@ -0,0 +1,7 @@ +
    + {{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} + + {{/'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} +
    \ No newline at end of file diff --git a/templates/sheets/actors/npc/notes.hbs b/templates/sheets/actors/npc/notes.hbs new file mode 100644 index 00000000..bc9ac3cf --- /dev/null +++ b/templates/sheets/actors/npc/notes.hbs @@ -0,0 +1,11 @@ +
    + {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} + + {{#if (and showAttribution document.system.attribution.artist)}} + + {{/if}} +
    \ No newline at end of file diff --git a/templates/sheets/global/tabs/tab-navigation.hbs b/templates/sheets/global/tabs/tab-navigation.hbs index f9a31d3e..8af1f140 100755 --- a/templates/sheets/global/tabs/tab-navigation.hbs +++ b/templates/sheets/global/tabs/tab-navigation.hbs @@ -4,7 +4,7 @@ {{/if}} + {{!-- Encounter Name --}} + {{#if combat.name}} +

    {{ combat.name }}

    + {{/if}} +
    {{!-- Combat Status --}} From 646ebc8bdf0fa6bcb18a2997c76802bff44a59a1 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:52:27 +0200 Subject: [PATCH 68/73] Added a temp fix for status effect rows (#1965) --- styles/less/hud/token-hud/token-hud.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/styles/less/hud/token-hud/token-hud.less b/styles/less/hud/token-hud/token-hud.less index 3cb94e1e..3b998f4e 100644 --- a/styles/less/hud/token-hud/token-hud.less +++ b/styles/less/hud/token-hud/token-hud.less @@ -38,6 +38,9 @@ } .status-effects { + // TODO: Remove when the issue https://github.com/foundryvtt/foundryvtt/issues/14410 is resolved and Foundry handles it cleanly themselves. + grid-template-rows: min-content; + .effect-control-container { position: relative; From df4a2c5d57c3592d3c15550c1f2fadc36bdba655 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:53:28 +0200 Subject: [PATCH 69/73] Fixed Countdown.migrationData assuming an array when it's actually an object (#1962) --- module/data/action/countdownAction.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/data/action/countdownAction.mjs b/module/data/action/countdownAction.mjs index abcc6b40..cb141637 100644 --- a/module/data/action/countdownAction.mjs +++ b/module/data/action/countdownAction.mjs @@ -36,7 +36,7 @@ export default class DhCountdownAction extends DHBaseAction { /** @inheritDoc */ static migrateData(source) { - for (const countdown of source.countdown) { + for (const countdown of Object.values(source.countdown)) { if (countdown.progress.max) { countdown.progress.startFormula = countdown.progress.max; countdown.progress.start = 1; From bcf274f1d05819cf9148ab597e558962e270a2e2 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:59:44 +0200 Subject: [PATCH 70/73] [Fix] ChatMessage Saves Pending Confirmation (#1963) --- lang/en.json | 6 +++--- module/documents/chatMessage.mjs | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lang/en.json b/lang/en.json index 9ce515d9..3a1340e0 100755 --- a/lang/en.json +++ b/lang/en.json @@ -711,9 +711,9 @@ }, "PendingReactionsDialog": { "title": "Pending Reaction Rolls Found", - "unfinishedRolls": "Some Tokens still need to roll their Reaction Roll.", - "confirmation": "Are you sure you want to continue ?", - "warning": "Undone reaction rolls will be considered as failed" + "unfinishedRolls": "Some Tokens have not finished their Reaction Rolls.", + "warning": "Unfinished reaction rolls will be considered as failed.", + "confirmation": "Are you sure you want to continue?" }, "ReactionRoll": { "title": "Reaction Roll: {trait}" diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 78bab016..893e6e5c 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -183,7 +183,11 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (pendingingSaves.length) { const confirm = await foundry.applications.api.DialogV2.confirm({ window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') }, - content: `

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}

    ` + content: ` +

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}

    +

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}

    +

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}

    + ` }); if (!confirm) return; } @@ -247,8 +251,24 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { const targets = this.filterPermTargets(this.system.hitTargets), config = foundry.utils.deepClone(this.system); config.event = event; + if (targets.length === 0) - ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); + return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); + else if (config.hasSave) { + const pendingingSaves = targets.filter(t => t.saved.success === null); + if (pendingingSaves.length) { + const confirm = await foundry.applications.api.DialogV2.confirm({ + window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') }, + content: ` +

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}

    +

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}

    +

    ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}

    + ` + }); + if (!confirm) return; + } + } + this.consumeOnSuccess(); this.system.action?.workflow.get('effects')?.execute(config, targets, true); } From 3c36c5747d477a831d6b0d5a653a414073f6f39c Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:02:42 +0200 Subject: [PATCH 71/73] [Fix] Base Attack Context Menu (#1961) * Fixed Adversary standard attack context menu * Fixed Character base attack context menu * Fixed Companion base attack context menu --- .../applications/sheets/actors/adversary.mjs | 10 +++++ .../applications/sheets/actors/character.mjs | 8 ++++ .../applications/sheets/actors/companion.mjs | 12 +++++- module/applications/sheets/api/base-actor.mjs | 37 +++++++++++++++++++ module/data/action/attackAction.mjs | 7 +++- 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 04be3efb..06dd4a0f 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -31,6 +31,16 @@ export default class AdversarySheet extends DHBaseActorSheet { dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', dropSelector: null } + ], + contextMenus: [ + { + handler: DHBaseActorSheet.getBaseAttackContextOptions, + selector: '[data-item-uuid][data-type="attack"]', + options: { + parentClassHooks: false, + fixed: true + } + } ] }; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 19b82712..e4d0e6d9 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -65,6 +65,14 @@ export default class CharacterSheet extends DHBaseActorSheet { fixed: true } }, + { + handler: DHBaseActorSheet.getBaseAttackContextOptions, + selector: '[data-item-uuid][data-type="attack"]', + options: { + parentClassHooks: false, + fixed: true + } + }, { handler: CharacterSheet.#getDomainCardContextOptions, selector: '[data-item-uuid][data-type="domainCard"]', diff --git a/module/applications/sheets/actors/companion.mjs b/module/applications/sheets/actors/companion.mjs index b30b9c07..a01b4a64 100644 --- a/module/applications/sheets/actors/companion.mjs +++ b/module/applications/sheets/actors/companion.mjs @@ -11,7 +11,17 @@ export default class DhCompanionSheet extends DHBaseActorSheet { toggleStress: DhCompanionSheet.#toggleStress, actionRoll: DhCompanionSheet.#actionRoll, levelManagement: DhCompanionSheet.#levelManagement - } + }, + contextMenus: [ + { + handler: DHBaseActorSheet.getBaseAttackContextOptions, + selector: '[data-item-uuid][data-type="attack"]', + options: { + parentClassHooks: false, + fixed: true + } + } + ] }; static PARTS = { diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 5cd0f6a5..7b820822 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -189,6 +189,43 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true }); } + /** + * Get the set of ContextMenu options for the base attack. + * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance + * @this {CharacterSheet} + * @protected + */ + static getBaseAttackContextOptions() { + /**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */ + return [ + { + label: 'DAGGERHEART.CONFIG.RollTypes.attack.name', + icon: 'fa-solid fa-burst', + onClick: async (event, target) => (await getDocFromElement(target)).use(event) + }, + { + label: 'DAGGERHEART.GENERAL.damage', + icon: 'fa-solid fa-explosion', + onClick: async (event, target) => { + const doc = await getDocFromElement(target), + action = doc?.system?.attack ?? doc; + const config = action.prepareConfig(event); + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects( + this.document, + doc + ); + config.hasRoll = false; + return action && action.workflow.get('damage').execute(config, null, true); + } + }, + { + label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat', + icon: 'fa-solid fa-message', + onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid) + } + ]; + } + /* -------------------------------------------- */ /* Application Listener Actions */ /* -------------------------------------------- */ diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index c4d07c25..1f7e1c92 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -75,7 +75,12 @@ export default class DHAttackAction extends DHDamageAction { const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active; for (const { value, valueAlt, type } of damage.parts) { const usedValue = useAltDamage ? valueAlt : value; - const str = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {}); + const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {}); + const str = damageString + ? damageString + : game.i18n.format('DAGGERHEART.GENERAL.missingX', { + x: game.i18n.localize('DAGGERHEART.GENERAL.damage') + }); const icons = Array.from(type) .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon) From d98a7c951e5363ce1980b1540767bad923385e64 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:20:06 +0200 Subject: [PATCH 72/73] [Fix] Tooltip Color Scope (#1964) * Added DH style to tooltips * Setting dh-style for ResourceManagementTooltip and ArmorManagementTooltip --- module/applications/sheets/actors/character.mjs | 4 ++-- module/documents/tooltipManager.mjs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index e4d0e6d9..5d0e7144 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -1053,7 +1053,7 @@ export default class CharacterSheet extends DHBaseActorSheet { game.tooltip.activate(target, { html, locked: true, - cssClass: 'bordered-tooltip', + cssClass: 'bordered-tooltip dh-style', direction: 'DOWN' }); @@ -1149,7 +1149,7 @@ export default class CharacterSheet extends DHBaseActorSheet { game.tooltip.activate(target, { html, locked: true, - cssClass: 'bordered-tooltip', + cssClass: 'bordered-tooltip dh-style', direction: 'DOWN', noOffset: true }); diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index 18c03169..3e3f4a16 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -3,7 +3,6 @@ import { AdversaryBPPerEncounter, BaseBPPerEncounter } from '../config/encounter export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager { #wide = false; #bordered = false; - #active = false; async activate(element, options = {}) { const { TextEditor } = foundry.applications.ux; From 5dbcd9448064f97d202abb541cd5b4c5072fe544 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 1 Jun 2026 22:22:50 +0200 Subject: [PATCH 73/73] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 5994c576..588ceafe 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.3.0", + "version": "2.3.1", "compatibility": { "minimum": "14.361", "verified": "14.363", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.0/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.1/system.zip", "authors": [ { "name": "WBHarry"