From 77c5cfcbb79e7131d58650fdc73016aece3e04f5 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 3 Jun 2026 21:46:23 +0200 Subject: [PATCH 01/63] Fixed so that actions on homebrew downtime/items don't crash due to note having metadata --- module/applications/sheets-configs/action-base-config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index e83dfae4..b65e1cdf 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -204,7 +204,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) }; } - if (this.action.parent.metadata.isInventoryItem) { + if (this.action.parent.metadata?.isInventoryItem) { options.quantity = { label: 'DAGGERHEART.GENERAL.itemQuantity', group: 'Global' From 6747be49b2089b4cc49200875c9cf82c553fa74f Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 4 Jun 2026 05:15:41 -0400 Subject: [PATCH 02/63] Allow removing empty string domains (#1968) --- module/applications/settings/homebrewSettings.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 40ea0301..09bb00f2 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -111,7 +111,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli switch (partId) { case 'domains': - const selectedDomain = this.selected.domain ? this.settings.domains[this.selected.domain] : null; + const selectedDomain = this.settings.domains[this.selected.domain] ?? null; const enrichedDescription = selectedDomain ? await foundry.applications.ux.TextEditor.implementation.enrichHTML(selectedDomain.description) : null; From 5ac4fc3b9ceec0f83e829295e6b197a47cdd4e01 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 4 Jun 2026 05:42:17 -0400 Subject: [PATCH 03/63] [Fix] visual quirk with blur in unfocused countdown (#1970) * Fix visual quirk with blur in unfocused countdown * Snuck in fixes and refactors --- module/applications/ui/countdowns.mjs | 22 ++++++++++++---------- styles/less/ui/countdown/countdown.less | 13 ++++++------- templates/ui/countdowns.hbs | 12 +++++++----- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 76e2b399..6fa05e29 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -31,9 +31,9 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application minimizable: false }, actions: { - toggleViewMode: DhCountdowns.#toggleViewMode, - editCountdowns: DhCountdowns.#editCountdowns, - loopCountdown: DhCountdowns.#loopCountdown, + toggleViewMode: DhCountdowns.#onToggleViewMode, + editCountdowns: DhCountdowns.#onEditCountdowns, + loopCountdown: DhCountdowns.#onLoopCountdown, decreaseCountdown: (_, target) => this.editCountdown(false, target), increaseCountdown: (_, target) => this.editCountdown(true, target) }, @@ -147,7 +147,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return true; } - static async #toggleViewMode() { + static async #onToggleViewMode() { const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode); const appMode = CONFIG.DH.GENERAL.countdownAppMode; const newMode = currentMode === appMode.textIcon ? appMode.iconOnly : appMode.textIcon; @@ -158,15 +158,16 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application this.render(); } - static async #editCountdowns() { + static async #onEditCountdowns() { new game.system.api.applications.ui.CountdownEdit().render(true); } - static async #loopCountdown(_, target) { + static async #onLoopCountdown(_, target) { if (!DhCountdowns.canPerformEdit()) return; const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const countdown = settings.countdowns[target.id]; + const countdownId = target.closest('[data-countdown]').dataset.countdown; + const countdown = settings.countdowns[countdownId]; let progressMax = countdown.progress.start; let message = null; @@ -185,7 +186,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application await waitForDiceSoNice(message); await settings.updateSource({ - [`countdowns.${target.id}.progress`]: { + [`countdowns.${countdownId}.progress`]: { current: newMax, start: newMax } @@ -199,11 +200,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application if (!DhCountdowns.canPerformEdit()) return; const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const countdown = settings.countdowns[target.id]; + const countdownId = target.closest('[data-countdown]').dataset.countdown; + const countdown = settings.countdowns[countdownId]; const newCurrent = increase ? Math.min(countdown.progress.current + 1, countdown.progress.start) : Math.max(countdown.progress.current - 1, 0); - await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent }); + await settings.updateSource({ [`countdowns.${countdownId}.progress.current`]: newCurrent }); await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, { refreshType: RefreshType.Countdown }); diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 66a6c88a..63e539ba 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -18,7 +18,7 @@ border: 0; box-shadow: none; color: @color-text-primary; - width: 300px; + width: 18.75rem; pointer-events: all; align-self: flex-end; transition: 0.3s right ease-in-out; @@ -36,7 +36,7 @@ transition: opacity var(--ui-fade-duration); } - :not(.performance-low, .noblur) { + &:not(.performance-low, .noblur) { backdrop-filter: blur(5px); } @@ -49,8 +49,7 @@ } &.icon-only { - width: 180px; - min-width: 180px; + width: 12rem; } .countdowns-header, @@ -108,8 +107,8 @@ gap: 16px; img { - width: 44px; - height: 44px; + width: 2.75rem; + height: 2.75rem; border-radius: 6px; } @@ -127,7 +126,7 @@ .countdown-tool-controls { display: flex; align-items: center; - gap: 16px; + gap: var(--spacer-12); } .progress-tag { diff --git a/templates/ui/countdowns.hbs b/templates/ui/countdowns.hbs index 95067826..faaffdc5 100644 --- a/templates/ui/countdowns.hbs +++ b/templates/ui/countdowns.hbs @@ -11,18 +11,20 @@
{{#each countdowns as | countdown id |}} -
+
- {{#unless ../iconOnly}}{{/unless}} + {{#unless ../iconOnly}} +
{{countdown.name}}
+ {{/unless}}
- {{#if countdown.editable}}{{/if}} + {{#if countdown.editable}}{{/if}}
{{countdown.progress.current}}/{{countdown.progress.start}}
- {{#if countdown.editable}}{{/if}} + {{#if countdown.editable}}{{/if}}
{{#if (not ../iconOnly)}} @@ -31,7 +33,7 @@ {{/if}} {{#unless (eq countdown.progress.looping "noLooping")}} - + {{#if (eq countdown.progress.looping "increasing")}} From c0c909584792661089370877e0eb521a836f7ca2 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 4 Jun 2026 14:08:40 -0400 Subject: [PATCH 04/63] [Fix] Preload ancestry and community features in description (#1967) * Preload ancestry and community features in description * Corrected comments --------- Co-authored-by: WBHarry --- module/data/item/ancestry.mjs | 6 +++++- module/data/item/community.mjs | 6 +++++- module/data/item/subclass.mjs | 2 +- module/helpers/utils.mjs | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/module/data/item/ancestry.mjs b/module/data/item/ancestry.mjs index b9253a3c..eae1136c 100644 --- a/module/data/item/ancestry.mjs +++ b/module/data/item/ancestry.mjs @@ -1,6 +1,6 @@ import BaseDataItem from './base.mjs'; import ItemLinkFields from '../../data/fields/itemLinkFields.mjs'; -import { getFeaturesHTMLData } from '../../helpers/utils.mjs'; +import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs'; export default class DHAncestry extends BaseDataItem { /** @inheritDoc */ @@ -45,6 +45,10 @@ export default class DHAncestry extends BaseDataItem { /**@inheritdoc */ async getDescriptionData() { + // Preload all ancestry 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 baseDescription = this.description; const features = await getFeaturesHTMLData(this.features); diff --git a/module/data/item/community.mjs b/module/data/item/community.mjs index 6d054976..6f4470b8 100644 --- a/module/data/item/community.mjs +++ b/module/data/item/community.mjs @@ -1,4 +1,4 @@ -import { getFeaturesHTMLData } from '../../helpers/utils.mjs'; +import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import BaseDataItem from './base.mjs'; @@ -27,6 +27,10 @@ export default class DHCommunity extends BaseDataItem { /**@inheritdoc */ async getDescriptionData() { + // Preload all community features for acquisition from the cache + // todo: make feature acquisition async and replace feature helpers for methods + await fromUuids(this._source.features); + const baseDescription = this.description; const features = await getFeaturesHTMLData(this.features); diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 55b078c2..934b55d3 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -91,7 +91,7 @@ export default class DHSubclass extends BaseDataItem { ? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label) : null; - // Preload all class features for acquisition from the cache + // Preload all subclass 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)); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 2f20175b..ddc353b1 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -879,6 +879,7 @@ export async function fromUuids(uuids) { const packEmbeddedEntries = entries.filter( e => !(e.value instanceof Document) && + e.parsed && e.parsed.collection instanceof foundry.documents.collections.CompendiumCollection && e.parsed.embedded.length > 0 ); @@ -895,7 +896,7 @@ export async function fromUuids(uuids) { const pack = game.packs.get(packGroup[0].value.pack); if (!pack) continue; - const ids = packGroup.map(p => p.parsed.id); + const ids = packGroup.map(p => p.parsed?.id).filter(id => !!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; From 52b81de11f7224c15f2e76243f4228abb0ea859f Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 5 Jun 2026 00:30:41 +0200 Subject: [PATCH 05/63] Fixed so that the saved data for an experience that is in the character data is used over that in the levelup data if available (#1971) --- module/applications/levelup/levelup.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index c4616d9a..03638548 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -358,14 +358,14 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases'); if (experienceIncreaseTagify) { const allExperiences = { - ...this.actor.system.experiences, ...Object.values(this.levelup.levels).reduce((acc, level) => { for (const key of Object.keys(level.achievements.experiences)) { acc[key] = level.achievements.experiences[key]; } return acc; - }, {}) + }, {}), + ...this.actor.system.experiences }; tagifyElement( experienceIncreaseTagify, From 2fc5b01f091edca3138aa83fdfbf1d055c1a6f19 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 05:31:01 -0400 Subject: [PATCH 06/63] Fix rerolling when hope/fear automation is enabled (#1972) --- module/dice/helpers.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs index 33519949..35adb8b7 100644 --- a/module/dice/helpers.mjs +++ b/module/dice/helpers.mjs @@ -1,3 +1,5 @@ +import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; + 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; From 5be79f4ab83b2b4483a0f4deb7f7e7f60bc60e34 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 05:33:20 -0400 Subject: [PATCH 07/63] Fix several issues with inline damage (#1973) --- module/data/actor/tierAdjustment.mjs | 5 +++-- module/dice/dhRoll.mjs | 1 + module/enrichers/DamageEnricher.mjs | 4 ++-- module/enrichers/parser.mjs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs index 785eec2b..bc6ad176 100644 --- a/module/data/actor/tierAdjustment.mjs +++ b/module/data/actor/tierAdjustment.mjs @@ -1,5 +1,6 @@ import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs'; import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; +import { parseInlineParams } from '../../enrichers/parser.mjs'; export function getTierAdjustedAdversary(source, tier) { const currentTier = source.tier ?? 1; @@ -60,8 +61,8 @@ export function getTierAdjustedAdversary(source, tier) { 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; + const { value: formula } = parseInlineParams(inner, { first: 'value' }); + if (!formula) return match; try { const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula; diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 02c4ab24..fb20870f 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -37,6 +37,7 @@ export default class DHRoll extends Roll { static async buildConfigure(config = {}, message = {}) { config.hooks = [...this.getHooks(), '']; config.dialog ??= {}; + config.damageOptions ??= {}; for (const hook of config.hooks) { if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null; diff --git a/module/enrichers/DamageEnricher.mjs b/module/enrichers/DamageEnricher.mjs index e3f9c42a..db0e8729 100644 --- a/module/enrichers/DamageEnricher.mjs +++ b/module/enrichers/DamageEnricher.mjs @@ -1,7 +1,7 @@ import { parseInlineParams } from './parser.mjs'; export default function DhDamageEnricher(match, _options) { - const { value, type, inline } = parseInlineParams(match[1]); + const { value, type, inline } = parseInlineParams(match[1], { first: 'value' }); if (!value || !type) return match[0]; return getDamageMessage(value, type, inline, match[0]); } @@ -59,7 +59,7 @@ export const renderDamageButton = async event => { { formula: value, applyTo: CONFIG.DH.GENERAL.healingTypes.hitPoints.id, - type: type + damageTypes: type } ] }; diff --git a/module/enrichers/parser.mjs b/module/enrichers/parser.mjs index 365caec9..76ea0b73 100644 --- a/module/enrichers/parser.mjs +++ b/module/enrichers/parser.mjs @@ -8,7 +8,7 @@ export function parseInlineParams(paramString, { first } = {}) { const parts = paramString.split('|').map(x => x.trim()); const params = {}; for (const [idx, param] of parts.entries()) { - if (first && idx === 0) { + if (first && idx === 0 && !param.includes(':')) { params[first] = param; } else { const parts = param.split(':'); From f0a7539018dd9f6c6c1320b1036adb084a483d33 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 06:25:44 -0400 Subject: [PATCH 08/63] Update README.md (#1976) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f59143fd..ac3666b3 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,10 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart Looking to contribute to the project? Look no further, check out our [contributing guide](CONTRIBUTING.md), and keep the [Code of Conduct](coc.md) in mind when working on things. +## AI Policy + +The Foundryborne Daggerheart system does not make use of AI (generative or otherwise) for any area of its implementation. We expect all contributors to follow this same policy when contributing with a pull request; contributions made using AI will be rejected outright. + ## Disclaimer: **Daggerheart System** From 3527fd7959a54bbee9e4c0419d1a973d2c42c770 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 5 Jun 2026 12:31:30 +0200 Subject: [PATCH 09/63] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 588ceafe..ed14a17b 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.1", + "version": "2.3.2", "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.1/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.2/system.zip", "authors": [ { "name": "WBHarry" From 6312a171e23f9b2769b3e81da3da1aff760c0ec7 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:36:07 +0200 Subject: [PATCH 10/63] [Housekeeping] Styles Index:ification (#1977) --- styles/daggerheart.less | 12 +--- styles/less/dialog/actions/index.less | 1 + styles/less/dialog/attribution/index.less | 1 + styles/less/dialog/beastform/index.less | 1 + .../less/dialog/character-creation/index.less | 4 ++ styles/less/dialog/character-reset/index.less | 1 + .../compendiumBrowserPackDialog/index.less | 1 + .../less/dialog/damage-reduction/index.less | 2 + .../less/dialog/damage-selection/index.less | 1 + styles/less/dialog/death-move/index.less | 1 + styles/less/dialog/dice-roll/index.less | 1 + styles/less/dialog/downtime/index.less | 1 + .../dialog/group-roll-dialog/_common.less | 44 -------------- .../less/dialog/group-roll-dialog/index.less | 11 +--- .../less/dialog/group-roll-dialog/sheet.less | 48 +++++++++++++++ styles/less/dialog/image-select/index.less | 1 + styles/less/dialog/index.less | 60 ++++++------------- styles/less/dialog/item-transfer/index.less | 1 + .../less/dialog/multiclass-choice/index.less | 1 + styles/less/dialog/resource-dice/index.less | 1 + styles/less/dialog/risk-it-all/index.less | 1 + styles/less/dialog/settings/index.less | 1 + styles/less/dialog/tag-team-dialog/index.less | 2 + styles/less/hud/index.less | 2 +- styles/less/hud/token-hud/index.less | 1 + .../adversary-settings/index.less | 3 + .../character-settings/index.less | 1 + .../environment-settings/index.less | 2 + styles/less/sheets-settings/index.less | 10 +--- styles/less/sheets/actions/index.less | 1 + styles/less/sheets/activeEffects/index.less | 1 + styles/less/sheets/actors/index.less | 7 +++ styles/less/sheets/index.less | 27 ++------- styles/less/sheets/items/index.less | 6 ++ styles/less/sheets/rollTables/index.less | 1 + styles/less/ui/chat/index.less | 10 ++++ styles/less/ui/combat-sidebar/index.less | 5 ++ styles/less/ui/countdown/index.less | 3 + styles/less/ui/effects-display/index.less | 1 + styles/less/ui/index.less | 51 ++++------------ styles/less/ui/item-browser/index.less | 1 + styles/less/ui/ownership-selection/index.less | 1 + styles/less/ui/resources/index.less | 1 + styles/less/ui/scene-config/index.less | 1 + styles/less/ui/scene-navigation/index.less | 1 + .../settings/appearance-settings/index.less | 1 + .../ui/settings/homebrew-settings/index.less | 3 + styles/less/ui/settings/index.less | 3 + styles/less/ui/sidebar/index.less | 2 + styles/less/utils/index.less | 4 ++ styles/less/ux/autocomplete/index.less | 1 + styles/less/ux/index.less | 12 +--- styles/less/ux/tooltip/index.less | 7 +++ 53 files changed, 185 insertions(+), 183 deletions(-) create mode 100644 styles/less/dialog/actions/index.less create mode 100644 styles/less/dialog/attribution/index.less create mode 100644 styles/less/dialog/beastform/index.less create mode 100644 styles/less/dialog/character-creation/index.less create mode 100644 styles/less/dialog/character-reset/index.less create mode 100644 styles/less/dialog/compendiumBrowserPackDialog/index.less create mode 100644 styles/less/dialog/damage-reduction/index.less create mode 100644 styles/less/dialog/damage-selection/index.less create mode 100644 styles/less/dialog/death-move/index.less create mode 100644 styles/less/dialog/dice-roll/index.less create mode 100644 styles/less/dialog/downtime/index.less delete mode 100644 styles/less/dialog/group-roll-dialog/_common.less create mode 100644 styles/less/dialog/group-roll-dialog/sheet.less create mode 100644 styles/less/dialog/image-select/index.less create mode 100644 styles/less/dialog/item-transfer/index.less create mode 100644 styles/less/dialog/multiclass-choice/index.less create mode 100644 styles/less/dialog/resource-dice/index.less create mode 100644 styles/less/dialog/risk-it-all/index.less create mode 100644 styles/less/dialog/settings/index.less create mode 100644 styles/less/dialog/tag-team-dialog/index.less create mode 100644 styles/less/hud/token-hud/index.less create mode 100644 styles/less/sheets-settings/adversary-settings/index.less create mode 100644 styles/less/sheets-settings/character-settings/index.less create mode 100644 styles/less/sheets-settings/environment-settings/index.less create mode 100644 styles/less/sheets/actions/index.less create mode 100644 styles/less/sheets/activeEffects/index.less create mode 100644 styles/less/sheets/actors/index.less create mode 100644 styles/less/sheets/items/index.less create mode 100644 styles/less/sheets/rollTables/index.less create mode 100644 styles/less/ui/chat/index.less create mode 100644 styles/less/ui/combat-sidebar/index.less create mode 100644 styles/less/ui/countdown/index.less create mode 100644 styles/less/ui/effects-display/index.less create mode 100644 styles/less/ui/item-browser/index.less create mode 100644 styles/less/ui/ownership-selection/index.less create mode 100644 styles/less/ui/resources/index.less create mode 100644 styles/less/ui/scene-config/index.less create mode 100644 styles/less/ui/scene-navigation/index.less create mode 100644 styles/less/ui/settings/appearance-settings/index.less create mode 100644 styles/less/ui/settings/homebrew-settings/index.less create mode 100644 styles/less/ui/settings/index.less create mode 100644 styles/less/ui/sidebar/index.less create mode 100644 styles/less/utils/index.less create mode 100644 styles/less/ux/autocomplete/index.less create mode 100644 styles/less/ux/tooltip/index.less diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 187402fb..4da2e043 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -1,19 +1,11 @@ @import './less/sheets/index.less'; @import './less/sheets-settings/index.less'; - @import './less/dialog/index.less'; - -@import './less//hud/index.less'; - -@import './less/utils/colors.less'; -@import './less/utils/fonts.less'; - +@import './less/hud/index.less'; +@import './less/utils/index.less'; @import './less/global/index.less'; - @import './less/ui/index.less'; - @import './less/ux/index.less'; @import '../build/tagify.css'; -@import './less/utils/mixin.less'; diff --git a/styles/less/dialog/actions/index.less b/styles/less/dialog/actions/index.less new file mode 100644 index 00000000..e9cc0401 --- /dev/null +++ b/styles/less/dialog/actions/index.less @@ -0,0 +1 @@ +@import "./action-list.less"; \ No newline at end of file diff --git a/styles/less/dialog/attribution/index.less b/styles/less/dialog/attribution/index.less new file mode 100644 index 00000000..2f8eaf45 --- /dev/null +++ b/styles/less/dialog/attribution/index.less @@ -0,0 +1 @@ +@import "./sheet.less"; \ No newline at end of file diff --git a/styles/less/dialog/beastform/index.less b/styles/less/dialog/beastform/index.less new file mode 100644 index 00000000..2f8eaf45 --- /dev/null +++ b/styles/less/dialog/beastform/index.less @@ -0,0 +1 @@ +@import "./sheet.less"; \ No newline at end of file diff --git a/styles/less/dialog/character-creation/index.less b/styles/less/dialog/character-creation/index.less new file mode 100644 index 00000000..adf8d57a --- /dev/null +++ b/styles/less/dialog/character-creation/index.less @@ -0,0 +1,4 @@ +@import "./sheet.less"; +@import "./creation-action-footer.less"; +@import "./selections-container.less"; +@import "./tab-navigation.less"; \ No newline at end of file diff --git a/styles/less/dialog/character-reset/index.less b/styles/less/dialog/character-reset/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/character-reset/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/compendiumBrowserPackDialog/index.less b/styles/less/dialog/compendiumBrowserPackDialog/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/compendiumBrowserPackDialog/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/damage-reduction/index.less b/styles/less/dialog/damage-reduction/index.less new file mode 100644 index 00000000..0b8e94a8 --- /dev/null +++ b/styles/less/dialog/damage-reduction/index.less @@ -0,0 +1,2 @@ +@import './sheets.less'; +@import './damage-reduction-container.less'; \ No newline at end of file diff --git a/styles/less/dialog/damage-selection/index.less b/styles/less/dialog/damage-selection/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/damage-selection/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/death-move/index.less b/styles/less/dialog/death-move/index.less new file mode 100644 index 00000000..8a8a16c4 --- /dev/null +++ b/styles/less/dialog/death-move/index.less @@ -0,0 +1 @@ +@import './death-move-container.less'; \ No newline at end of file diff --git a/styles/less/dialog/dice-roll/index.less b/styles/less/dialog/dice-roll/index.less new file mode 100644 index 00000000..8e0af6e0 --- /dev/null +++ b/styles/less/dialog/dice-roll/index.less @@ -0,0 +1 @@ +@import './roll-selection.less'; \ No newline at end of file diff --git a/styles/less/dialog/downtime/index.less b/styles/less/dialog/downtime/index.less new file mode 100644 index 00000000..09cc2dfe --- /dev/null +++ b/styles/less/dialog/downtime/index.less @@ -0,0 +1 @@ +@import './downtime-container.less'; \ No newline at end of file diff --git a/styles/less/dialog/group-roll-dialog/_common.less b/styles/less/dialog/group-roll-dialog/_common.less deleted file mode 100644 index f74ab8a0..00000000 --- a/styles/less/dialog/group-roll-dialog/_common.less +++ /dev/null @@ -1,44 +0,0 @@ -h1 { - color: @color-text-emphatic; - font: 700 var(--font-size-24) var(--dh-font-subtitle); - text-align: center; -} - -header { - --bar-color: light-dark(@dark-blue, @golden); - color: light-dark(@dark, @beige); - display: flex; - justify-content: center; - align-items: center; - - &:not(:first-child) { - margin-top: var(--spacer-8); - } - - span { - padding: 0 10px; - } - - &:before { - content: ' '; - flex: 1; - height: 1px; - background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, var(--bar-color) 100%); - } - - &:after { - content: ' '; - flex: 1; - height: 1px; - background: linear-gradient(90deg, var(--bar-color) 0%, rgba(0, 0, 0, 0) 100%); - } -} - -img.portrait { - border-radius: 50%; - border: none; - object-fit: cover; - object-position: center top; - width: 2.5rem; - height: 2.5rem; -} diff --git a/styles/less/dialog/group-roll-dialog/index.less b/styles/less/dialog/group-roll-dialog/index.less index 27925fa2..f90b57dc 100644 --- a/styles/less/dialog/group-roll-dialog/index.less +++ b/styles/less/dialog/group-roll-dialog/index.less @@ -1,8 +1,3 @@ -.daggerheart.dialog.dh-style.views.group-roll-dialog { - .window-content { - @import "./_common.less"; - } -} - -@import "./initialization.less"; -@import "./main.less"; +@import './sheet.less'; +@import './initialization.less'; +@import './main.less'; diff --git a/styles/less/dialog/group-roll-dialog/sheet.less b/styles/less/dialog/group-roll-dialog/sheet.less new file mode 100644 index 00000000..938710c9 --- /dev/null +++ b/styles/less/dialog/group-roll-dialog/sheet.less @@ -0,0 +1,48 @@ +.daggerheart.dialog.dh-style.views.group-roll-dialog { + .window-content { + h1 { + color: @color-text-emphatic; + font: 700 var(--font-size-24) var(--dh-font-subtitle); + text-align: center; + } + + header { + --bar-color: light-dark(@dark-blue, @golden); + color: light-dark(@dark, @beige); + display: flex; + justify-content: center; + align-items: center; + + &:not(:first-child) { + margin-top: var(--spacer-8); + } + + span { + padding: 0 10px; + } + + &:before { + content: ' '; + flex: 1; + height: 1px; + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, var(--bar-color) 100%); + } + + &:after { + content: ' '; + flex: 1; + height: 1px; + background: linear-gradient(90deg, var(--bar-color) 0%, rgba(0, 0, 0, 0) 100%); + } + } + + img.portrait { + border-radius: 50%; + border: none; + object-fit: cover; + object-position: center top; + width: 2.5rem; + height: 2.5rem; + } + } +} \ No newline at end of file diff --git a/styles/less/dialog/image-select/index.less b/styles/less/dialog/image-select/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/image-select/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index e8f61318..4ce4834e 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -1,42 +1,20 @@ -@import './attribution/sheet.less'; -@import './level-up/index.less'; - -@import './resource-dice/sheet.less'; - -@import './actions/action-list.less'; - -@import './damage-selection/sheet.less'; - -@import './downtime/downtime-container.less'; - -@import './death-move/death-move-container.less'; - -@import './beastform/sheet.less'; - -@import './character-creation/creation-action-footer.less'; -@import './character-creation/selections-container.less'; -@import './character-creation/sheet.less'; -@import './character-creation/tab-navigation.less'; - -@import './dice-roll/roll-selection.less'; -@import './damage-reduction/damage-reduction-container.less'; -@import './damage-reduction/sheets.less'; - -@import './multiclass-choice/sheet.less'; - -@import './tag-team-dialog/initialization.less'; -@import './tag-team-dialog/sheet.less'; - +@import './actions/index.less'; +@import './attribution/index.less'; +@import './beastform/index.less'; +@import './character-creation/index.less'; +@import './character-reset/index.less'; +@import './compendiumBrowserPackDialog/index.less'; +@import './damage-reduction/index.less'; +@import './damage-selection/index.less'; +@import './death-move/index.less'; +@import './dice-roll/index.less'; +@import './downtime/index.less'; @import './group-roll-dialog/index.less'; - -@import './image-select/sheet.less'; - -@import './item-transfer/sheet.less'; - -@import './settings/change-currency-icon.less'; - -@import './risk-it-all/sheet.less'; - -@import './character-reset/sheet.less'; - -@import './compendiumBrowserPackDialog/sheet.less'; +@import './level-up/index.less'; +@import './resource-dice/index.less'; +@import './multiclass-choice/index.less'; +@import './tag-team-dialog/index.less'; +@import './image-select/index.less'; +@import './item-transfer/index.less'; +@import './settings/index.less'; +@import './risk-it-all/index.less'; \ No newline at end of file diff --git a/styles/less/dialog/item-transfer/index.less b/styles/less/dialog/item-transfer/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/item-transfer/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/multiclass-choice/index.less b/styles/less/dialog/multiclass-choice/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/multiclass-choice/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/resource-dice/index.less b/styles/less/dialog/resource-dice/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/resource-dice/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/risk-it-all/index.less b/styles/less/dialog/risk-it-all/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/risk-it-all/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/settings/index.less b/styles/less/dialog/settings/index.less new file mode 100644 index 00000000..235f3b9c --- /dev/null +++ b/styles/less/dialog/settings/index.less @@ -0,0 +1 @@ +@import './change-currency-icon.less'; \ No newline at end of file diff --git a/styles/less/dialog/tag-team-dialog/index.less b/styles/less/dialog/tag-team-dialog/index.less new file mode 100644 index 00000000..8bf56824 --- /dev/null +++ b/styles/less/dialog/tag-team-dialog/index.less @@ -0,0 +1,2 @@ +@import './sheet.less'; +@import './initialization.less'; \ No newline at end of file diff --git a/styles/less/hud/index.less b/styles/less/hud/index.less index 459f8fd7..f1f4602e 100644 --- a/styles/less/hud/index.less +++ b/styles/less/hud/index.less @@ -1 +1 @@ -@import './token-hud/token-hud.less'; +@import './token-hud/index.less'; diff --git a/styles/less/hud/token-hud/index.less b/styles/less/hud/token-hud/index.less new file mode 100644 index 00000000..c86d0939 --- /dev/null +++ b/styles/less/hud/token-hud/index.less @@ -0,0 +1 @@ +@import './token-hud.less'; \ No newline at end of file diff --git a/styles/less/sheets-settings/adversary-settings/index.less b/styles/less/sheets-settings/adversary-settings/index.less new file mode 100644 index 00000000..5968577d --- /dev/null +++ b/styles/less/sheets-settings/adversary-settings/index.less @@ -0,0 +1,3 @@ +@import './sheet.less'; +@import './experiences.less'; +@import './features.less'; \ No newline at end of file diff --git a/styles/less/sheets-settings/character-settings/index.less b/styles/less/sheets-settings/character-settings/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/sheets-settings/character-settings/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/sheets-settings/environment-settings/index.less b/styles/less/sheets-settings/environment-settings/index.less new file mode 100644 index 00000000..1e6ee34d --- /dev/null +++ b/styles/less/sheets-settings/environment-settings/index.less @@ -0,0 +1,2 @@ +@import './adversaries.less'; +@import './features.less'; \ No newline at end of file diff --git a/styles/less/sheets-settings/index.less b/styles/less/sheets-settings/index.less index f575f848..53a03868 100644 --- a/styles/less/sheets-settings/index.less +++ b/styles/less/sheets-settings/index.less @@ -1,8 +1,4 @@ @import './header.less'; -@import './adversary-settings/sheet.less'; -@import './adversary-settings/experiences.less'; -@import './adversary-settings/features.less'; -@import './character-settings/sheet.less'; - -@import './environment-settings/features.less'; -@import './environment-settings/adversaries.less'; +@import './adversary-settings/index.less'; +@import './character-settings/index.less'; +@import './environment-settings/index.less'; diff --git a/styles/less/sheets/actions/index.less b/styles/less/sheets/actions/index.less new file mode 100644 index 00000000..29ef8645 --- /dev/null +++ b/styles/less/sheets/actions/index.less @@ -0,0 +1 @@ +@import './actions.less'; \ No newline at end of file diff --git a/styles/less/sheets/activeEffects/index.less b/styles/less/sheets/activeEffects/index.less new file mode 100644 index 00000000..19f8a3a7 --- /dev/null +++ b/styles/less/sheets/activeEffects/index.less @@ -0,0 +1 @@ +@import './activeEffects.less'; \ No newline at end of file diff --git a/styles/less/sheets/actors/index.less b/styles/less/sheets/actors/index.less new file mode 100644 index 00000000..959bc0f5 --- /dev/null +++ b/styles/less/sheets/actors/index.less @@ -0,0 +1,7 @@ +@import './actor-sheet-shared.less'; +@import './adversary/index.less'; +@import './character/index.less'; +@import './companion/index.less'; +@import './environment/index.less'; +@import './npc/index.less'; +@import './party/index.less'; \ No newline at end of file diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index 4312f755..451ae03a 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -1,22 +1,5 @@ -@import './actions/actions.less'; - -@import './actors/actor-sheet-shared.less'; - -@import './actors/adversary/index.less'; -@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'; -@import './items/class.less'; -@import './items/domain-card.less'; -@import './items/feature.less'; -@import './items/heritage.less'; -@import './items/item-sheet-shared.less'; - -@import './rollTables/sheet.less'; -@import './actions/actions.less'; - -@import './activeEffects/activeEffects.less'; +@import './activeEffects/index.less'; +@import './actions/index.less'; +@import './actors/index.less'; +@import './items/index.less'; +@import './rollTables/index.less'; \ No newline at end of file diff --git a/styles/less/sheets/items/index.less b/styles/less/sheets/items/index.less new file mode 100644 index 00000000..7c40a2e3 --- /dev/null +++ b/styles/less/sheets/items/index.less @@ -0,0 +1,6 @@ +@import './beastform.less'; +@import './class.less'; +@import './domain-card.less'; +@import './feature.less'; +@import './heritage.less'; +@import './item-sheet-shared.less'; \ No newline at end of file diff --git a/styles/less/sheets/rollTables/index.less b/styles/less/sheets/rollTables/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/sheets/rollTables/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/ui/chat/index.less b/styles/less/ui/chat/index.less new file mode 100644 index 00000000..9cadbd04 --- /dev/null +++ b/styles/less/ui/chat/index.less @@ -0,0 +1,10 @@ +@import './sheet.less'; +@import './ability-use.less'; +@import './action.less'; +@import './chat.less'; +@import './damage-summary.less'; +@import './deathmoves.less'; +@import './downtime.less'; +@import './effect-summary.less'; +@import './group-roll.less'; +@import './refresh-message.less'; \ No newline at end of file diff --git a/styles/less/ui/combat-sidebar/index.less b/styles/less/ui/combat-sidebar/index.less new file mode 100644 index 00000000..786815ef --- /dev/null +++ b/styles/less/ui/combat-sidebar/index.less @@ -0,0 +1,5 @@ +@import './combat-sidebar.less'; +@import './combatant-controls.less'; +@import './encounter-controls.less'; +@import './spotlight-control.less'; +@import './token-actions.less'; \ No newline at end of file diff --git a/styles/less/ui/countdown/index.less b/styles/less/ui/countdown/index.less new file mode 100644 index 00000000..45ecda26 --- /dev/null +++ b/styles/less/ui/countdown/index.less @@ -0,0 +1,3 @@ +@import './sheet.less'; +@import './countdown-edit.less'; +@import './countdown.less'; \ No newline at end of file diff --git a/styles/less/ui/effects-display/index.less b/styles/less/ui/effects-display/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/ui/effects-display/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 31ea8955..53a71b9b 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -1,40 +1,11 @@ -@import './chat/ability-use.less'; -@import './chat/action.less'; -@import './chat/chat.less'; -@import './chat/damage-summary.less'; -@import './chat/downtime.less'; -@import './chat/effect-summary.less'; -@import './chat/group-roll.less'; -@import './chat/refresh-message.less'; -@import './chat/deathmoves.less'; -@import './chat/sheet.less'; - -@import './combat-sidebar/combat-sidebar.less'; -@import './combat-sidebar/combatant-controls.less'; -@import './combat-sidebar/encounter-controls.less'; -@import './combat-sidebar/spotlight-control.less'; -@import './combat-sidebar/token-actions.less'; -@import './item-browser/item-browser.less'; - -@import './countdown/countdown.less'; -@import './countdown/countdown-edit.less'; -@import './countdown/sheet.less'; - -@import './ownership-selection/ownership-selection.less'; - -@import './resources/resources.less'; - -@import './settings/settings.less'; -@import './settings/homebrew-settings/domains.less'; -@import './settings/homebrew-settings/types.less'; -@import './settings/homebrew-settings/resources.less'; -@import './settings/appearance-settings/diceSoNice.less'; - -@import './sidebar/tabs.less'; -@import './sidebar/daggerheartMenu.less'; - -@import './scene-config/scene-config.less'; - -@import './effects-display/sheet.less'; - -@import './scene-navigation/scene-navigation.less'; +@import './chat/index.less'; +@import './combat-sidebar/index.less'; +@import './countdown/index.less'; +@import './effects-display/index.less'; +@import './item-browser/index.less'; +@import './ownership-selection/index.less'; +@import './resources/index.less'; +@import './scene-config/index.less'; +@import './scene-navigation/index.less'; +@import './settings/index.less'; +@import './sidebar/index.less'; \ No newline at end of file diff --git a/styles/less/ui/item-browser/index.less b/styles/less/ui/item-browser/index.less new file mode 100644 index 00000000..842f716b --- /dev/null +++ b/styles/less/ui/item-browser/index.less @@ -0,0 +1 @@ +@import './item-browser.less'; \ No newline at end of file diff --git a/styles/less/ui/ownership-selection/index.less b/styles/less/ui/ownership-selection/index.less new file mode 100644 index 00000000..9646670a --- /dev/null +++ b/styles/less/ui/ownership-selection/index.less @@ -0,0 +1 @@ +@import './ownership-selection.less'; \ No newline at end of file diff --git a/styles/less/ui/resources/index.less b/styles/less/ui/resources/index.less new file mode 100644 index 00000000..a7d08785 --- /dev/null +++ b/styles/less/ui/resources/index.less @@ -0,0 +1 @@ +@import './resources.less'; \ No newline at end of file diff --git a/styles/less/ui/scene-config/index.less b/styles/less/ui/scene-config/index.less new file mode 100644 index 00000000..4e3af363 --- /dev/null +++ b/styles/less/ui/scene-config/index.less @@ -0,0 +1 @@ +@import './scene-config.less'; \ No newline at end of file diff --git a/styles/less/ui/scene-navigation/index.less b/styles/less/ui/scene-navigation/index.less new file mode 100644 index 00000000..c0765ae7 --- /dev/null +++ b/styles/less/ui/scene-navigation/index.less @@ -0,0 +1 @@ +@import './scene-navigation.less'; \ No newline at end of file diff --git a/styles/less/ui/settings/appearance-settings/index.less b/styles/less/ui/settings/appearance-settings/index.less new file mode 100644 index 00000000..8b1c109a --- /dev/null +++ b/styles/less/ui/settings/appearance-settings/index.less @@ -0,0 +1 @@ +@import './diceSoNice.less'; \ No newline at end of file diff --git a/styles/less/ui/settings/homebrew-settings/index.less b/styles/less/ui/settings/homebrew-settings/index.less new file mode 100644 index 00000000..f0a8bfc1 --- /dev/null +++ b/styles/less/ui/settings/homebrew-settings/index.less @@ -0,0 +1,3 @@ +@import './domains.less'; +@import './resources.less'; +@import './types.less'; \ No newline at end of file diff --git a/styles/less/ui/settings/index.less b/styles/less/ui/settings/index.less new file mode 100644 index 00000000..4e1aa798 --- /dev/null +++ b/styles/less/ui/settings/index.less @@ -0,0 +1,3 @@ +@import './settings.less'; +@import './appearance-settings/index.less'; +@import './homebrew-settings/index.less'; \ No newline at end of file diff --git a/styles/less/ui/sidebar/index.less b/styles/less/ui/sidebar/index.less new file mode 100644 index 00000000..b4961b41 --- /dev/null +++ b/styles/less/ui/sidebar/index.less @@ -0,0 +1,2 @@ +@import './daggerheartMenu.less'; +@import './tabs.less'; \ No newline at end of file diff --git a/styles/less/utils/index.less b/styles/less/utils/index.less new file mode 100644 index 00000000..37b096d3 --- /dev/null +++ b/styles/less/utils/index.less @@ -0,0 +1,4 @@ +@import './colors.less'; +@import './fonts.less'; +@import './mixin.less'; +@import './spacing.less'; \ No newline at end of file diff --git a/styles/less/ux/autocomplete/index.less b/styles/less/ux/autocomplete/index.less new file mode 100644 index 00000000..91007146 --- /dev/null +++ b/styles/less/ux/autocomplete/index.less @@ -0,0 +1 @@ +@import './autocomplete.less'; \ No newline at end of file diff --git a/styles/less/ux/index.less b/styles/less/ux/index.less index a73f2d1c..b6c9a2e7 100644 --- a/styles/less/ux/index.less +++ b/styles/less/ux/index.less @@ -1,10 +1,2 @@ -@import './tooltip/sheet.less'; -@import './tooltip/tooltip.less'; -@import './tooltip/armorManagement.less'; -@import './tooltip/battlepoints.less'; -@import './tooltip/bordered-tooltip.less'; -@import './tooltip/domain-cards.less'; - -@import './autocomplete/autocomplete.less'; - -@import './tooltip/resource-management.less'; +@import './autocomplete/index.less'; +@import './tooltip/index.less'; \ No newline at end of file diff --git a/styles/less/ux/tooltip/index.less b/styles/less/ux/tooltip/index.less new file mode 100644 index 00000000..eeec9354 --- /dev/null +++ b/styles/less/ux/tooltip/index.less @@ -0,0 +1,7 @@ +@import './sheet.less'; +@import './armorManagement.less'; +@import './battlepoints.less'; +@import './bordered-tooltip.less'; +@import './domain-cards.less'; +@import './resource-management.less'; +@import './tooltip.less'; \ No newline at end of file From a4428fd5be62fab57f5b32f3901b9bfe0d6807ac Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 15:53:15 -0400 Subject: [PATCH 11/63] Replace prettier with stylistic, improve types, and add no-undef rule (#1975) --- .editorconfig | 1 + .prettierrc | 13 - daggerheart.d.ts | 24 +- daggerheart.mjs | 4 +- eslint.config.mjs | 97 ++- jsconfig.json | 4 +- .../characterCreation/characterCreation.mjs | 12 +- module/applications/dialogs/d20RollDialog.mjs | 20 +- .../dialogs/damageReductionDialog.mjs | 8 +- module/applications/dialogs/downtime.mjs | 4 +- .../applications/dialogs/groupRollDialog.mjs | 4 +- module/applications/dialogs/tagTeamDialog.mjs | 4 +- module/applications/hud/tokenHUD.mjs | 32 +- .../applications/levelup/characterLevelup.mjs | 24 +- .../applications/levelup/companionLevelup.mjs | 18 +- module/applications/levelup/levelup.mjs | 248 +------- .../settings/homebrewSettings.mjs | 4 +- .../sheets-configs/setting-feature-config.mjs | 10 +- .../applications/sheets/actors/character.mjs | 10 +- module/applications/sheets/actors/party.mjs | 8 +- .../sheets/api/application-mixin.mjs | 8 +- module/applications/sheets/api/base-actor.mjs | 4 +- .../sheets/api/item-attachment-sheet.mjs | 10 - .../sidebar/tabs/actorDirectory.mjs | 4 +- module/applications/ui/chatLog.mjs | 8 +- module/applications/ui/countdownEdit.mjs | 14 +- module/applications/ui/countdowns.mjs | 18 +- module/applications/ui/effectsDisplay.mjs | 8 +- module/canvas/placeables/token.mjs | 12 +- module/config/itemBrowserConfig.mjs | 4 +- module/data/action/attackAction.mjs | 4 +- module/data/action/baseAction.mjs | 18 +- module/data/activeEffect/beastformEffect.mjs | 4 +- .../data/activeEffect/changeTypes/armor.mjs | 8 +- module/data/actor/character.mjs | 46 +- module/data/companionLevelup.mjs | 18 +- module/data/countdowns.mjs | 14 +- module/data/fields/action/beastformField.mjs | 4 +- module/data/fields/action/costField.mjs | 8 +- module/data/fields/action/countdownField.mjs | 10 +- module/data/fields/action/effectsField.mjs | 10 +- module/data/fields/action/saveField.mjs | 14 +- module/data/item/beastform.mjs | 4 +- module/data/levelup.mjs | 18 +- module/dice/dhRoll.mjs | 6 +- module/dice/dualityRoll.mjs | 12 +- module/documents/activeEffect.mjs | 4 +- module/documents/chatMessage.mjs | 22 +- module/documents/item.mjs | 4 +- module/documents/token.mjs | 12 +- module/enrichers/DualityRollEnricher.mjs | 12 +- module/enrichers/TemplateEnricher.mjs | 4 +- module/helpers/utils.mjs | 14 +- module/systemRegistration/migrations.mjs | 4 +- package-lock.json | 551 ++++++++++++++---- package.json | 8 +- tools/analyze-damage.mjs | 6 +- tools/create-symlink.mjs | 2 + tools/eslint.config.mjs | 20 + 59 files changed, 886 insertions(+), 614 deletions(-) delete mode 100644 .prettierrc create mode 100644 tools/eslint.config.mjs diff --git a/.editorconfig b/.editorconfig index 6cfef2fc..aa391e00 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,6 @@ [*] indent_size = 4 indent_style = spaces +end_of_line = lf [*.yml] indent_size = 2 diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 6de9e5d0..00000000 --- a/.prettierrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "trailingComma": "none", - "tabWidth": 4, - "useTabs": false, - "semi": true, - "singleQuote": true, - "quoteProps": "consistent", - "bracketSpacing": true, - "arrowParens": "avoid", - "printWidth": 120, - "endOfLine": "lf", - "bracketSameLine": true -} diff --git a/daggerheart.d.ts b/daggerheart.d.ts index ab754b17..7ff7fd59 100644 --- a/daggerheart.d.ts +++ b/daggerheart.d.ts @@ -1,8 +1,11 @@ import '@client/global.mjs'; +import '@common/global.mjs'; +import '@common/primitives/global.mjs'; import Canvas from '@client/canvas/board.mjs'; // Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such // This declare global hopefully fixes that +// Note: eslint is not aware of these, whatever is added here should go in the eslint's globals list declare global { /** * A simple event framework used throughout Foundry Virtual Tabletop. @@ -12,9 +15,28 @@ declare global { class Hooks extends foundry.helpers.Hooks {} const fromUuid = foundry.utils.fromUuid; const fromUuidSync = foundry.utils.fromUuidSync; - + /** + * A representation of a color in hexadecimal format. + * This class provides methods for transformations and manipulations of colors. + */ + class Color extends foundry.utils.Color {} /** * The singleton game canvas */ const canvas: Canvas; + + const ActiveEffect: foundry.documents.ActiveEffect; + const Actor: foundry.documents.Actor; + const BaseScene: foundry.documents.BaseScene; + const ChatMessage: foundry.documents.ChatMessage; + const Combat: foundry.documents.Combat; + const Combatant: foundry.documents.Combatant; + const Item: foundry.documents.Item; + const Macro: foundry.documents.Macro; + const Scene: foundry.documents.Scene; + const TokenDocument: foundry.documents.TokenDocument; + + const Collection: foundry.utils.Collection; + const FormDataExtended: foundry.applications.ux.FormDataExtended; + const TextEditor: foundry.applications.ux.TextEditor; } diff --git a/daggerheart.mjs b/daggerheart.mjs index 25c41ced..7bfdf874 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -453,8 +453,8 @@ Hooks.on('renderDialogV2', (_dialog, html) => { const cls = html.classList.contains('item-create') ? documents.DHItem.implementation : html.classList.contains('actor-create') - ? documents.DhpActor.implementation - : null; + ? documents.DhpActor.implementation + : null; if (!cls) return; const form = html.querySelector('form'); diff --git a/eslint.config.mjs b/eslint.config.mjs index ce2bb86f..3c9b8fd9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,14 +1,101 @@ import globals from 'globals'; -import { defineConfig } from 'eslint/config'; -import prettier from 'eslint-plugin-prettier'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import tseslint from 'typescript-eslint'; +import js from '@eslint/js'; +import stylistic from '@stylistic/eslint-plugin'; + +/** @type {Partial} */ +export const stylisticRules = { + '@stylistic/indent': [ + 'error', + 4, + { + SwitchCase: 1 + } + ], + '@stylistic/max-len': ['error', { + code: 120, + ignoreComments: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true + }], + '@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }], + '@stylistic/arrow-parens': ['error', 'as-needed'], + '@stylistic/quote-props': ['error', 'as-needed'], + '@stylistic/array-bracket-newline': ['error', 'consistent'], + '@stylistic/key-spacing': 'error', + '@stylistic/comma-dangle': ['error', 'never'], + '@stylistic/space-in-parens': ['error', 'never'], + '@stylistic/space-infix-ops': 2, + '@stylistic/keyword-spacing': 2, + '@stylistic/semi-spacing': 2, + '@stylistic/no-multi-spaces': 2, + '@stylistic/no-extra-semi': 2, + '@stylistic/no-whitespace-before-property': 2, + '@stylistic/space-unary-ops': 2 +}; export default defineConfig([ - { files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: globals.browser } }, - { plugins: { prettier } }, + globalIgnores(['foundry/**/*', 'build/**/*']), + { + files: ['gulpfile.js', 'postcss.config.js'], + languageOptions: { globals: globals.node } + }, { files: ['**/*.{js,mjs,cjs}'], + plugins: { + '@stylistic': stylistic + }, + languageOptions: { + globals: { + ...globals.browser, + CONFIG: 'readonly', + CONST: 'readonly', + // Global classes + Color: 'readonly', + Handlebars: 'readonly', + Hooks: 'readonly', + PIXI: 'readonly', + ProseMirror: 'readonly', + Roll: 'readonly', + // global namespaces + canvas: 'readonly', + foundry: 'readonly', + game: 'readonly', + ui: 'readonly', + // global functions + fromUuid: 'readonly', + fromUuidSync: 'readonly', + getDocumentClass: 'readonly', + _del: 'readonly', + _replace: 'readonly', + _loc: 'readonly', + // Documents + ActiveEffect: 'readonly', + Actor: 'readonly', + BaseScene: 'readonly', + ChatMessage: 'readonly', + Combat: 'readonly', + Combatant: 'readonly', + Item: 'readonly', + Macro: 'readonly', + Scene: 'readonly', + TokenDocument: 'readonly', + // Other + Collection: 'readonly', + FormDataExtended: 'readonly', + TextEditor: 'readonly' + } + }, rules: { - 'prettier/prettier': 'error' + 'no-undef': 'error', + // 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + ...stylisticRules } + }, + { + files: ['**/*.ts'], + extends: [js.configs.recommended, tseslint.configs.recommended] } ]); diff --git a/jsconfig.json b/jsconfig.json index 00bab1f5..a0d51d0b 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "module": "ES6", - "target": "ES6", + "module": "es2022", + "target": "es2022", "paths": { "@client/*": ["./foundry/client/*"], "@common/*": ["./foundry/common/*"] diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index 82ca9ccb..517f95da 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -154,8 +154,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : this.tabGroups.primary !== 'equipment' - ? v.active - : false; + ? v.active + : false; v.cssClass = v.active ? 'active' : ''; switch (v.id) { @@ -211,9 +211,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl context.suggestedTraits = this.setup.class.system ? Object.keys(this.setup.class.system.characterGuide.suggestedTraits).map(traitKey => { - const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey]; - return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`; - }) + const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey]; + return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`; + }) : []; context.traits = { values: Object.keys(this.setup.traits).map(traitKey => { @@ -450,7 +450,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl if (equipment.includes(type)) presets.filter = { 'system.tier': { key: 'system.tier', value: 1 }, - 'type': { key: 'type', value: type } + type: { key: 'type', value: type } }; ui.compendiumBrowser.open(presets); diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 76b2e751..9a98b197 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -196,14 +196,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1 ? this.config.costs.filter(x => x.extKey !== button.dataset.key) : [ - ...this.config.costs, - { - extKey: button.dataset.key, - key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope', - value: 1, - name: this.config.data?.system.experiences?.[button.dataset.key]?.name - } - ]; + ...this.config.costs, + { + extKey: button.dataset.key, + key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope', + value: 1, + name: this.config.data?.system.experiences?.[button.dataset.key]?.name + } + ]; this.render(); } @@ -213,8 +213,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.config.actionType = this.reactionOverride ? 'reaction' : this.config.actionType === 'reaction' - ? 'action' - : this.config.actionType; + ? 'action' + : this.config.actionType; this.render(); } } diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index b916a5de..e5108e34 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -138,13 +138,13 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap const stressReductionStress = this.availableStressReductions ? stressReductions.reduce((acc, red) => acc + red.cost, 0) : 0; + const stress = this.actor.system.resources.stress; context.stress = selectedStressMarks.length > 0 || this.availableStressReductions ? { - value: - this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress, - max: this.actor.system.resources.stress.max - } + value: stress.value + selectedStressMarks.length + stressReductionStress, + max: stress.max + } : null; context.maxArmorUsed = maxArmorUsed; diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 367540bf..e209cc3b 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -259,10 +259,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV const resetValue = increasing ? 0 : feature.system.resource.max - ? new Roll( + ? new Roll( Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData()) ).evaluateSync().total - : 0; + : 0; await feature.update({ 'system.resource.value': resetValue }); } diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index dd504b4b..7196d848 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -167,8 +167,8 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat partContext.groupRoll = { totalLabel: leader?.rollData ? game.i18n.format('DAGGERHEART.GENERAL.withThing', { - thing: leader.roll.totalLabel - }) + thing: leader.roll.totalLabel + }) : null, totalDualityClass: leader?.roll?.isCritical ? 'critical' : leader?.roll?.withHope ? 'hope' : 'fear', total: leaderTotal + modifierTotal, diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 3dc6b0fc..b2ce0258 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -653,8 +653,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const baseSecondaryRoll = selectedRoll ? memberValues.find(x => !x.selected) : memberValues.length > 1 - ? memberValues[1] - : null; + ? memberValues[1] + : null; if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null; diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs index 671b01a1..4805cd9e 100644 --- a/module/applications/hud/tokenHUD.mjs +++ b/module/applications/hud/tokenHUD.mjs @@ -50,11 +50,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { ).showGenericStatusEffects; context.genericStatusEffects = useGeneric ? Object.keys(context.statusEffects).reduce((acc, key) => { - const effect = context.statusEffects[key]; - if (!effect.systemEffect) acc[key] = effect; + const effect = context.statusEffects[key]; + if (!effect.systemEffect) acc[key] = effect; - return acc; - }, {}) + return acc; + }, {}) : null; context.hasCompanion = this.actor.system.companion; @@ -68,11 +68,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { const warning = tokensWithoutActors.length === 1 ? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', { - name: tokensWithoutActors[0].name - }) + name: tokensWithoutActors[0].name + }) : game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', { - names: tokensWithoutActors.map(x => x.name).join(', ') - }); + names: tokensWithoutActors.map(x => x.name).join(', ') + }); const tokens = canvas.tokens.controlled .filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type)) @@ -174,8 +174,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { nonZeroIndex === sideMiddle ? 0 : nonZeroIndex < sideMiddle - ? -nonZeroIndex - : nonZeroIndex - sideMiddle; + ? -nonZeroIndex + : nonZeroIndex - sideMiddle; return { x: actorX - sizeX * distance, y: actorY - sizeY * distanceCoefficient }; } else if (index < side + inbetween) { const inbetweenIndex = nonZeroIndex - side; @@ -183,8 +183,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { inbetweenIndex === inbetweenMiddle ? 0 : inbetweenIndex < inbetweenMiddle - ? -inbetweenIndex - : inbetweenIndex - inbetweenMiddle; + ? -inbetweenIndex + : inbetweenIndex - inbetweenMiddle; return { x: actorX + sizeX * distanceCoefficient, y: actorY + sizeY * distance }; } else if (index < 2 * side + inbetween) { const sideIndex = nonZeroIndex - side - inbetween; @@ -192,8 +192,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { sideIndex === sideMiddle ? 0 : sideIndex < sideMiddle - ? sideIndex - : -(sideIndex - sideMiddle); + ? sideIndex + : -(sideIndex - sideMiddle); return { x: actorX + sizeX * distance, y: actorY + sizeY * distanceCoefficient }; } else { const inbetweenIndex = nonZeroIndex - 2 * side - inbetween; @@ -201,8 +201,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { inbetweenIndex === inbetweenMiddle ? 0 : inbetweenIndex < inbetweenMiddle - ? inbetweenIndex - : -(inbetweenIndex - inbetweenMiddle); + ? inbetweenIndex + : -(inbetweenIndex - inbetweenMiddle); return { x: actorX - sizeX * distanceCoefficient, y: actorY + sizeY * distance }; } }) diff --git a/module/applications/levelup/characterLevelup.mjs b/module/applications/levelup/characterLevelup.mjs index e8d6cf1c..a2df63c1 100644 --- a/module/applications/levelup/characterLevelup.mjs +++ b/module/applications/levelup/characterLevelup.mjs @@ -210,9 +210,9 @@ export default class DhCharacterLevelUp extends LevelUpBase { achievementExperiences = level.achievements.experiences ? Object.values(level.achievements.experiences).reduce((acc, experience) => { - if (experience.name) acc.push(experience); - return acc; - }, []) + if (experience.name) acc.push(experience); + return acc; + }, []) : []; } @@ -315,15 +315,15 @@ export default class DhCharacterLevelUp extends LevelUpBase { : null; advancement[choiceKey] = multiclassItem ? { - ...multiclassItem.toObject(), - domain: checkbox.secondaryData.domain - ? game.i18n.localize( - CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain] - .label - ) - : null, - subclass: subclass ? subclass.name : null - } + ...multiclassItem.toObject(), + domain: checkbox.secondaryData.domain + ? game.i18n.localize( + CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain] + .label + ) + : null, + subclass: subclass ? subclass.name : null + } : {}; break; } diff --git a/module/applications/levelup/companionLevelup.mjs b/module/applications/levelup/companionLevelup.mjs index d6bf2d78..92cf3050 100644 --- a/module/applications/levelup/companionLevelup.mjs +++ b/module/applications/levelup/companionLevelup.mjs @@ -77,9 +77,9 @@ export default class DhCompanionLevelUp extends BaseLevelUp { achievementExperiences = level.achievements.experiences ? Object.values(level.achievements.experiences).reduce((acc, experience) => { - if (experience.name) acc.push(experience); - return acc; - }, []) + if (experience.name) acc.push(experience); + return acc; + }, []) : []; } context.achievements = { @@ -155,15 +155,15 @@ export default class DhCompanionLevelUp extends BaseLevelUp { vicious: { damage: advancement.vicious?.damage ? { - old: actorDamageDice, - new: advancement.vicious.damage - } + old: actorDamageDice, + new: advancement.vicious.damage + } : null, range: advancement.vicious?.range ? { - old: game.i18n.localize(`DAGGERHEART.CONFIG.Range.${actorRange}.name`), - new: game.i18n.localize(advancement.vicious.range.label) - } + old: game.i18n.localize(`DAGGERHEART.CONFIG.Range.${actorRange}.name`), + new: game.i18n.localize(advancement.vicious.range.label) + } : null }, simple: advancement.simple ?? {} diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index 03638548..cafc5c89 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -135,192 +135,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections }; context.showTabs = this.tabGroups.primary !== 'summary'; break; - - const actorArmor = this.actor.system.armor; - const levelKeys = Object.keys(this.levelup.levels); - let achivementProficiency = 0; - const achievementCards = []; - let achievementExperiences = []; - for (var levelKey of levelKeys) { - const level = this.levelup.levels[levelKey]; - if (Number(levelKey) < this.levelup.startLevel) continue; - - achivementProficiency += level.achievements.proficiency ?? 0; - const cards = level.achievements.domainCards ? Object.values(level.achievements.domainCards) : null; - if (cards) { - for (var card of cards) { - const itemCard = await foundry.utils.fromUuid(card.uuid); - achievementCards.push(itemCard); - } - } - - achievementExperiences = level.achievements.experiences - ? Object.values(level.achievements.experiences).reduce((acc, experience) => { - if (experience.name) acc.push(experience); - return acc; - }, []) - : []; - } - - context.achievements = { - proficiency: { - old: this.actor.system.proficiency, - new: this.actor.system.proficiency + achivementProficiency, - shown: achivementProficiency > 0 - }, - damageThresholds: { - major: { - old: this.actor.system.damageThresholds.major, - new: this.actor.system.damageThresholds.major + changedActorLevel - currentActorLevel - }, - severe: { - old: this.actor.system.damageThresholds.severe, - new: - this.actor.system.damageThresholds.severe + - (actorArmor - ? changedActorLevel - currentActorLevel - : (changedActorLevel - currentActorLevel) * 2) - }, - unarmored: !actorArmor - }, - domainCards: { - values: achievementCards, - shown: achievementCards.length > 0 - }, - experiences: { - values: achievementExperiences - } - }; - - const advancement = {}; - for (var levelKey of levelKeys) { - const level = this.levelup.levels[levelKey]; - if (Number(levelKey) < this.levelup.startLevel) continue; - - for (var choiceKey of Object.keys(level.choices)) { - const choice = level.choices[choiceKey]; - for (var checkbox of Object.values(choice)) { - switch (choiceKey) { - case 'proficiency': - case 'hitPoint': - case 'stress': - case 'evasion': - advancement[choiceKey] = advancement[choiceKey] - ? advancement[choiceKey] + Number(checkbox.value) - : Number(checkbox.value); - break; - case 'trait': - if (!advancement[choiceKey]) advancement[choiceKey] = {}; - for (var traitKey of checkbox.data) { - if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0; - advancement[choiceKey][traitKey] += 1; - } - break; - case 'domainCard': - if (!advancement[choiceKey]) advancement[choiceKey] = []; - if (checkbox.data.length === 1) { - const choiceItem = await foundry.utils.fromUuid(checkbox.data[0]); - advancement[choiceKey].push(choiceItem.toObject()); - } - break; - case 'experience': - if (!advancement[choiceKey]) advancement[choiceKey] = []; - const data = checkbox.data.map(data => { - const experience = Object.keys(this.actor.system.experiences).find( - x => x === data - ); - return this.actor.system.experiences[experience]?.description ?? ''; - }); - advancement[choiceKey].push({ data: data, value: checkbox.value }); - break; - case 'subclass': - if (checkbox.data[0]) { - const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]); - if (!advancement[choiceKey]) advancement[choiceKey] = []; - advancement[choiceKey].push({ - ...subclassItem.toObject(), - featureLabel: game.i18n.localize( - subclassFeatureLabels[Number(checkbox.secondaryData.featureState)] - ) - }); - } - break; - case 'multiclass': - const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]); - const subclass = multiclassItem - ? await foundry.utils.fromUuid(checkbox.secondaryData.subclass) - : null; - advancement[choiceKey] = multiclassItem - ? { - ...multiclassItem.toObject(), - domain: checkbox.secondaryData.domain - ? game.i18n.localize( - CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain] - .label - ) - : null, - subclass: subclass ? subclass.name : null - } - : {}; - break; - } - } - } - } - - context.advancements = { - statistics: { - proficiency: { - old: context.achievements.proficiency.new, - new: context.achievements.proficiency.new + (advancement.proficiency ?? 0) - }, - hitPoints: { - old: this.actor.system.resources.hitPoints.max, - new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0) - }, - stress: { - old: this.actor.system.resources.stress.max, - new: this.actor.system.resources.stress.max + (advancement.stress ?? 0) - }, - evasion: { - old: this.actor.system.evasion, - new: this.actor.system.evasion + (advancement.evasion ?? 0) - } - }, - traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => { - if (advancement.trait?.[traitKey]) { - if (!acc) acc = {}; - acc[traitKey] = { - label: game.i18n.localize(abilities[traitKey].label), - old: this.actor.system.traits[traitKey].value, - new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey] - }; - } - return acc; - }, null), - domainCards: advancement.domainCard ?? [], - experiences: - advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ?? - [], - multiclass: advancement.multiclass, - subclass: advancement.subclass - }; - - context.advancements.statistics.proficiency.shown = - context.advancements.statistics.proficiency.new > context.advancements.statistics.proficiency.old; - context.advancements.statistics.hitPoints.shown = - context.advancements.statistics.hitPoints.new > context.advancements.statistics.hitPoints.old; - context.advancements.statistics.stress.shown = - context.advancements.statistics.stress.new > context.advancements.statistics.stress.old; - context.advancements.statistics.evasion.shown = - context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old; - context.advancements.statistics.shown = - context.advancements.statistics.proficiency.shown || - context.advancements.statistics.hitPoints.shown || - context.advancements.statistics.stress.shown || - context.advancements.statistics.evasion.shown; - - break; } return context; @@ -384,37 +198,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) this._dragDrop.forEach(d => d.bind(htmlElement)); } - tagifyUpdate = - type => - async (_, { option, removed }) => { - const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce( - (acc, choiceKey) => { - const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey]; - Object.keys(choice).forEach(checkboxNr => { - const checkbox = choice[checkboxNr]; - if ( - choiceKey === type && - (removed ? checkbox.data.includes(option) : checkbox.data.length < checkbox.amount) - ) { - acc = `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}.data`; - } - }); + tagifyUpdate = type => async (_, { option, removed }) => { + const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce( + (acc, choiceKey) => { + const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey]; + Object.keys(choice).forEach(checkboxNr => { + const checkbox = choice[checkboxNr]; + if ( + choiceKey === type && + (removed ? checkbox.data.includes(option) : checkbox.data.length < checkbox.amount) + ) { + acc = `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}.data`; + } + }); - return acc; - }, - null - ); + return acc; + }, + null + ); - if (!updatePath) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectionsLeft')); - return; - } + if (!updatePath) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectionsLeft')); + return; + } - const currentData = foundry.utils.getProperty(this.levelup, updatePath); - const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option]; - await this.levelup.updateSource({ [updatePath]: updatedData }); - this.render(); - }; + const currentData = foundry.utils.getProperty(this.levelup, updatePath); + const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option]; + await this.levelup.updateSource({ [updatePath]: updatedData }); + this.render(); + }; static async updateForm(event, _, formData) { const { levelup } = foundry.utils.expandObject(formData.object); @@ -593,10 +405,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const domainCards = this.levelup.levels[this.levelup.currentLevel].achievements.domainCards; const illegalDomainCards = option.secondaryData.domain ? Object.keys(domainCards) - .map(key => ({ ...domainCards[key], key })) - .filter( - x => x.uuid && foundry.utils.fromUuidSync(x.uuid).system.domain === option.secondaryData.domain - ) + .map(key => ({ ...domainCards[key], key })) + .filter( + x => x.uuid && foundry.utils.fromUuidSync(x.uuid).system.domain === option.secondaryData.domain + ) : []; illegalDomainCards.forEach(card => { update[`levels.${this.levelup.currentLevel}.achievements.domainCards.${card.key}.uuid`] = null; diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 09bb00f2..c4dfc397 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -251,8 +251,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli const configTitle = isDowntime ? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMove') : type === 'armorFeatures' - ? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.armorFeature') - : game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.weaponFeature'); + ? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.armorFeature') + : game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.weaponFeature'); const editedBase = await game.system.api.applications.sheetConfigs.SettingFeatureConfig.configure( configTitle, diff --git a/module/applications/sheets-configs/setting-feature-config.mjs b/module/applications/sheets-configs/setting-feature-config.mjs index a5bcc4f9..531158cd 100644 --- a/module/applications/sheets-configs/setting-feature-config.mjs +++ b/module/applications/sheets-configs/setting-feature-config.mjs @@ -168,8 +168,8 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App updatedEffects = deleteEffect ? currentEffects.filter(x => x.id !== effectData.id) : existingEffectIndex === -1 - ? [...currentEffects, effectData] - : currentEffects.with(existingEffectIndex, effectData); + ? [...currentEffects, effectData] + : currentEffects.with(existingEffectIndex, effectData); await this.updateMove({ [`${this.movePath}.effects`]: updatedEffects }); @@ -235,9 +235,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App return this.hasEffects ? tabs : Object.keys(tabs).reduce((acc, key) => { - if (key !== 'effects') acc[key] = tabs[key]; - return acc; - }, {}); + if (key !== 'effects') acc[key] = tabs[key]; + return acc; + }, {}); } /** @override */ diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 5d0e7144..bc2cdb41 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -785,11 +785,11 @@ export default class CharacterSheet extends DHBaseActorSheet { filter: key === 'subclasses' ? { - 'system.linkedClass.uuid': { - key: 'system.linkedClass.uuid', - value: this.document.system.class.value?._stats.compendiumSource - } - } + 'system.linkedClass.uuid': { + key: 'system.linkedClass.uuid', + value: this.document.system.class.value?._stats.compendiumSource + } + } : undefined, render: { noFolder: true diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 927a8810..3af8ea5f 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -162,9 +162,9 @@ export default class Party extends DHBaseActorSheet { difficulty: actor.system.difficulty, traits: actor.system.traits ? traits.map(t => ({ - label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`), - value: actor.system.traits[t].value - })) + label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`), + value: actor.system.traits[t].value + })) : null, weapons }); @@ -306,7 +306,7 @@ export default class Party extends DHBaseActorSheet { static async downtimeMoveQuery({ actorId, downtimeType }) { const actor = await foundry.utils.fromUuid(actorId); - if (!actor || !actor?.isOwner) reject(); + if (!actor || !actor?.isOwner) return; new game.system.api.applications.dialogs.Downtime(actor, downtimeType === 'shortRest').render({ force: true }); diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 2b0c3e55..63bbb536 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -722,10 +722,10 @@ export default function DHApplicationMixin(Base) { const parent = featureOnCharacter ? this.document.parent : parentIsItem && documentClass === 'Item' - ? type === 'action' - ? this.document.system - : null - : this.document; + ? type === 'action' + ? this.document.system + : null + : this.document; let systemData = {}; if (featureOnCharacter) { diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 7b820822..812ad311 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -377,7 +377,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { action: 'update', documentName: 'Item', parent: targetActor, - updates: [{ '_id': existing.id, 'system.quantity': existing.system.quantity + quantity }] + updates: [{ _id: existing.id, 'system.quantity': existing.system.quantity + quantity }] }); } else { const itemsToCreate = []; @@ -410,7 +410,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { action: 'update', documentName: 'Item', parent: originActor, - updates: [{ '_id': item.id, 'system.quantity': item.system.quantity - quantity }] + updates: [{ _id: item.id, 'system.quantity': item.system.quantity - quantity }] }); } diff --git a/module/applications/sheets/api/item-attachment-sheet.mjs b/module/applications/sheets/api/item-attachment-sheet.mjs index bcf2fc3c..ea4d5352 100644 --- a/module/applications/sheets/api/item-attachment-sheet.mjs +++ b/module/applications/sheets/api/item-attachment-sheet.mjs @@ -29,16 +29,6 @@ export default function ItemAttachmentSheet(Base) { } }; - async _preparePartContext(partId, context) { - await super._preparePartContext(partId, context); - - if (partId === 'attachments') { - context.attachedItems = await prepareAttachmentContext(this.document); - } - - return context; - } - async _onDrop(event) { const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); diff --git a/module/applications/sidebar/tabs/actorDirectory.mjs b/module/applications/sidebar/tabs/actorDirectory.mjs index 89da1426..a6f48b54 100644 --- a/module/applications/sidebar/tabs/actorDirectory.mjs +++ b/module/applications/sidebar/tabs/actorDirectory.mjs @@ -13,8 +13,8 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs. return document.type === 'adversary' ? game.i18n.localize(adversaryTypes[document.system.type]?.label ?? 'TYPES.Actor.adversary') : document.type === 'environment' - ? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment') - : null; + ? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment') + : null; }; } diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 7036a5df..199ee87d 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -41,8 +41,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const advantage = rollCommand.advantage ? CONFIG.DH.ACTIONS.advantageState.advantage.value : rollCommand.disadvantage - ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value - : undefined; + ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value + : undefined; const difficulty = rollCommand.difficulty; const grantResources = rollCommand.grantResources; @@ -50,8 +50,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const title = (flavor ?? traitValue) ? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label) - }) + ability: game.i18n.localize(CONFIG.DH.ACTOR.abilities[traitValue].label) + }) : game.i18n.localize('DAGGERHEART.GENERAL.duality'); enrichedDualityRoll({ diff --git a/module/applications/ui/countdownEdit.mjs b/module/applications/ui/countdownEdit.mjs index b418107c..5974e1f2 100644 --- a/module/applications/ui/countdownEdit.mjs +++ b/module/applications/ui/countdownEdit.mjs @@ -56,8 +56,8 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio ? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id ? 'DAGGERHEART.UI.Countdowns.increasingLoop' : countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id - ? 'DAGGERHEART.UI.Countdowns.decreasingLoop' - : 'DAGGERHEART.UI.Countdowns.loop' + ? 'DAGGERHEART.UI.Countdowns.decreasingLoop' + : 'DAGGERHEART.UI.Countdowns.loop' : null; const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic; acc[key] = { @@ -148,11 +148,11 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio } async gmSetSetting(data) { - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data), - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.Countdown } - }); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.Countdown } + }); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); } diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 6fa05e29..2a2a113e 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -101,8 +101,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application ? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id ? 'DAGGERHEART.UI.Countdowns.increasingLoop' : countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id - ? 'DAGGERHEART.UI.Countdowns.decreasingLoop' - : 'DAGGERHEART.UI.Countdowns.loop' + ? 'DAGGERHEART.UI.Countdowns.decreasingLoop' + : 'DAGGERHEART.UI.Countdowns.loop' : null; const loopDisabled = !countdownEditable || @@ -181,8 +181,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id ? Number(progressMax) + 1 : countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id - ? Math.max(Number(progressMax) - 1, 0) - : progressMax; + ? Math.max(Number(progressMax) - 1, 0) + : progressMax; await waitForDiceSoNice(message); await settings.updateSource({ @@ -212,11 +212,11 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application } static async gmSetSetting(data) { - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data), - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.Countdown } - }); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.Countdown } + }); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); } diff --git a/module/applications/ui/effectsDisplay.mjs b/module/applications/ui/effectsDisplay.mjs index 035041e1..a64b1b22 100644 --- a/module/applications/ui/effectsDisplay.mjs +++ b/module/applications/ui/effectsDisplay.mjs @@ -67,10 +67,10 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica const actor = token ? token.actor : canvas.tokens.controlled.length === 0 - ? !game.user.isGM - ? game.user.character - : null - : canvas.tokens.controlled[0].actor; + ? !game.user.isGM + ? game.user.character + : null + : canvas.tokens.controlled[0].actor; return getIconVisibleActiveEffects(actor?.getActiveEffects() ?? []); }; diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 68e325c2..02eed7db 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -155,15 +155,15 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint); const adjustedOriginPoint = originEdge ? canvas.grid.getTopLeftPoint({ - x: originEdge.x + Math.sign(originPoint.x - originEdge.x), - y: originEdge.y + Math.sign(originPoint.y - originEdge.y) - }) + x: originEdge.x + Math.sign(originPoint.x - originEdge.x), + y: originEdge.y + Math.sign(originPoint.y - originEdge.y) + }) : originPoint; const adjustDestinationPoint = targetEdge ? canvas.grid.getTopLeftPoint({ - x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x), - y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y) - }) + x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x), + y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y) + }) : targetPoint; const distance = canvas.grid.measurePath([ { ...adjustedOriginPoint, elevation: 0 }, diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index ae5fa71b..83572dc0 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -127,8 +127,8 @@ export const typeConfig = { isSecondary ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false - ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' - : '-' + ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' + : '-' }, { key: 'system.tier', diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index 1f7e1c92..1988b1d8 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -79,8 +79,8 @@ export default class DHAttackAction extends DHDamageAction { const str = damageString ? damageString : game.i18n.format('DAGGERHEART.GENERAL.missingX', { - x: game.i18n.localize('DAGGERHEART.GENERAL.damage') - }); + x: game.i18n.localize('DAGGERHEART.GENERAL.damage') + }); const icons = Array.from(type) .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index acd104a7..c71f5ef9 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -144,8 +144,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return this.item instanceof DhpActor ? this.item : this.item?.parent instanceof DhpActor - ? this.item.parent - : null; + ? this.item.parent + : null; } /** @@ -223,7 +223,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * @returns {object} */ async use(event, configOptions = {}) { - if (!this.actor) throw new Error("An Action can't be used outside of an Actor context."); + if (!this.actor) throw new Error('An Action can\'t be used outside of an Actor context.'); let config = this.prepareConfig(event, configOptions); if (!config) return; @@ -300,17 +300,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel const groupAttackTokens = this.damage.groupAttack ? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens( - this.actor.id, - this.damage.groupAttack - ) + this.actor.id, + this.damage.groupAttack + ) : null; config.damageOptions = { groupAttack: this.damage.groupAttack ? { - numAttackers: Math.max(groupAttackTokens.length, 1), - range: this.damage.groupAttack - } + numAttackers: Math.max(groupAttackTokens.length, 1), + range: this.damage.groupAttack + } : null }; } diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 0fbea122..7e037f5b 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -90,13 +90,13 @@ export default class BeastformEffect extends BaseEffect { ...baseUpdate, x, y, - 'texture': { + texture: { enabled: this.characterTokenData.usesDynamicToken, src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg, scaleX: this.characterTokenData.tokenSize.scale, scaleY: this.characterTokenData.tokenSize.scale }, - 'ring': { + ring: { subject: { texture: token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs index 217ff9dd..0c226513 100644 --- a/module/data/activeEffect/changeTypes/armor.mjs +++ b/module/data/activeEffect/changeTypes/armor.mjs @@ -166,10 +166,10 @@ export default class ArmorChange extends foundry.abstract.DataModel { value: change.type === 'armor' ? { - ...change.value, - current: Math.min(change.value.current, newMax), - max: newMax - } + ...change.value, + current: Math.min(change.value.current, newMax), + max: newMax + } : change.value })) ]; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 10d53c13..aed27650 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -315,8 +315,8 @@ export default class DhCharacter extends DhCreature { return currentLevel === 1 ? 1 : Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find( - tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end - ).tier; + tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end + ).tier; } get ancestry() { @@ -520,20 +520,20 @@ export default class DhCharacter extends DhCreature { if (armorSource.type === 'armor') { armorUpdates[armorSource.parent.id].updates.push({ - '_id': armorSource.id, + _id: armorSource.id, 'system.armor.current': armorSource.system.armor.current + usedArmorChange }); } else { effectUpdates[armorSource.parent.id].updates.push({ - '_id': armorSource.id, + _id: armorSource.id, 'system.changes': armorSource.system.changes.map(change => ({ ...change, value: change.type === 'armor' ? { - ...change.value, - current: armorSource.system.armorChange.value.current + usedArmorChange - } + ...change.value, + current: armorSource.system.armorChange.value.current + usedArmorChange + } : change.value })) }); @@ -621,21 +621,21 @@ export default class DhCharacter extends DhCreature { }, ...(multiclassFeatures.length ? { - multiclassFeatures: { - title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`, - type: 'multiclass', - values: multiclassFeatures - } - } + 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 - } - } + 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'), @@ -659,8 +659,8 @@ export default class DhCharacter extends DhCreature { (this.primaryWeapon && this.secondaryWeapon) ? burden.twoHanded.value : this.primaryWeapon || this.secondaryWeapon - ? burden.oneHanded.value - : null; + ? burden.oneHanded.value + : null; } get deathMoveViable() { @@ -726,8 +726,8 @@ export default class DhCharacter extends DhCreature { currentLevel === 1 ? null : Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find( - tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end - ).tier; + tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end + ).tier; if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto) { for (let levelKey in this.levelData.levelups) { const level = this.levelData.levelups[levelKey]; diff --git a/module/data/companionLevelup.mjs b/module/data/companionLevelup.mjs index 7ab61210..e24820de 100644 --- a/module/data/companionLevelup.mjs +++ b/module/data/companionLevelup.mjs @@ -22,12 +22,12 @@ export class DhCompanionLevelup extends foundry.abstract.DataModel { const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {}; const experiences = initialAchievements.experience ? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => { - acc[foundry.utils.randomID()] = { - name: '', - modifier: initialAchievements.experience.modifier - }; - return acc; - }, {}) + acc[foundry.utils.randomID()] = { + name: '', + modifier: initialAchievements.experience.modifier + }; + return acc; + }, {}) : {}; const currentChoices = pcLevelData.levelups[i]?.selections?.length; @@ -302,9 +302,9 @@ export class DhLevelupLevel extends foundry.abstract.DataModel { experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {}, domainCards: levelData.achievements?.domainCards ? levelData.achievements.domainCards.reduce((acc, card, index) => { - acc[index] = { ...card }; - return acc; - }, {}) + acc[index] = { ...card }; + return acc; + }, {}) : (achievements.domainCards ?? {}), proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null }, diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 7d27197d..54971d34 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -77,11 +77,11 @@ export class DhCountdown extends foundry.abstract.DataModel { static defaultCountdown(type, playerHidden) { const ownership = playerHidden ? game.users.reduce((acc, user) => { - if (!user.isGM) { - acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE; - } - return acc; - }, {}) + if (!user.isGM) { + acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE; + } + return acc; + }, {}) : undefined; return { @@ -102,8 +102,8 @@ export class DhCountdown extends foundry.abstract.DataModel { value: user.isGM ? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER : this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1 - ? this.ownership.players[user.id].type - : this.ownership.default, + ? this.ownership.players[user.id].type + : this.ownership.default, isGM: user.isGM }; diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs index e3be9937..0eeb95c2 100644 --- a/module/data/fields/action/beastformField.mjs +++ b/module/data/fields/action/beastformField.mjs @@ -105,8 +105,8 @@ export default class BeastformField extends fields.SchemaField { baseSize === 'custom' ? 'custom' : (Object.keys(CONFIG.DH.ACTOR.tokenSize).find( - x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1 - ) ?? baseSize); + x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1 + ) ?? baseSize); formData.system.tokenSize = { ...evolvedData.form.system.tokenSize, size: evolvedSize diff --git a/module/data/fields/action/costField.mjs b/module/data/fields/action/costField.mjs index 1928af41..82cfcd23 100644 --- a/module/data/fields/action/costField.mjs +++ b/module/data/fields/action/costField.mjs @@ -116,8 +116,8 @@ export default class CostField extends fields.ArrayField { c.key === 'fear' ? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) : resources[c.key].isReversed - ? resources[c.key].max - resources[c.key].value - : resources[c.key].value; + ? resources[c.key].max - resources[c.key].value + : resources[c.key].value; if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step); return c; }); @@ -149,8 +149,8 @@ export default class CostField extends fields.ArrayField { !resources[c.key] ? a : a && resources[c.key].isReversed - ? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max - : resources[c.key]?.value >= (c.total ?? c.value), + ? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max + : resources[c.key]?.value >= (c.total ?? c.value), true ); } diff --git a/module/data/fields/action/countdownField.mjs b/module/data/fields/action/countdownField.mjs index 990f8ef1..96d9dd91 100644 --- a/module/data/fields/action/countdownField.mjs +++ b/module/data/fields/action/countdownField.mjs @@ -87,11 +87,11 @@ export default class CountdownField extends fields.ArrayField { CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting.toObject() - ), - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.Countdown } - }); + ); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.Countdown } + }); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); }, data, diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index d2ee1682..e943d63d 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -61,11 +61,11 @@ export default class EffectsField extends fields.ArrayField { token: messageToken, conditionImmunities: Object.values(conditionImmunities).some(x => x) ? game.i18n.format('DAGGERHEART.UI.Chat.effectSummary.immunityTo', { - immunities: Object.keys(conditionImmunities) - .filter(x => conditionImmunities[x]) - .map(x => game.i18n.localize(conditions[x].name)) - .join(', ') - }) + immunities: Object.keys(conditionImmunities) + .filter(x => conditionImmunities[x]) + .map(x => game.i18n.localize(conditions[x].name)) + .join(', ') + }) : null }); diff --git a/module/data/fields/action/saveField.mjs b/module/data/fields/action/saveField.mjs index 0629353e..7343ab85 100644 --- a/module/data/fields/action/saveField.mjs +++ b/module/data/fields/action/saveField.mjs @@ -69,11 +69,11 @@ export default class SaveField extends fields.SchemaField { game.user === actor.owner ? SaveField.rollSave.call(this, actor, event) : actor.owner.query('reactionRoll', { - actionId: this.uuid, - actorId: actor.uuid, - event, - message - }); + actionId: this.uuid, + actorId: actor.uuid, + event, + message + }); const result = await rollSave; await SaveField.updateSaveMessage.call(this, result, message, target.id); subResolve(); @@ -97,8 +97,8 @@ export default class SaveField extends fields.SchemaField { const title = actor.isNPC ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') : game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: game.i18n.localize(abilities[this.save.trait]?.label) - }), + ability: game.i18n.localize(abilities[this.save.trait]?.label) + }), rollConfig = { event, title, diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index ee9d9839..ba274cc7 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -208,8 +208,8 @@ export default class DHBeastform extends BaseDataItem { const autoTokenSize = this.tokenSize.size !== 'custom' ? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes[ - this.tokenSize.size - ] + this.tokenSize.size + ] : null; const width = autoTokenSize ?? this.tokenSize.width; const height = autoTokenSize ?? this.tokenSize.height; diff --git a/module/data/levelup.mjs b/module/data/levelup.mjs index 4dc1c058..e30bf52d 100644 --- a/module/data/levelup.mjs +++ b/module/data/levelup.mjs @@ -19,12 +19,12 @@ export class DhLevelup extends foundry.abstract.DataModel { const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {}; const experiences = initialAchievements.experience ? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => { - acc[foundry.utils.randomID()] = { - name: '', - modifier: initialAchievements.experience.modifier - }; - return acc; - }, {}) + acc[foundry.utils.randomID()] = { + name: '', + modifier: initialAchievements.experience.modifier + }; + return acc; + }, {}) : {}; const domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => { @@ -298,9 +298,9 @@ export class DhLevelupLevel extends foundry.abstract.DataModel { experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {}, domainCards: levelData.achievements?.domainCards ? levelData.achievements.domainCards.reduce((acc, card, index) => { - acc[index] = { ...card }; - return acc; - }, {}) + acc[index] = { ...card }; + return acc; + }, {}) : (achievements.domainCards ?? {}), proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null }, diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index fb20870f..c28db98f 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -104,9 +104,9 @@ export default class DHRoll extends Roll { if (action?.chatDisplay) { actionDescription = action ? await foundry.applications.ux.TextEditor.implementation.enrichHTML(action.description, { - relativeTo: config.data, - rollData: config.data.getRollData?.() ?? {} - }) + relativeTo: config.data, + rollData: config.data.getRollData?.() ?? {} + }) : null; config.actionChatMessageHandled = true; } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 1d2d556a..1cfed094 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -109,10 +109,10 @@ export default class DualityRoll extends D20Roll { const label = this.guaranteedCritical ? 'DAGGERHEART.GENERAL.guaranteedCriticalSuccess' : this.isCritical - ? 'DAGGERHEART.GENERAL.criticalSuccess' - : this.withHope - ? 'DAGGERHEART.GENERAL.hope' - : 'DAGGERHEART.GENERAL.fear'; + ? 'DAGGERHEART.GENERAL.criticalSuccess' + : this.withHope + ? 'DAGGERHEART.GENERAL.hope' + : 'DAGGERHEART.GENERAL.fear'; return game.i18n.localize(label); } @@ -147,8 +147,8 @@ export default class DualityRoll extends D20Roll { const advDieClass = this.hasAdvantage ? game.system.api.dice.diceTypes.AdvantageDie : this.hasDisadvantage - ? game.system.api.dice.diceTypes.DisadvantageDie - : null; + ? game.system.api.dice.diceTypes.DisadvantageDie + : null; if (advDieClass) { const advDie = new advDieClass({ faces: this.advantageFaces, number: this.advantageNumber }); if (this.terms.length < 4) { diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index f9239a90..3518210b 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -175,8 +175,8 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { return model instanceof documentClass ? model : model.parent - ? this.#resolveParentDocument(model.parent, documentClass) - : null; + ? this.#resolveParentDocument(model.parent, documentClass) + : null; } static getChangeValue(model, change, effect) { diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 893e6e5c..480f8c69 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -9,9 +9,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { actor && this.isContentVisible ? actor : { - img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg', - name: '' - }; + img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg', + name: '' + }; /* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */ const html = await super.renderHTML({ actor: actorData, author: this.author }); @@ -290,14 +290,14 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { behaviors: effects.length > 0 ? [ - { - name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'), - type: 'applyActiveEffect', - system: { - effects: effects - } - } - ] + { + name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'), + type: 'applyActiveEffect', + system: { + effects: effects + } + } + ] : [], displayMeasurements: true, locked: false, diff --git a/module/documents/item.mjs b/module/documents/item.mjs index f46e24e6..4716068d 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -98,8 +98,8 @@ export default class DHItem extends foundry.documents.Item { isInventoryItem === true ? 'Inventory Items' //TODO localize : isInventoryItem === false - ? 'Character Items' //TODO localize - : 'Other'; //TODO localize + ? 'Character Items' //TODO localize + : 'Other'; //TODO localize return { value: type, label, group }; } diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 30862724..8e91d4f0 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -324,7 +324,7 @@ export default class DHToken extends CONFIG.Token.documentClass { } let x = 0.5 * bottom; let y = 0.25; - for (let k = width - bottom; k--; ) { + for (let k = width - bottom; k--;) { points.push(x, y); x += 0.5; y -= 0.25; @@ -333,7 +333,7 @@ export default class DHToken extends CONFIG.Token.documentClass { y += 0.25; } points.push(x, y); - for (let k = bottom; k--; ) { + for (let k = bottom; k--;) { y += 0.5; points.push(x, y); x += 0.5; @@ -341,14 +341,14 @@ export default class DHToken extends CONFIG.Token.documentClass { points.push(x, y); } y += 0.5; - for (let k = top; k--; ) { + for (let k = top; k--;) { points.push(x, y); x -= 0.5; y += 0.25; points.push(x, y); y += 0.5; } - for (let k = width - top; k--; ) { + for (let k = width - top; k--;) { points.push(x, y); x -= 0.5; y += 0.25; @@ -357,7 +357,7 @@ export default class DHToken extends CONFIG.Token.documentClass { y -= 0.25; } points.push(x, y); - for (let k = top; k--; ) { + for (let k = top; k--;) { y -= 0.5; points.push(x, y); x -= 0.5; @@ -365,7 +365,7 @@ export default class DHToken extends CONFIG.Token.documentClass { points.push(x, y); } y -= 0.5; - for (let k = bottom; k--; ) { + for (let k = bottom; k--;) { points.push(x, y); x += 0.5; y -= 0.25; diff --git a/module/enrichers/DualityRollEnricher.mjs b/module/enrichers/DualityRollEnricher.mjs index 5b66179f..a7db01a4 100644 --- a/module/enrichers/DualityRollEnricher.mjs +++ b/module/enrichers/DualityRollEnricher.mjs @@ -15,8 +15,8 @@ function getDualityMessage(roll, flavor) { (roll?.trait ? game.i18n.format('DAGGERHEART.GENERAL.rollWith', { roll: trait }) : roll?.reaction - ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') - : game.i18n.localize('DAGGERHEART.GENERAL.duality')); + ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') + : game.i18n.localize('DAGGERHEART.GENERAL.duality')); const dataLabel = trait ? game.i18n.localize(abilities[roll.trait].label) @@ -25,14 +25,14 @@ function getDualityMessage(roll, flavor) { const advantage = roll?.advantage ? CONFIG.DH.ACTIONS.advantageState.advantage.value : roll?.disadvantage - ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value - : undefined; + ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value + : undefined; const advantageLabel = advantage === CONFIG.DH.ACTIONS.advantageState.advantage.value ? 'Advantage' : advantage === CONFIG.DH.ACTIONS.advantageState.disadvantage.value - ? 'Disadvantage' - : undefined; + ? 'Disadvantage' + : undefined; const dualityElement = document.createElement('span'); dualityElement.innerHTML = ` diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index 8db3ec14..bbe93b17 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -8,8 +8,8 @@ export default function DhTemplateEnricher(match, _options) { const range = params.range && Number.isNaN(Number(params.range)) ? Object.values(CONFIG.DH.GENERAL.templateRanges).find( - x => x.id.toLowerCase() === params.range || x.short === params.range - )?.id + x => x.id.toLowerCase() === params.range || x.short === params.range + )?.id : params.range; if (!CONFIG.DH.GENERAL.templateTypes[type] || !range) return match[0]; diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index ddc353b1..af6c2777 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -108,9 +108,9 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {} const options = Array.isArray(baseOptions) ? baseOptions : Object.keys(baseOptions).map(optionKey => ({ - ...baseOptions[optionKey], - id: optionKey - })); + ...baseOptions[optionKey], + id: optionKey + })); const tagifyElement = new Tagify(element, { tagTextProp: 'name', @@ -605,8 +605,8 @@ export function calculateExpectedValue(formulaOrTerms) { const terms = Array.isArray(formulaOrTerms) ? formulaOrTerms : typeof formulaOrTerms === 'string' - ? parseTermsFromSimpleFormula(formulaOrTerms) - : [formulaOrTerms]; + ? parseTermsFromSimpleFormula(formulaOrTerms) + : [formulaOrTerms]; return terms.reduce((r, t) => r + (t.bonus ?? 0) + (t.diceQuantity ? (t.diceQuantity * (t.faces + 1)) / 2 : 0), 0); } @@ -656,8 +656,8 @@ export async function RefreshFeatures( 'resource.value': increasing ? 0 : game.system.api.documents.DhActiveEffect.effectSafeEval( - Roll.replaceFormulaData(item.system.resource.max, actor.getRollData()) - ) + Roll.replaceFormulaData(item.system.resource.max, actor.getRollData()) + ) }; } if (item.system.metadata?.hasActions) { diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index b4c446b2..b718a127 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -24,8 +24,8 @@ export async function runMigrations() { const { originItemType, isMulticlass, identifier } = item.system; const base = originItemType ? actor.items.find( - x => x.type === originItemType && Boolean(isMulticlass) === Boolean(x.system.isMulticlass) - ) + x => x.type === originItemType && Boolean(isMulticlass) === Boolean(x.system.isMulticlass) + ) : null; if (base) { const feature = base.system.features.find(x => x.item && x.item.uuid === item.uuid); diff --git a/package-lock.json b/package-lock.json index 28223032..dee096eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,18 +13,20 @@ "rollup": "^4.40.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@foundryvtt/foundryvtt-cli": "^1.0.2", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", + "@stylistic/eslint-plugin": "^5.10.0", "concurrently": "^8.2.2", "eslint": "^10.2.1", - "eslint-plugin-prettier": "^5.5.5", "globals": "^17.5.0", "husky": "^9.1.7", "lint-staged": "^16.4.0", "postcss": "^8.4.32", - "prettier": "^3.5.3", - "rollup-plugin-postcss": "^4.0.2" + "rollup-plugin-postcss": "^4.0.2", + "typescript": "^6.0.3", + "typescript-eslint": "^8.60.1" } }, "node_modules/@babel/runtime": { @@ -158,6 +160,27 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, "node_modules/@eslint/object-schema": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", @@ -420,19 +443,6 @@ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, "node_modules/@rollup/plugin-commonjs": { "version": "25.0.8", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", @@ -761,6 +771,58 @@ "util": "^0.12.4" } }, + "node_modules/@stylistic/eslint-plugin": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/types": "^8.56.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.0.0 || ^10.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -795,6 +857,288 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@yaireo/tagify": { "version": "4.35.1", "resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.1.tgz", @@ -1853,10 +2197,11 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2241,37 +2586,6 @@ } } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", - "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.1", - "synckit": "^0.11.12" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, "node_modules/eslint-scope": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", @@ -2511,13 +2825,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -2554,6 +2861,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -5217,34 +5542,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", - "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "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", @@ -6053,22 +6350,6 @@ "node": ">= 10" } }, - "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, "node_modules/teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -6116,6 +6397,23 @@ "node": ">=18" } }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6147,6 +6445,19 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6171,6 +6482,44 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz", + "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.1", + "@typescript-eslint/parser": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", diff --git a/package.json b/package.json index 73a7fe99..ede90401 100644 --- a/package.json +++ b/package.json @@ -24,18 +24,20 @@ "prepare": "husky" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@foundryvtt/foundryvtt-cli": "^1.0.2", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", + "@stylistic/eslint-plugin": "^5.10.0", "concurrently": "^8.2.2", "eslint": "^10.2.1", - "eslint-plugin-prettier": "^5.5.5", "globals": "^17.5.0", "husky": "^9.1.7", "lint-staged": "^16.4.0", "postcss": "^8.4.32", - "prettier": "^3.5.3", - "rollup-plugin-postcss": "^4.0.2" + "rollup-plugin-postcss": "^4.0.2", + "typescript": "^6.0.3", + "typescript-eslint": "^8.60.1" }, "lint-staged": { "**/*": "eslint --fix" diff --git a/tools/analyze-damage.mjs b/tools/analyze-damage.mjs index 7b3fb9e5..31c254ce 100644 --- a/tools/analyze-damage.mjs +++ b/tools/analyze-damage.mjs @@ -82,7 +82,7 @@ function getMean(numbers) { } function getMedianAverageDeviation(numbers, { median }) { - const residuals = allDamage.map(d => Math.abs(d - median)); + const residuals = numbers.map(d => Math.abs(d - median)); return getMedian(residuals); } @@ -98,8 +98,8 @@ function parseDamage(damage) { p.value.custom.enabled ? p.value.custom.formula : [p.value.flatMultiplier ? `${p.value.flatMultiplier}${p.value.dice}` : 0, p.value.bonus ?? 0] - .filter(p => !!p) - .join('+') + .filter(p => !!p) + .join('+') ) .join('+'); return getExpectedDamage(formula); diff --git a/tools/create-symlink.mjs b/tools/create-symlink.mjs index fd828c73..4e14d5df 100644 --- a/tools/create-symlink.mjs +++ b/tools/create-symlink.mjs @@ -2,6 +2,8 @@ import fs from 'fs'; import path from 'path'; import readline from 'readline'; +console.log('Creates a foundry symlink in the base folder for type purposes\n'); + const askQuestion = question => { const rl = readline.createInterface({ input: process.stdin, diff --git a/tools/eslint.config.mjs b/tools/eslint.config.mjs new file mode 100644 index 00000000..36a5174a --- /dev/null +++ b/tools/eslint.config.mjs @@ -0,0 +1,20 @@ +import globals from 'globals'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import { stylisticRules } from '../eslint.config.mjs'; +import stylistic from '@stylistic/eslint-plugin'; + +export default defineConfig([ + globalIgnores(['foundry/**/*']), + { + files: ['**/*.{js,mjs,cjs}'], + plugins: { + '@stylistic': stylistic + }, + languageOptions: { globals: globals.node }, + rules: { + 'no-undef': 'error', + 'no-unused-vars': 0, + ...stylisticRules + } + } +]); From d030bfba34cc243c89a32ef9d2b67d91461cfcaf Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:13:57 +0200 Subject: [PATCH 12/63] Fixed so that only GMs can access the Countdown Edit menu (#1979) --- templates/ui/countdowns.hbs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/templates/ui/countdowns.hbs b/templates/ui/countdowns.hbs index faaffdc5..4a77dfd7 100644 --- a/templates/ui/countdowns.hbs +++ b/templates/ui/countdowns.hbs @@ -2,9 +2,11 @@
{{localize "DAGGERHEART.UI.Countdowns.title"}} - - - + {{#if isGM}} + + + + {{/if}} From 8fec742da3d2b3af2cc47f8299ccaedefc770557 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 6 Jun 2026 21:52:45 -0400 Subject: [PATCH 13/63] [Housekeeping] Fancier dev setup (#1980) --- .env.example | 2 +- README.md | 12 +++--- package.json | 1 - tools/create-symlink.mjs | 91 +++++++++++++++++++++++++--------------- tools/dev-setup.mjs | 18 -------- tools/run-start.mjs | 17 ++------ tools/util.mjs | 39 +++++++++++++++++ 7 files changed, 107 insertions(+), 73 deletions(-) delete mode 100644 tools/dev-setup.mjs create mode 100644 tools/util.mjs diff --git a/.env.example b/.env.example index 730309d3..3d62d699 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ FOUNDRY_MAIN_PATH=/path/to/foundry/resources/app/main.js -FOUNDRY_DATA_PATH=/path/to/foundry/data \ No newline at end of file +FOUNDRY_DATA_PATH=/path/to/foundry/ \ No newline at end of file diff --git a/README.md b/README.md index ac3666b3..177636c7 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,13 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart 3. **Configure your Foundry paths:** - ```bash - npm run setup:dev -- --foundry-path="/path/to/foundry/main.js" --data-path="/path/to/data" - ``` + Copy `.env.example` to `.env` and set up the foundry path and data path. The foundry path must point to main.js, and the data path must point the folder containing the Data folder itself (but not the `Data` folder itself). -4. **Start developing:** +4. **Setup symlinks:** + + In a shell with elevated privilages, run `npm run createSymlink`. It will add a foundry folder for types, and add a symlink to the daggerheart folder into your foundry data. + +5. **Start developing:** ```bash npm start ``` @@ -51,7 +53,7 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart - `npm start` - Start development with file watching and Foundry launching - `npm run build` - One-time build -- `npm run setup:dev -- --foundry-path="" --data-path=""` - Configure development environment +- `npm run createSymlink` - Setup types and output symlinks ### Notes diff --git a/package.json b/package.json index ede90401..d164261f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "pullYMLtoLDB": "node ./tools/pullYMLtoLDB.mjs", "pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDB.mjs --build", "createSymlink": "node ./tools/create-symlink.mjs", - "setup:dev": "node ./tools/dev-setup.mjs", "lint": "eslint", "lint:fix": "eslint --fix", "prepare": "husky" diff --git a/tools/create-symlink.mjs b/tools/create-symlink.mjs index 4e14d5df..a9a121b0 100644 --- a/tools/create-symlink.mjs +++ b/tools/create-symlink.mjs @@ -1,47 +1,70 @@ import fs from 'fs'; import path from 'path'; import readline from 'readline'; +import { isContainedPath, readEnvFile } from './util.mjs'; -console.log('Creates a foundry symlink in the base folder for type purposes\n'); +const projectRoot = path.resolve(import.meta.dirname, '../') +const { foundryRoot, dataPath } = readEnvFile(); -const askQuestion = question => { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); +async function createFoundrySymlink() { + // If foundry already exists, exit and inform the user. This operation can't complete correctly otherwise. + // If the folder is empty, its fine. It may have failed due to perms + const foundryDestPath = path.join(projectRoot, 'foundry'); + if (fs.existsSync(foundryDestPath) && fs.readdirSync(foundryDestPath).length) { + console.log('"foundry" folder already exists in this project'); + return; + } - return new Promise(resolve => - rl.question(question, answer => { - rl.close(); - resolve(answer); - }) - ); -}; - -const installPath = await askQuestion('Enter your Foundry install path: '); - -// Determine if it's an Electron install (nested structure) -const nested = fs.existsSync(path.join(installPath, 'resources', 'app')); -const fileRoot = nested ? path.join(installPath, 'resources', 'app') : installPath; - -try { - await fs.promises.mkdir('foundry'); -} catch (e) { - if (e.code !== 'EEXIST') throw e; -} - -// JavaScript files -for (const p of ['client', 'common', 'tsconfig.json']) { + console.log('Creating "foundry" symlinks for types'); try { - await fs.promises.symlink(path.join(fileRoot, p), path.join('foundry', p)); + await fs.promises.mkdir(foundryDestPath); + console.log('Root foundry folder created'); } catch (e) { if (e.code !== 'EEXIST') throw e; } + + // JavaScript files + for (const p of ['client', 'common', 'tsconfig.json']) { + try { + await fs.promises.symlink(path.join(foundryRoot, p), path.join(foundryDestPath, p)); + console.log(`${p} folder created`); + } catch (e) { + if (e.code !== 'EEXIST') throw e; + } + } + + // Language files + try { + await fs.promises.symlink(path.join(foundryRoot, 'public', 'lang'), path.join(foundryDestPath, 'lang')); + console.log(`lang folder created`); + } catch (e) { + if (e.code !== 'EEXIST') throw e; + console.log(`lang folder already exists`); + } } -// Language files -try { - await fs.promises.symlink(path.join(fileRoot, 'public', 'lang'), path.join('foundry', 'lang')); -} catch (e) { - if (e.code !== 'EEXIST') throw e; +async function createDaggerheartSymlink() { + if (isContainedPath(dataPath, projectRoot)) { + console.log('The Daggerheart project repo is in foundry data, so a symlink won\'t be created'); + return; + } + + const destination = path.join(dataPath, 'Data', 'systems', 'daggerheart'); + if (fs.existsSync(destination)) { + console.log('A Daggerheart folder already exists in Foundry data'); + return; + } + + console.log('Creating Daggerheart symlink in the foundry systems folder') + try { + await fs.promises.symlink(projectRoot, destination); + console.log('Daggerheart system folder symlink created'); + } catch (e) { + if (e.code !== 'EEXIST') throw e; + console.log(`Daggerheart system folder already exists`); + } } + +await createFoundrySymlink(); +console.log(); // Add empty newline +await createDaggerheartSymlink(); diff --git a/tools/dev-setup.mjs b/tools/dev-setup.mjs deleted file mode 100644 index f232f5a8..00000000 --- a/tools/dev-setup.mjs +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env node -import fs from 'fs'; - -const args = process.argv.slice(2); -const foundryPath = args.find(arg => arg.startsWith('--foundry-path='))?.split('=')[1]; -const dataPath = args.find(arg => arg.startsWith('--data-path='))?.split('=')[1]; - -if (!foundryPath || !dataPath) { - console.log('Usage: npm run setup:dev -- --foundry-path="/path/to/foundry/main.js" --data-path="/path/to/data"'); - process.exit(1); -} - -const envContent = `FOUNDRY_MAIN_PATH=${foundryPath} -FOUNDRY_DATA_PATH=${dataPath} -`; - -fs.writeFileSync('.env', envContent); -console.log(`✅ Development environment configured: ${foundryPath}, ${dataPath}`); diff --git a/tools/run-start.mjs b/tools/run-start.mjs index 3f6b25cb..41002ffa 100644 --- a/tools/run-start.mjs +++ b/tools/run-start.mjs @@ -1,21 +1,10 @@ #!/usr/bin/env node import { spawn } from 'child_process'; +import { readEnvFile } from './util.mjs'; import fs from 'fs'; -// Load .env file if it exists -if (fs.existsSync('.env')) { - const envFile = fs.readFileSync('.env', 'utf8'); - envFile.split('\n').forEach(line => { - const [key, value] = line.split('='); - if (key && value) { - process.env[key] = value; - } - }); -} - -// Set defaults if not in environment -const foundryPath = process.env.FOUNDRY_MAIN_PATH || '../../../../FoundryDev/main.js'; -const dataPath = process.env.FOUNDRY_DATA_PATH || '../../../'; +// Load .env file params +const { foundryPath, dataPath } = readEnvFile(); // Run the original command with proper environment const args = ['rollup -c --watch', `node "\"${foundryPath}\"" --dataPath="${dataPath}" --noupnp`, 'gulp']; diff --git a/tools/util.mjs b/tools/util.mjs new file mode 100644 index 00000000..3af83540 --- /dev/null +++ b/tools/util.mjs @@ -0,0 +1,39 @@ +import fs from 'fs'; +import path from 'path'; + +export function readEnvFile() { + if (!fs.existsSync('.env')) { + console.error('No configured .env file. Copy .env.example to .env and configure it.'); + process.exit(); + } + + const envFile = fs.readFileSync('.env', 'utf8'); + envFile.split('\n').forEach(line => { + const [key, value] = line.split('='); + if (key && value) { + process.env[key] = value; + } + }); + + // Determine foundry path, handling if its an electron install (nested structure) + const foundryPath = path.normalize(process.env.FOUNDRY_MAIN_PATH); + const dataPath = path.normalize(process.env.FOUNDRY_DATA_PATH); + if (!foundryPath.endsWith(path.join('app', 'main.js'))) { + console.error('Configured FOUNDRY_MAIN_PATH is invalid, it must end with app/main.js'); + process.exit(); + } + if (/Data(\/|\\)?$/.test(dataPath) || !fs.existsSync(path.join(dataPath, 'Data'))) { + console.error('Configured FOUNDRY_DATA_PATH is incorrect. This must be a folder that contains "Data"'); + } + + return { + foundryPath, + foundryRoot: path.dirname(foundryPath), + dataPath + }; +} + +export function isContainedPath(parent, child) { + const relative = path.relative(parent, child); + return relative && !relative.startsWith('..') && !path.isAbsolute(relative); +} \ No newline at end of file From 67cd28f991f955d911a581bd197e5e789e4ea469 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 7 Jun 2026 15:23:55 +0200 Subject: [PATCH 14/63] Fixed so that FOUNDRY_MAIN_PATH can be a node setup (#1984) --- tools/util.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/util.mjs b/tools/util.mjs index 3af83540..6add7ea4 100644 --- a/tools/util.mjs +++ b/tools/util.mjs @@ -16,10 +16,10 @@ export function readEnvFile() { }); // Determine foundry path, handling if its an electron install (nested structure) - const foundryPath = path.normalize(process.env.FOUNDRY_MAIN_PATH); - const dataPath = path.normalize(process.env.FOUNDRY_DATA_PATH); - if (!foundryPath.endsWith(path.join('app', 'main.js'))) { - console.error('Configured FOUNDRY_MAIN_PATH is invalid, it must end with app/main.js'); + const foundryPath = path.normalize(process.env.FOUNDRY_MAIN_PATH).trimEnd(); + const dataPath = path.normalize(process.env.FOUNDRY_DATA_PATH).trimEnd(); + if (!foundryPath.endsWith('main.js')) { + console.error('Configured FOUNDRY_MAIN_PATH is invalid, it must end with main.js'); process.exit(); } if (/Data(\/|\\)?$/.test(dataPath) || !fs.existsSync(path.join(dataPath, 'Data'))) { From a148aa3bcb2a4ddbacb05656c014f6db2154976e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iohan=20R=2E=20Tr=C3=A9zze?= Date: Sun, 7 Jun 2026 10:37:42 -0300 Subject: [PATCH 15/63] [Feature] Configurable starting gold amounts in Homebrew settings (#1969) * [Feature] Configurable starting gold amounts in Homebrew settings --- lang/en.json | 4 ++- module/data/actor/character.mjs | 13 +++++++- module/data/fields/actorField.mjs | 17 ++++++----- module/data/settings/Homebrew.mjs | 15 ++++++++-- styles/less/ui/settings/settings.less | 17 +++++++++++ .../settings/homebrew-settings/settings.hbs | 30 +++++++++++-------- 6 files changed, 72 insertions(+), 24 deletions(-) diff --git a/lang/en.json b/lang/en.json index 3a1340e0..9f16828e 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2887,7 +2887,9 @@ "iconName": "Icon Name", "iconNameHint": "Icons are from fontawesome", "bagName": "Bag Name", - "chestName": "Chest Name" + "chestName": "Chest Name", + "denomination": "Denomination", + "initialAmount": "Starting Amount" }, "domains": { "domainsTitle": "Base Domains", diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index aed27650..0cae662f 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -64,7 +64,18 @@ export default class DhCharacter extends DhCreature { core: new fields.BooleanField({ initial: false }) }) ), - gold: new GoldField(), + gold: new GoldField({ + initial: () => { + const homebrew = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew); + const { coins, handfuls, bags, chests } = homebrew.currency; + return { + coins: coins.enabled ? coins.initialAmount : 0, + handfuls: handfuls.enabled ? handfuls.initialAmount : 0, + bags: bags.enabled ? bags.initialAmount : 0, + chests: chests.enabled ? chests.initialAmount : 0 + }; + } + }), scars: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.scars' }), biography: new fields.SchemaField({ background: new fields.HTMLField(), diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs index a5f6f379..e165f5cc 100644 --- a/module/data/fields/actorField.mjs +++ b/module/data/fields/actorField.mjs @@ -116,13 +116,16 @@ class ResourcesField extends fields.TypedObjectField { } class GoldField extends fields.SchemaField { - constructor() { - super({ - coins: new fields.NumberField({ initial: 0, integer: true }), - handfuls: new fields.NumberField({ initial: 1, integer: true }), - bags: new fields.NumberField({ initial: 0, integer: true }), - chests: new fields.NumberField({ initial: 0, integer: true }) - }); + constructor(options = {}) { + super( + { + coins: new fields.NumberField({ initial: 0, integer: true }), + handfuls: new fields.NumberField({ initial: 1, integer: true }), + bags: new fields.NumberField({ initial: 0, integer: true }), + chests: new fields.NumberField({ initial: 0, integer: true }) + }, + options + ); } } diff --git a/module/data/settings/Homebrew.mjs b/module/data/settings/Homebrew.mjs index 31247458..a0761e20 100644 --- a/module/data/settings/Homebrew.mjs +++ b/module/data/settings/Homebrew.mjs @@ -2,7 +2,7 @@ import { defaultRestOptions } from '../../config/generalConfig.mjs'; import { resetAndRerenderActors } from '../../helpers/utils.mjs'; import { ActionsField } from '../fields/actionField.mjs'; -const currencyField = (initial, label, icon) => +const currencyField = (initial, label, icon, initialAmount = 0) => new foundry.data.fields.SchemaField({ enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }), label: new foundry.data.fields.StringField({ @@ -10,7 +10,14 @@ const currencyField = (initial, label, icon) => initial, label }), - icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon }) + icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon }), + initialAmount: new foundry.data.fields.NumberField({ + required: true, + integer: true, + min: 0, + initial: initialAmount, + label: 'DAGGERHEART.SETTINGS.Homebrew.currency.initialAmount' + }) }); const restMoveField = () => @@ -108,7 +115,8 @@ export default class DhHomebrew extends foundry.abstract.DataModel { handfuls: currencyField( 'Handfuls', 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName', - 'fa-solid fa-coins' + 'fa-solid fa-coins', + 1 ), bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName', 'fa-solid fa-sack'), chests: currencyField( @@ -193,6 +201,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel { for (const type of ['coins', 'handfuls', 'bags', 'chests']) { const initial = this.schema.fields.currency.fields[type].getInitialValue(); source.currency[type] = foundry.utils.mergeObject(initial, source.currency[type], { inplace: false }); + source.currency[type].initialAmount ??= 0; } return source; } diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index 35c48480..f2ab99e7 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -201,6 +201,23 @@ } } + .currency-rows { + display: grid; + grid-template-columns: auto 1fr 1fr auto; + gap: 4px; + align-items: center; + width: 100%; + + .currency-header-label { + text-align: center; + font-size: var(--font-size-14); + } + + input[type='checkbox'] { + justify-self: center; + } + } + .settings-hint { width: 100%; display: flex; diff --git a/templates/settings/homebrew-settings/settings.hbs b/templates/settings/homebrew-settings/settings.hbs index 4b6e7d85..1fd449a9 100644 --- a/templates/settings/homebrew-settings/settings.hbs +++ b/templates/settings/homebrew-settings/settings.hbs @@ -42,29 +42,35 @@
- {{localize "DAGGERHEART.SETTINGS.Homebrew.currency.title"}} + {{localize "DAGGERHEART.SETTINGS.Homebrew.currency.title"}}
{{formGroup settingFields.schema.fields.currency.fields.title value=settingFields._source.currency.title localize=true}}
-
+
+ + {{localize "DAGGERHEART.SETTINGS.Homebrew.currency.denomination"}} + {{localize "DAGGERHEART.SETTINGS.Homebrew.currency.initialAmount"}} + + - {{formGroup settingFields.schema.fields.currency.fields.coins.fields.label value=settingFields._source.currency.coins.label localize=true}} + {{formInput settingFields.schema.fields.currency.fields.coins.fields.label value=settingFields._source.currency.coins.label name="currency.coins.label"}} + -
-
+ - {{formGroup settingFields.schema.fields.currency.fields.handfuls.fields.label value=settingFields._source.currency.handfuls.label localize=true}} + {{formInput settingFields.schema.fields.currency.fields.handfuls.fields.label value=settingFields._source.currency.handfuls.label name="currency.handfuls.label"}} + -
-
+ - {{formGroup settingFields.schema.fields.currency.fields.bags.fields.label value=settingFields._source.currency.bags.label localize=true}} + {{formInput settingFields.schema.fields.currency.fields.bags.fields.label value=settingFields._source.currency.bags.label name="currency.bags.label"}} + -
-
+ - {{formGroup settingFields.schema.fields.currency.fields.chests.fields.label value=settingFields._source.currency.chests.label localize=true}} + {{formInput settingFields.schema.fields.currency.fields.chests.fields.label value=settingFields._source.currency.chests.label name="currency.chests.label"}} +
From c211085196ebcd756b841bcc2486a1a185ddafc7 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 7 Jun 2026 17:22:51 +0200 Subject: [PATCH 16/63] Fixed node start not running gulp --- package.json | 1 - tools/run-start.mjs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index d164261f..533d250a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ }, "scripts": { "start": "node ./tools/run-start.mjs", - "start-test": "node ./resources/app/main.js --dataPath=./ && rollup -c --watch && gulp", "build": "npm run rollup && npm run gulp", "rollup": "rollup -c", "gulp": "gulp less", diff --git a/tools/run-start.mjs b/tools/run-start.mjs index 41002ffa..379e174e 100644 --- a/tools/run-start.mjs +++ b/tools/run-start.mjs @@ -7,7 +7,7 @@ import fs from 'fs'; const { foundryPath, dataPath } = readEnvFile(); // Run the original command with proper environment -const args = ['rollup -c --watch', `node "\"${foundryPath}\"" --dataPath="${dataPath}" --noupnp`, 'gulp']; +const args = ['rollup -c --watch', 'gulp', `node "\"${foundryPath}\"" --dataPath="${dataPath}" --noupnp`]; spawn('npx', ['concurrently', ...args.map(arg => `"${arg}"`)], { stdio: 'inherit', From afd8a83241354c0158c1d7738f167eaddfc03ac7 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:29:03 +0200 Subject: [PATCH 17/63] Fixed the styling for the TokenHUD movement palette (#1983) --- templates/hud/tokenHUD.hbs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/hud/tokenHUD.hbs b/templates/hud/tokenHUD.hbs index cf43e15d..91c82f13 100644 --- a/templates/hud/tokenHUD.hbs +++ b/templates/hud/tokenHUD.hbs @@ -103,7 +103,7 @@ {{/if}} -
+
{{#each movementActions as |action|}} - + {{#if action.img}} {{ action.label }} From 9a220b4e16aa0f3e80a08098a050cba31a2ef904 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 9 Jun 2026 08:34:25 +0200 Subject: [PATCH 18/63] [Fix] Companion Rolls (#1982) --- module/applications/dialogs/d20RollDialog.mjs | 3 ++- module/dice/d20Roll.mjs | 17 +++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 9a98b197..e9140b56 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -224,7 +224,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.render(); } - static async submitRoll() { + static async submitRoll(event) { + event.preventDefault(); await this.close({ submitted: true }); } diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index b1d3bd0b..c0069822 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -100,16 +100,13 @@ export default class D20Roll extends DHRoll { this.options.roll.modifiers = this.applyBaseBonus(); - const actorExperiences = this.options.roll.companionRoll - ? (this.options.data?.companion?.system.experiences ?? {}) - : (this.options.data.system?.experiences ?? {}); - this.options.experiences?.forEach(m => { - if (actorExperiences[m]) - this.options.roll.modifiers.push({ - label: actorExperiences[m].name, - value: actorExperiences[m].value - }); - }); + let actorExperiences = this.options.data.system?.experiences ?? {}; + if (this.options.roll.companionRoll) { + const companion = typeof this.options.data.companion === 'string' ? + foundry.utils.fromUuidSync(this.options.data.companion) : + this.options.data.companion; + actorExperiences = companion?.system?.experiences ?? {}; + } this.addModifiers(); if (this.options.extraFormula) { From a96eb6ab6267ddaccbb297769a7c0e02477a9f51 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 10 Jun 2026 10:47:39 -0400 Subject: [PATCH 19/63] Fix check for FOUNDRY_DATA_PATH (#1989) --- tools/util.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/util.mjs b/tools/util.mjs index 6add7ea4..ae3fedc5 100644 --- a/tools/util.mjs +++ b/tools/util.mjs @@ -22,8 +22,9 @@ export function readEnvFile() { console.error('Configured FOUNDRY_MAIN_PATH is invalid, it must end with main.js'); process.exit(); } - if (/Data(\/|\\)?$/.test(dataPath) || !fs.existsSync(path.join(dataPath, 'Data'))) { + if (path.basename(dataPath) === 'Data' || !fs.existsSync(path.join(dataPath, 'Data'))) { console.error('Configured FOUNDRY_DATA_PATH is incorrect. This must be a folder that contains "Data"'); + process.exit(); } return { From 58eb41e4a99703d84d13b21003cb3b7defd3311f Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:49:47 +0200 Subject: [PATCH 20/63] [Feature] System Styled GamePause (#1986) * Experimented with hope/fear pulsed text and the DhCompatible logo * Improved handling so that modules changing the game pause work normally * Moved pause-pulse keyframes to game-pause.less --- daggerheart.mjs | 1 + module/applications/ui/_module.mjs | 1 + module/applications/ui/gamePause.mjs | 23 +++++++++++++++++++++++ styles/less/ui/game-pause/game-pause.less | 23 +++++++++++++++++++++++ styles/less/ui/game-pause/index.less | 1 + styles/less/ui/index.less | 1 + 6 files changed, 50 insertions(+) create mode 100644 module/applications/ui/gamePause.mjs create mode 100644 styles/less/ui/game-pause/game-pause.less create mode 100644 styles/less/ui/game-pause/index.less diff --git a/daggerheart.mjs b/daggerheart.mjs index 7bfdf874..a018caba 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -88,6 +88,7 @@ CONFIG.ui.actors = applications.sidebar.DhActorDirectory; CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu; CONFIG.ui.resources = applications.ui.DhFearTracker; CONFIG.ui.countdowns = applications.ui.DhCountdowns; +CONFIG.ui.pause = applications.ui.DhGamePause; CONFIG.ux.ContextMenu = applications.ux.DHContextMenu; CONFIG.ux.TooltipManager = documents.DhTooltipManager; CONFIG.ux.TokenManager = new TokenManager(); diff --git a/module/applications/ui/_module.mjs b/module/applications/ui/_module.mjs index 80d3ebe4..e30950b5 100644 --- a/module/applications/ui/_module.mjs +++ b/module/applications/ui/_module.mjs @@ -8,3 +8,4 @@ export { default as DhHotbar } from './hotbar.mjs'; export { default as DhSceneNavigation } from './sceneNavigation.mjs'; export { ItemBrowser } from './itemBrowser.mjs'; export { default as DhProgress } from './progress.mjs'; +export { default as DhGamePause } from './gamePause.mjs'; diff --git a/module/applications/ui/gamePause.mjs b/module/applications/ui/gamePause.mjs new file mode 100644 index 00000000..8047f1dc --- /dev/null +++ b/module/applications/ui/gamePause.mjs @@ -0,0 +1,23 @@ +export default class DhGamePause extends foundry.applications.ui.GamePause { + async _onRender(context, options) { + await super._onRender(context, options); + + /* Avoid altering the styling if a module has subscribed to the renderGamePause hook */ + if (!Hooks.events.renderGamePause?.length) { + this.element.classList.add('dh-style'); + } + } + + /** @override */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + + /* Avoid altering the gamepause context if a module has subscribed to the renderGamePause hook */ + if (!Hooks.events.renderGamePause?.length) { + context.spin = options.spin ?? false; + context.icon = options.icon ?? 'systems/daggerheart/assets/logos/compatible_with_DH_logos-10.png'; + } + + return context; + } +} \ No newline at end of file diff --git a/styles/less/ui/game-pause/game-pause.less b/styles/less/ui/game-pause/game-pause.less new file mode 100644 index 00000000..60d08bec --- /dev/null +++ b/styles/less/ui/game-pause/game-pause.less @@ -0,0 +1,23 @@ +@import '../../utils/mixin.less'; + +#pause.dh-style { + figcaption { + position: absolute; + margin-top: 24px; + animation: pause-pulse 3s ease-in-out infinite; + } + + @keyframes pause-pulse { + 0% { + filter: drop-shadow(0 0 5px @secondary-blue); + } + + 50% { + filter: drop-shadow(0 0 5px @golden); + } + + 100% { + filter: drop-shadow(0 0 5px @secondary-blue); + } + } +} \ No newline at end of file diff --git a/styles/less/ui/game-pause/index.less b/styles/less/ui/game-pause/index.less new file mode 100644 index 00000000..ad28855c --- /dev/null +++ b/styles/less/ui/game-pause/index.less @@ -0,0 +1 @@ +@import './game-pause.less'; \ No newline at end of file diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 53a71b9b..af1614e4 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -2,6 +2,7 @@ @import './combat-sidebar/index.less'; @import './countdown/index.less'; @import './effects-display/index.less'; +@import './game-pause/index.less'; @import './item-browser/index.less'; @import './ownership-selection/index.less'; @import './resources/index.less'; From 0fb79b98bb65f11cc53b2fdc538700b6624f2b9a Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:29:15 +0200 Subject: [PATCH 21/63] [Housekeeping] V14.364 Update (#1991) * Raised compatability to foundry-364. Swapped to new resourceBar color methodology * Bumped min-version --- daggerheart.mjs | 14 ++++++++++++++ module/canvas/placeables/token.mjs | 10 ++++------ system.json | 4 ++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/daggerheart.mjs b/daggerheart.mjs index a018caba..b92be2a2 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -77,6 +77,20 @@ CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenCon CONFIG.Token.objectClass = placeables.DhTokenPlaceable; CONFIG.Token.rulerClass = placeables.DhTokenRuler; CONFIG.Token.hudClass = applications.hud.DHTokenHUD; +CONFIG.Token.barConfig = { + bar1: { + colors: { + full: Color.fromRGB([1, 0, 0]), + empty: Color.fromRGB([0, 0, 0]) + } + }, + bar2: { + colors: { + full: Color.fromString('#0032b1'), + empty: Color.fromRGB([0, 0, 0]) + } + } +}; CONFIG.ui.combat = applications.ui.DhCombatTracker; CONFIG.ui.nav = applications.ui.DhSceneNavigation; diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 02eed7db..7ae6dc37 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -248,7 +248,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { } /** @inheritDoc */ - _drawBar(number, bar, data) { + _drawBar(index, bar, data) { // Determine sizing const { width, height } = this.document.getSize(); const s = canvas.dimensions.uiScale; @@ -256,9 +256,7 @@ 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 Color = foundry.utils.Color; - const fillColor = number === 0 ? Color.fromRGB([1, 0, 0]) : Color.fromString('#0032b1'); - const emptyColor = Color.fromRGB([0, 0, 0]); + const { full: fullColor, empty: emptyColor } = this._getBarColors(index, data); // Draw the bar (accounting floating point numbers from bar animations) const widthUnit = bw / Math.ceil(data.max); @@ -268,7 +266,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { const x = mark * widthUnit; 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; + const color = !marked ? emptyColor : remainder ? emptyColor.mix(fullColor, remainder) : fullColor; 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 { @@ -277,7 +275,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { } // Set position - const posY = number === 0 ? height - bh : 0; + const posY = index === 0 ? height - bh : 0; bar.position.set(0, posY); return true; } diff --git a/system.json b/system.json index ed14a17b..014af29a 100644 --- a/system.json +++ b/system.json @@ -4,8 +4,8 @@ "description": "An unofficial implementation of the Daggerheart system", "version": "2.3.2", "compatibility": { - "minimum": "14.361", - "verified": "14.363", + "minimum": "14.364", + "verified": "14.364", "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart", From b88413082696e61a9b75591b9d8a7e6abe7c541e Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 11 Jun 2026 15:19:04 -0400 Subject: [PATCH 22/63] Bulk add items during character creation (#1992) --- .../characterCreation/characterCreation.mjs | 97 +++++++++---------- module/helpers/utils.mjs | 34 ------- 2 files changed, 48 insertions(+), 83 deletions(-) diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index 517f95da..a0b6a75f 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -524,37 +524,52 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl } }; - await createEmbeddedItemWithEffects(this.character, ancestry); - await createEmbeddedItemWithEffects(this.character, this.setup.community); - await createEmbeddedItemWithEffects(this.character, this.setup.class); - await createEmbeddedItemWithEffects(this.character, this.setup.subclass); - await createEmbeddedItemsWithEffects(this.character, Object.values(this.setup.domainCards)); + // Inner function to create the base item data + async function createEmbeddedItemData(baseData) { + const uuid = baseData.uuid ?? baseData._uuid + const data = baseData instanceof Item ? baseData : await foundry.utils.fromUuid(baseData.uuid) ?? baseData; + return { + ...baseData, + id: data.id, + uuid: uuid, + _uuid: uuid, + effects: data.effects?.map(effect => effect.toObject()), + flags: baseData.flags ?? data.flags, + _stats: { + ...data._stats, + compendiumSource: uuid.startsWith('Compendium.') ? uuid : null, + duplicateSource: uuid && !uuid.startsWith('Compendium.') ? uuid : null + } + }; + } + // Add the class first. All other items validate it during pre creation + await this.character.createEmbeddedDocuments('Item', [await createEmbeddedItemData(this.setup.class)]); + + // Add the remaining items + const newItems = [ + await createEmbeddedItemData(ancestry), + await createEmbeddedItemData(this.setup.community), + await createEmbeddedItemData(this.setup.subclass), + ...(await Promise.all( + Object.values(this.setup.domainCards).map(d => createEmbeddedItemData(d)) + )) + ]; if (this.equipment.armor.uuid) - await createEmbeddedItemWithEffects(this.character, this.equipment.armor, { - ...this.equipment.armor, - system: { ...this.equipment.armor.system, equipped: true } - }); + newItems.push(await createEmbeddedItemData(this.equipment.armor)); if (this.equipment.primaryWeapon.uuid) - await createEmbeddedItemWithEffects(this.character, this.equipment.primaryWeapon, { - ...this.equipment.primaryWeapon, - system: { ...this.equipment.primaryWeapon.system, equipped: true } - }); + newItems.push(await createEmbeddedItemData(this.equipment.primaryWeapon)); if (this.equipment.secondaryWeapon.uuid) - await createEmbeddedItemWithEffects(this.character, this.equipment.secondaryWeapon, { - ...this.equipment.secondaryWeapon, - system: { ...this.equipment.secondaryWeapon.system, equipped: true } - }); + newItems.push(await createEmbeddedItemData(this.equipment.secondaryWeapon)); if (this.equipment.inventory.choiceA.uuid) - await createEmbeddedItemWithEffects(this.character, this.equipment.inventory.choiceA); + newItems.push(await createEmbeddedItemData(this.equipment.inventory.choiceA)); if (this.equipment.inventory.choiceB.uuid) - await createEmbeddedItemWithEffects(this.character, this.equipment.inventory.choiceB); - - await createEmbeddedItemsWithEffects( - this.character, - this.setup.class.system.inventory.take.filter(x => x) - ); + newItems.push(await createEmbeddedItemData(this.equipment.inventory.choiceB)); + for (const item of this.setup.class.system.inventory.take.filter(x => x)) { + newItems.push(await createEmbeddedItemData(item)); + } + await this.character.createEmbeddedDocuments('Item', newItems); await this.character.update( { system: { @@ -587,26 +602,14 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl const item = await foundry.utils.fromUuid(data.uuid); if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) { this.setup.ancestryName.primary = item.name; - this.setup.primaryAncestry = { - ...item, - effects: Array.from(item.effects).map(x => x.toObject()), - uuid: item.uuid - }; + this.setup.primaryAncestry = item; } else if (item.type === 'ancestry' && event.target.closest('.secondary-ancestry-card')) { this.setup.ancestryName.secondary = item.name; - this.setup.secondaryAncestry = { - ...item, - effects: Array.from(item.effects).map(x => x.toObject()), - uuid: item.uuid - }; + this.setup.secondaryAncestry = item; } else if (item.type === 'community' && event.target.closest('.community-card')) { - this.setup.community = { - ...item, - effects: Array.from(item.effects).map(x => x.toObject()), - uuid: item.uuid - }; + this.setup.community = item; } else if (item.type === 'class' && event.target.closest('.class-card')) { - this.setup.class = { ...item, effects: Array.from(item.effects).map(x => x.toObject()), uuid: item.uuid }; + this.setup.class = item; this.setup.subclass = {}; this.setup.domainCards = { [foundry.utils.randomID()]: {}, @@ -619,11 +622,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl return; } - this.setup.subclass = { - ...item, - effects: Array.from(item.effects).map(x => x.toObject()), - uuid: item.uuid - }; + this.setup.subclass = item; } else if (item.type === 'domainCard' && event.target.closest('.domain-card')) { if (!this.setup.class.uuid) { ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass')); @@ -645,14 +644,14 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl return; } - this.setup.domainCards[event.target.closest('.domain-card').dataset.card] = { ...item, uuid: item.uuid }; + this.setup.domainCards[event.target.closest('.domain-card').dataset.card] = item; } else if (item.type === 'armor' && event.target.closest('.armor-card')) { if (item.system.tier > 1) { ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.itemTooHighTier')); return; } - this.equipment.armor = { ...item, uuid: item.uuid }; + this.equipment.armor = item; } else if (item.type === 'weapon' && event.target.closest('.primary-weapon-card')) { if (item.system.secondary) { ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.notPrimary')); @@ -668,7 +667,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl this.equipment.secondaryWeapon = {}; } - this.equipment.primaryWeapon = { ...item, uuid: item.uuid }; + this.equipment.primaryWeapon = item; } else if (item.type === 'weapon' && event.target.closest('.secondary-weapon-card')) { if (this.equipment.primaryWeapon?.system?.burden === burden.twoHanded.value) { ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.primaryIsTwoHanded')); @@ -685,7 +684,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl return; } - this.equipment.secondaryWeapon = { ...item, uuid: item.uuid }; + this.equipment.secondaryWeapon = item; } else { return; } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index af6c2777..90a5c29d 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -418,40 +418,6 @@ export function createScrollText(actor, data) { } } -export async function createEmbeddedItemWithEffects(actor, baseData, update) { - const data = baseData.uuid.startsWith('Compendium') ? await foundry.utils.fromUuid(baseData.uuid) : baseData; - const [doc] = await actor.createEmbeddedDocuments('Item', [ - { - ...(update ?? data), - ...baseData, - id: data.id, - uuid: data.uuid, - _uuid: data.uuid, - effects: data.effects?.map(effect => effect.toObject()), - _stats: { - ...data._stats, - compendiumSource: data.pack ? `Compendium.${data.pack}.Item.${data.id}` : null - } - } - ]); - - return doc; -} - -export async function createEmbeddedItemsWithEffects(actor, baseData) { - const effectData = []; - for (let d of baseData) { - const data = d.uuid.startsWith('Compendium') ? await foundry.utils.fromUuid(d.uuid) : d; - effectData.push({ - ...data, - id: data.id, - uuid: data.uuid, - effects: data.effects?.map(effect => effect.toObject()) - }); - } - await actor.createEmbeddedDocuments('Item', effectData); -} - export function shuffleArray(array) { let currentIndex = array.length; while (currentIndex != 0) { From 7a631c27a752ebfc12b52b5973afea957a41fa4a Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 11 Jun 2026 17:22:37 -0400 Subject: [PATCH 23/63] Fix linking world item subclasses (#1987) --- .../characterCreation/characterCreation.mjs | 3 +-- module/applications/sheets/actors/character.mjs | 2 +- module/applications/sheets/items/subclass.mjs | 2 +- module/data/item/class.mjs | 6 +++--- module/data/item/subclass.mjs | 4 +--- module/documents/item.mjs | 10 ++++++++++ 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index a0b6a75f..b068bf7a 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -441,9 +441,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl if (type === 'subclasses') { const classItem = this.setup.class; - const uuid = classItem?._stats.compendiumSource ?? classItem?.uuid; presets.filter = { - 'system.linkedClass': { key: 'system.linkedClass', value: uuid } + 'system.linkedClass': { key: 'system.linkedClass', value: classItem?.sourceUuid } }; } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index bc2cdb41..c5d9cdd5 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -356,7 +356,7 @@ export default class CharacterSheet extends DHBaseActorSheet { const levelups = Object.values(actor.system.levelData?.levelups) ?? []; const uuid = item.uuid; - const sourceUuid = item._stats.compendiumSource; // on older characters this may be missing + const sourceUuid = item.sourceUuid; // 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); diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index e9d8370e..650d2088 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -59,7 +59,7 @@ export default class SubclassSheet extends DHBaseItemSheet { const item = await fromUuid(data.uuid); const itemType = data.type === 'ActiveEffect' ? data.type : item.type; if (itemType === 'class') { - const uuid = item._stats.compendiumSource ?? item.uuid; + const uuid = item.sourceUuid; if (this.document.system.linkedClass !== uuid) { await this.document.update({ 'system.linkedClass': uuid }); // Re-render all class sheets for instant feedback diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 470a1e3c..b9cda264 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -70,14 +70,14 @@ export default class DHClass extends BaseDataItem { } async fetchSubclasses() { - 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)); + const sourceUuid = this.parent.sourceUuid; + const subclasses = game.items.filter(x => x.type === 'subclass' && x.system.linkedClass === sourceUuid); 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 (index.system?.linkedClass !== sourceUuid) continue; if (subclasses.find(x => x.uuid === index.uuid)) continue; packIds.push(index._id); } diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 934b55d3..cbf73bc3 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -70,9 +70,7 @@ export default class DHSubclass extends BaseDataItem { return false; } - const match = [multiclass, actorClass].find( - c => c && (c._stats.compendiumSource ?? c.uuid) === this.linkedClass - ); + const match = [multiclass, actorClass].find(c => c && c.sourceUuid === this.linkedClass); if (!match) { const key = multiclass ? 'subclassNotInMulticlass' : 'subclassNotInClass'; ui.notifications.warn(`DAGGERHEART.UI.Notifications.${key}`, { localize: true }); diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 4716068d..32543ebd 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -5,6 +5,16 @@ import ActionSelectionDialog from '../applications/dialogs/actionSelectionDialog * @extends {foundry.documents.Item} */ export default class DHItem extends foundry.documents.Item { + /** + * Returns the uuid of the original item this item was derived from, + * or its own uuid if its a compendium item or not derived from a source item. + * @returns {string} + */ + get sourceUuid() { + const isCompendium = this._id && this.pack && !this.isEmbedded; + return isCompendium ? this.uuid : this._stats.duplicateSource ?? this._stats.compendiumSource ?? this.uuid; + } + /** @inheritDoc */ prepareEmbeddedDocuments() { super.prepareEmbeddedDocuments(); From 9f26c53af5f0684de5719f76019d309aa3c3d3f2 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 13 Jun 2026 05:30:27 -0400 Subject: [PATCH 24/63] Fix dragging AE from compendium or within same actor (#1997) --- .../applications/sheets/actors/adversary.mjs | 9 ----- .../applications/sheets/actors/character.mjs | 9 ----- .../sheets/api/application-mixin.mjs | 33 ++++++++----------- module/applications/sheets/api/base-actor.mjs | 8 +++-- 4 files changed, 19 insertions(+), 40 deletions(-) diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 06dd4a0f..c8d5a299 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -186,15 +186,6 @@ export default class AdversarySheet extends DHBaseActorSheet { }); } - /** @inheritdoc */ - async _onDragStart(event) { - const inventoryItem = event.currentTarget.closest('.inventory-item'); - if (inventoryItem) { - event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0); - } - super._onDragStart(event); - } - /* -------------------------------------------- */ /* Application Clicks Actions */ /* -------------------------------------------- */ diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index c5d9cdd5..f0f8326f 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -1193,15 +1193,6 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } - /** @inheritdoc */ - async _onDragStart(event) { - const inventoryItem = event.currentTarget.closest('.inventory-item'); - if (inventoryItem) { - event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0); - } - super._onDragStart(event); - } - async _onDropItem(event, item) { const setupCriticalItemTypes = ['class', 'subclass', 'ancestry', 'community']; if (this.document.system.needsCharacterSetup && setupCriticalItemTypes.includes(item.type)) { diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 63bbb536..752dc80b 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -361,18 +361,17 @@ export default function DHApplicationMixin(Base) { */ async _onDragStart(event) { const inventoryItem = event.currentTarget.closest('.inventory-item'); - if (inventoryItem) { - const { type, itemUuid } = inventoryItem.dataset; - if (type === 'effect') { - const effect = await foundry.utils.fromUuid(itemUuid); - const effectData = { - type: 'ActiveEffect', - data: { ...effect.toObject(), _id: null }, - fromInternal: this.document.uuid - }; - event.dataTransfer.setData('text/plain', JSON.stringify(effectData)); - event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0); - } + if (!inventoryItem) return; + + const { type, itemUuid } = inventoryItem.dataset; + const effect = type === 'effect' ? await foundry.utils.fromUuid(itemUuid) : null; + if (effect) { + const effectData = { + ...effect.toDragData(), + fromInternal: this.document.uuid + }; + event.dataTransfer.setData('text/plain', JSON.stringify(effectData)); + event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0); } } @@ -382,14 +381,10 @@ export default function DHApplicationMixin(Base) { * @protected */ _onDrop(event) { + // Fallback to super, but note that config sheets don't have this option + // We still need this to avoid setting apps having issues event.stopPropagation(); - const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); - if (data.type === 'ActiveEffect' && data.fromInternal !== this.document.uuid) { - this.document.createEmbeddedDocuments('ActiveEffect', [data.data]); - } else { - // Fallback to super, but note that item sheets do not have this function - return super._onDrop?.(event); - } + return super._onDrop?.(event); } /* -------------------------------------------- */ diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 812ad311..027dd397 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -447,13 +447,15 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { const item = await getDocFromElement(event.target); if (item) { + const inventoryItem = event.currentTarget.closest('.inventory-item'); const dragData = { + ...item.toDragData(), originActor: this.document.uuid, - originId: item.id, - type: item.documentName, - uuid: item.uuid + originId: item.id }; event.dataTransfer.setData('text/plain', JSON.stringify(dragData)); + if (inventoryItem) event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0); + return; } super._onDragStart(event); From a486ed75a2ad8656df812f39fbdae70bd327b66b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 13 Jun 2026 05:43:59 -0400 Subject: [PATCH 25/63] [Refactor] Inherit font family body on most standard components (#1994) --- styles/less/utils/fonts.less | 46 ++++++++++++++++-------------------- styles/less/utils/mixin.less | 24 +++++++------------ 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/styles/less/utils/fonts.less b/styles/less/utils/fonts.less index 201ea356..07da3389 100755 --- a/styles/less/utils/fonts.less +++ b/styles/less/utils/fonts.less @@ -1,25 +1,21 @@ -@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Cinzel+Decorative:wght@700&family=Montserrat:wght@400;600&display=swap'); -@import './mixin.less'; - -:root { - --dh-font-title: 'Cinzel Decorative'; - --dh-font-subtitle: 'Cinzel'; - --dh-font-body: 'Montserrat'; - - /* Include missing font sizes */ - --font-size-8: 0.5rem; - --font-size-9: 0.5625rem; - --font-size-22: 1.375rem; -} - -@font-title: ~"var(--dh-font-title, 'Cinzel Decorative'), serif"; -@font-subtitle: ~"var(--dh-font-subtitle, 'Cinzel'), serif"; -@font-body: ~"var(--dh-font-body, 'Montserrat'), sans-serif"; - -.dh-style { - .dh-typography(); -} - -.dh-style fieldset { - .dh-typography(); -} +@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Cinzel+Decorative:wght@700&family=Montserrat:wght@400;600&display=swap'); +@import './mixin.less'; + +:root { + --dh-font-title: 'Cinzel Decorative'; + --dh-font-subtitle: 'Cinzel'; + --dh-font-body: 'Montserrat'; + + /* Include missing font sizes */ + --font-size-8: 0.5rem; + --font-size-9: 0.5625rem; + --font-size-22: 1.375rem; +} + +@font-title: ~"var(--dh-font-title, 'Cinzel Decorative'), serif"; +@font-subtitle: ~"var(--dh-font-subtitle, 'Cinzel'), serif"; +@font-body: ~"var(--dh-font-body, 'Montserrat'), sans-serif"; + +.dh-style { + .dh-typography(); +} diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index 18b1f9a6..e2ef85ef 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -49,16 +49,15 @@ * Apply default typography styles. */ .dh-typography() { + --font-body: @font-body; + font-family: @font-body; + h1 { --dh-input-color-text: @color-text-emphatic; font-family: @font-title; margin: 0; border: none; font-weight: normal; - input[type='text'], - .input[contenteditable] { - font-family: @font-title; - } } h2, @@ -86,22 +85,17 @@ font-weight: normal; } - p, - span, - textarea, - label, - select, + // todo: determine if we can remove these or replace with font-family: inherit multi-select .tags .tag, table, - fieldset legend, - input[type='text'], - input[type='number'], - input[type='search'], - .tagify__dropdown, - li { + .tagify__dropdown { font-family: @font-body; } + textarea { + font-family: inherit; + } + button span { font-weight: bold; } From 44b2adf296c2fbdaeff8cbd4233d0181cbade18f Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 13 Jun 2026 06:11:08 -0400 Subject: [PATCH 26/63] Fix dragging currency to other actors (#1998) --- module/applications/dialogs/itemTransfer.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/applications/dialogs/itemTransfer.mjs b/module/applications/dialogs/itemTransfer.mjs index 42e3a727..46ceafbd 100644 --- a/module/applications/dialogs/itemTransfer.mjs +++ b/module/applications/dialogs/itemTransfer.mjs @@ -39,6 +39,7 @@ export default class ItemTransferDialog extends HandlebarsApplicationMixin(Appli const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew; const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null; const max = item?.system.quantity ?? originActor.system.gold[currency] ?? 0; + const isQuantifiable = targetActor.system.metadata.quantifiable?.includes(item?.type); return { originActor, @@ -46,7 +47,7 @@ export default class ItemTransferDialog extends HandlebarsApplicationMixin(Appli itemImage: item?.img, currencyIcon: currencySetting?.icon, max, - initial: targetActor.system.metadata.quantifiable?.includes(item.type) ? max : 1, + initial: isQuantifiable || !!currencySetting ? max : 1, title: item?.name ?? currencySetting?.label }; } From baafa6b9894ad2ea8e9aafdf1e6aaa08251195f7 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 13 Jun 2026 12:44:00 +0200 Subject: [PATCH 27/63] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 014af29a..a471683b 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.2", + "version": "2.3.3", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -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.2/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.3/system.zip", "authors": [ { "name": "WBHarry" From 2c093106947e5543ed11e82dbdc97938e52166b3 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 14 Jun 2026 06:23:45 -0400 Subject: [PATCH 28/63] Fix adding experiences to a roll (#2002) --- module/dice/d20Roll.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index c0069822..e9c447e4 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -107,6 +107,12 @@ export default class D20Roll extends DHRoll { this.options.data.companion; actorExperiences = companion?.system?.experiences ?? {}; } + for (const m of this.options.experiences?.filter(m => !!actorExperiences[m]) ?? []) { + this.options.roll.modifiers.push({ + label: actorExperiences[m].name, + value: actorExperiences[m].value + }); + } this.addModifiers(); if (this.options.extraFormula) { From 500dcb22142b5dfb856711779f36d98b83fb2617 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 14 Jun 2026 06:26:29 -0400 Subject: [PATCH 29/63] Fix updating hope/fear on companion rolls (#2003) --- module/applications/characterCreation/characterCreation.mjs | 1 - module/applications/sheets/actors/companion.mjs | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index b068bf7a..9d27fbb7 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -1,6 +1,5 @@ import { abilities } from '../../config/actorConfig.mjs'; import { burden } from '../../config/generalConfig.mjs'; -import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; diff --git a/module/applications/sheets/actors/companion.mjs b/module/applications/sheets/actors/companion.mjs index a01b4a64..4b5b5a56 100644 --- a/module/applications/sheets/actors/companion.mjs +++ b/module/applications/sheets/actors/companion.mjs @@ -62,9 +62,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet { await this.document.update({ 'system.resources.stress.value': newValue }); } - /** - * - */ + /** @this {DhCompanionSheet} **/ static async #actionRoll(event) { const partner = this.actor.system.partner; const config = { @@ -80,6 +78,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet { const result = await partner.diceRoll(config); this.consumeResource(result?.costs); + result?.resourceUpdates.updateResources(); } // Remove when Action Refactor part #2 done From b08cc696eeb3b7ebd1ac300372ca1be9f74a909e Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 14 Jun 2026 12:34:55 +0200 Subject: [PATCH 30/63] Fixed ItemCompendium errors from rendering of filter fields without defined fields --- templates/ui/itemBrowser/filterContainer.hbs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/templates/ui/itemBrowser/filterContainer.hbs b/templates/ui/itemBrowser/filterContainer.hbs index da8e8cfe..64784bf1 100644 --- a/templates/ui/itemBrowser/filterContainer.hbs +++ b/templates/ui/itemBrowser/filterContainer.hbs @@ -9,13 +9,15 @@
{{else}} - {{#if filtered }} - {{formField field localize=true blank="" name=name choices=(@root.formatChoices this) valueAttr="value" dataset=(object key=key) value=value}} - {{else}} - {{#if field.label}} - {{formField field localize=true blank="" name=name dataset=(object key=key) value=value}} + {{#if field}} + {{#if filtered }} + {{formField field localize=true blank="" name=name choices=(@root.formatChoices this) valueAttr="value" dataset=(object key=key) value=value}} {{else}} - {{formField field localize=true blank="" name=name dataset=(object key=key) label=label value=value}} + {{#if field.label}} + {{formField field localize=true blank="" name=name dataset=(object key=key) value=value}} + {{else}} + {{formField field localize=true blank="" name=name dataset=(object key=key) label=label value=value}} + {{/if}} {{/if}} {{/if}} {{/if}} From af49c1f4c883d2b15d7e156d24545aba5e51d440 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 14 Jun 2026 12:37:45 +0200 Subject: [PATCH 31/63] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index a471683b..f5e13a62 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.3", + "version": "2.3.4", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -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.3/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.4/system.zip", "authors": [ { "name": "WBHarry" From 96f090bef5b49385b4dbac2160704c797e2ed8f0 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 14 Jun 2026 16:10:49 -0400 Subject: [PATCH 32/63] Enable no unused vars and add more types (#2005) --- .vscode/settings.json | 3 ++ daggerheart.d.ts | 40 +++++++++++++++++++ eslint.config.mjs | 18 ++++++++- jsconfig.json | 2 +- .../dialogs/damageReductionDialog.mjs | 1 - module/applications/levelup/levelup.mjs | 1 - .../settings/appearanceSettings.mjs | 3 +- module/applications/sheets/actors/_types.d.ts | 8 ++++ module/applications/sheets/api/base-actor.mjs | 6 ++- module/data/actor/_types.d.ts | 14 +++++++ module/data/actor/base.mjs | 10 ++++- module/data/fields/formulaField.mjs | 2 +- module/data/item/_types.d.ts | 8 ++++ module/data/registeredTriggers.mjs | 2 +- module/dice/dhRoll.mjs | 8 ++++ module/documents/_types.d.ts | 22 ++++++++++ module/documents/activeEffect.mjs | 2 +- module/documents/actor.mjs | 16 +------- module/enrichers/FateRollEnricher.mjs | 2 +- module/helpers/utils.mjs | 4 +- 20 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 module/applications/sheets/actors/_types.d.ts create mode 100644 module/data/actor/_types.d.ts create mode 100644 module/data/item/_types.d.ts create mode 100644 module/documents/_types.d.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b472f383 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "js/ts.preferGoToSourceDefinition": true +} \ No newline at end of file diff --git a/daggerheart.d.ts b/daggerheart.d.ts index 7ff7fd59..1641faa8 100644 --- a/daggerheart.d.ts +++ b/daggerheart.d.ts @@ -2,6 +2,7 @@ import '@client/global.mjs'; import '@common/global.mjs'; import '@common/primitives/global.mjs'; import Canvas from '@client/canvas/board.mjs'; +import { ResourceUpdateMap } from './module/data/action/baseAction.mjs'; // Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such // This declare global hopefully fixes that @@ -39,4 +40,43 @@ declare global { const Collection: foundry.utils.Collection; const FormDataExtended: foundry.applications.ux.FormDataExtended; const TextEditor: foundry.applications.ux.TextEditor; + + /** + * Data used to build rolls such as duality rolls. The definition is incomplete and likely incorrect. + * Objects will often accept a Partial and spit out a non-partial. Those that are not guaranteed should be marked optional. + */ + interface RollConfig { + // unverified, check which ones are used and optional/not optional + event: Event; + title: string; + roll: { + modifier: number; + simple: boolean; + type: string; + difficulty: number; + }; + hasDamage: boolean; + hasEffect: boolean; + hasRoll: boolean; + chatMessage: { + template: string; + mute: boolean; + }; + targets: object; + costs: object; + + // verified + source?: { + /** uuid of the actor this roll is coming from */ + actor: string; + }; + /** Roll data associated with the actor or item */ + data: object; + resourceUpdates: ResourceUpdateMap; + hooks: string[]; + dialog: { + configure: boolean; + }; + damageOptions: object; + } } diff --git a/eslint.config.mjs b/eslint.config.mjs index 3c9b8fd9..74141cc8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -90,12 +90,26 @@ export default defineConfig([ }, rules: { 'no-undef': 'error', - // 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + 'no-unused-vars': [ + 'error', + { + args: 'none', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_[A-Z]', + ignoreRestSiblings: true + } + ], ...stylisticRules } }, { files: ['**/*.ts'], - extends: [js.configs.recommended, tseslint.configs.recommended] + extends: [js.configs.recommended, tseslint.configs.recommended], + plugins: { + '@stylistic': stylistic + }, + rules: { + ...stylisticRules + } } ]); diff --git a/jsconfig.json b/jsconfig.json index a0d51d0b..42c4b404 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -8,7 +8,7 @@ } }, "exclude": ["node_modules", "**/node_modules/*"], - "include": ["daggerheart.mjs", "foundry/client/client.mjs", "daggerheart.d.ts"], + "include": ["daggerheart.mjs", "foundry/client/client.mjs", "daggerheart.d.ts", "module/**/*.d.ts"], "typeAcquisition": { "include": ["jquery"] } diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index e5108e34..8cb026f0 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -176,7 +176,6 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } static updateData(event, _, formData) { - const form = foundry.utils.expandObject(formData.object); this.render(true); } diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index cafc5c89..f40609a4 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -1,4 +1,3 @@ -import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs'; import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; diff --git a/module/applications/settings/appearanceSettings.mjs b/module/applications/settings/appearanceSettings.mjs index 9de9e752..f2defffb 100644 --- a/module/applications/settings/appearanceSettings.mjs +++ b/module/applications/settings/appearanceSettings.mjs @@ -1,12 +1,13 @@ -import DhAppearance from '../../data/settings/Appearance.mjs'; import { getDiceSoNicePreset } from '../../config/generalConfig.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; /** + * @import DhAppearance from '../../data/settings/Appearance.mjs'; * @import {ApplicationClickAction} from "@client/applications/_types.mjs" */ +/** Settings menu for appearance settings */ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(ApplicationV2) { /**@inheritdoc */ static DEFAULT_OPTIONS = { diff --git a/module/applications/sheets/actors/_types.d.ts b/module/applications/sheets/actors/_types.d.ts new file mode 100644 index 00000000..89adc57b --- /dev/null +++ b/module/applications/sheets/actors/_types.d.ts @@ -0,0 +1,8 @@ +import DhCompanion from '../../../data/actor/companion.mjs'; +import DhpActor from '../../../documents/actor.mjs'; + +declare module './companion.mjs' { + export default interface DhCompanionSheet { + actor: DhpActor; + } +} diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 027dd397..e65745c0 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -1,10 +1,12 @@ import { getDocFromElement, itemIsIdentical } from '../../../helpers/utils.mjs'; -import DHBaseActorSettings from './actor-setting.mjs'; import DHApplicationMixin from './application-mixin.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; -/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ +/** + * @import DHBaseActorSettings from './actor-setting.mjs'; + * @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction + */ /** * A base actor sheet extending {@link ActorSheetV2} via {@link DHApplicationMixin} diff --git a/module/data/actor/_types.d.ts b/module/data/actor/_types.d.ts new file mode 100644 index 00000000..fec6e638 --- /dev/null +++ b/module/data/actor/_types.d.ts @@ -0,0 +1,14 @@ +import DhpActor from '../../documents/actor.mjs' +import DhCharacter from './character.mjs'; + +declare module './base.mjs' { + export default interface BaseDataActor { + parent: DhpActor; + } +} + +declare module './companion.mjs' { + export default interface DhCompanion { + partner: DhpActor; + } +} diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 9efbe7d7..995708eb 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -1,9 +1,13 @@ -import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; -import DHItem from '../../documents/item.mjs'; import { createShallowProxy, getScrollTextData } from '../../helpers/utils.mjs'; const fields = foundry.data.fields; +/** + * @import DHItem from '../../documents/item.mjs'; + * @import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; + */ + +/** Function to generate resistance fields for damage types */ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) => new fields.SchemaField({ resistance: new fields.BooleanField({ @@ -96,6 +100,8 @@ export const commonActorRules = (extendedData = { damageReduction: {}, attack: { * @property {Boolean} isNPC - This data model represents a NPC? * @property {typeof DHBaseActorSettings} settingSheet - The sheet class used to render the settings UI for this actor type. */ + +/** Base actor type data model for all actors in Daggerheart */ export default class BaseDataActor extends foundry.abstract.TypeDataModel { /** @returns {ActorDataModelMetadata}*/ static get metadata() { diff --git a/module/data/fields/formulaField.mjs b/module/data/fields/formulaField.mjs index 85922f1f..c465bec3 100644 --- a/module/data/fields/formulaField.mjs +++ b/module/data/fields/formulaField.mjs @@ -39,7 +39,7 @@ export default class FormulaField extends foundry.data.fields.StringField { let roll = null; try { roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, '1')); - } catch (_) { + } catch { roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, 'd6')); } roll.evaluateSync({ strict: false }); diff --git a/module/data/item/_types.d.ts b/module/data/item/_types.d.ts new file mode 100644 index 00000000..6004e13b --- /dev/null +++ b/module/data/item/_types.d.ts @@ -0,0 +1,8 @@ +import DHItem from '../../documents/item.mjs'; + +declare module './base.mjs' { + export default interface BaseDataItem { + parent: DHItem; + actor: DhpActor; + } +} \ No newline at end of file diff --git a/module/data/registeredTriggers.mjs b/module/data/registeredTriggers.mjs index ab86351c..0b637930 100644 --- a/module/data/registeredTriggers.mjs +++ b/module/data/registeredTriggers.mjs @@ -149,7 +149,7 @@ export default class RegisteredTriggers extends Map { const result = await command(...args); if (result?.updates?.length) updates.push(...result.updates); - } catch (_) { + } catch { const triggerName = game.i18n.localize(triggerData.label); ui.notifications.error( game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', { diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index c28db98f..1c6ec829 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -23,6 +23,10 @@ export default class DHRoll extends Roll { static DefaultDialog = D20RollDialog; + /** + * @param {Partial} config + * @returns {Promise} + */ static async build(config = {}, message = {}) { const roll = await this.buildConfigure(config, message); if (!roll) return; @@ -34,6 +38,10 @@ export default class DHRoll extends Roll { return config; } + /** + * @param {Partial} config + * @returns {Promise} + */ static async buildConfigure(config = {}, message = {}) { config.hooks = [...this.getHooks(), '']; config.dialog ??= {}; diff --git a/module/documents/_types.d.ts b/module/documents/_types.d.ts new file mode 100644 index 00000000..a94d6395 --- /dev/null +++ b/module/documents/_types.d.ts @@ -0,0 +1,22 @@ +import BaseDataActor from '../data/actor/base.mjs' +import DHItem from './item.mjs'; +import BaseDataItem from '../data/item/base.mjs'; +import DhActiveEffect from './activeEffect.mjs'; +import EmbeddedCollection from '@common/abstract/embedded-collection.mjs'; + +declare module './actor.mjs' { + export default interface DhpActor { + system: T; + items: EmbeddedCollection; + effects: EmbeddedCollection; + } +} + +declare module './item.mjs' { + export default interface DHItem { + parent: DhpActor; + actor: DhpActor; + system: T; + effects: EmbeddedCollection; + } +} \ No newline at end of file diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 3518210b..0e7f5d1e 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -212,7 +212,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { try { const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`); result = evl(Roll.MATH_PROXY); - } catch (err) { + } catch { return expression; } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index fb10435f..1642ed30 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -531,21 +531,7 @@ export default class DhpActor extends Actor { } /** - * @param {object} config - * @param {Event} config.event - * @param {string} config.title - * @param {object} config.roll - * @param {number} config.roll.modifier - * @param {boolean} [config.roll.simple=false] - * @param {string} [config.roll.type] - * @param {number} [config.roll.difficulty] - * @param {boolean} [config.hasDamage] - * @param {boolean} [config.hasEffect] - * @param {object} [config.chatMessage] - * @param {string} config.chatMessage.template - * @param {boolean} [config.chatMessage.mute] - * @param {object} [config.targets] - * @param {object} [config.costs] + * @param {Partial} config */ async diceRoll(config) { config.source = { ...(config.source ?? {}), actor: this.uuid }; diff --git a/module/enrichers/FateRollEnricher.mjs b/module/enrichers/FateRollEnricher.mjs index c82bbcb2..8513ed94 100644 --- a/module/enrichers/FateRollEnricher.mjs +++ b/module/enrichers/FateRollEnricher.mjs @@ -49,7 +49,7 @@ export const renderFateButton = async event => { const fateTypeData = getFateTypeData(button.dataset?.fatetype); if (!fateTypeData) ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing')); - const { value: fateType, label: fateTypeLabel } = fateTypeData; + const { value: fateType } = fateTypeData; await enrichedFateRoll( { diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 90a5c29d..3c5192be 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -318,7 +318,7 @@ export function getDocFromElementSync(element) { const target = element.closest('[data-item-uuid]'); try { return foundry.utils.fromUuidSync(target.dataset.itemUuid) ?? null; - } catch (_) { + } catch { return null; } } @@ -377,7 +377,7 @@ export const itemAbleRollParse = (value, actor, item) => { try { return Roll.replaceFormulaData(slicedValue, rollData); - } catch (_) { + } catch { return ''; } }; From 24d83e39ec457235becf1a2955715c3b9ea11a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iohan=20R=2E=20Tr=C3=A9zze?= Date: Tue, 16 Jun 2026 00:33:58 -0300 Subject: [PATCH 33/63] [Feature] Enable sorting for Loadout domain cards (#2004) --- module/data/actor/character.mjs | 4 ++-- templates/sheets/global/partials/domain-card-item.hbs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 0cae662f..8ae78ff8 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -411,8 +411,8 @@ export default class DhCharacter extends DhCreature { get domainCards() { const domainCards = this.parent.items.filter(x => x.type === 'domainCard'); - const loadout = domainCards.filter(x => !x.system.inVault); - const vault = domainCards.filter(x => x.system.inVault); + const loadout = domainCards.filter(x => !x.system.inVault).sort((a, b) => a.sort - b.sort); + const vault = domainCards.filter(x => x.system.inVault).sort((a, b) => a.sort - b.sort); return { loadout: loadout, diff --git a/templates/sheets/global/partials/domain-card-item.hbs b/templates/sheets/global/partials/domain-card-item.hbs index 54e44e64..ca584e57 100644 --- a/templates/sheets/global/partials/domain-card-item.hbs +++ b/templates/sheets/global/partials/domain-card-item.hbs @@ -1,4 +1,4 @@ -
  • +
  • {{item.system.recallCost}} From 3a3aa17a1c03a0ab1d1f2a3dedd338ef349f00a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iohan=20R=2E=20Tr=C3=A9zze?= Date: Tue, 16 Jun 2026 08:06:48 -0300 Subject: [PATCH 34/63] Fix adversary type not updating in actor directory (#2009) --- module/applications/sidebar/tabs/actorDirectory.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/sidebar/tabs/actorDirectory.mjs b/module/applications/sidebar/tabs/actorDirectory.mjs index a6f48b54..4d00afa2 100644 --- a/module/applications/sidebar/tabs/actorDirectory.mjs +++ b/module/applications/sidebar/tabs/actorDirectory.mjs @@ -1,6 +1,6 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.ActorDirectory { static DEFAULT_OPTIONS = { - renderUpdateKeys: ['system.levelData.level.current', 'system.partner', 'system.tier'] + renderUpdateKeys: ['system.levelData.level.current', 'system.partner', 'system.tier', 'system.type'] }; static _entryPartial = 'systems/daggerheart/templates/ui/sidebar/actor-document-partial.hbs'; From d2e87e4eb9edeb0d60235f9c083118e87df5508e Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:23:13 +0200 Subject: [PATCH 35/63] [Feature] CountdownsType Split (#1990) * Toggle pills * Finished animation framework * . * Fixed localization * Fixed iconOnly * Updated SRD Action Countdown types * feat: add shimmer animation when change countdown value * Fixed so that hidden countdowns don't take up space * Fixed countdowns.hbs part using wrong context for iconOnly * Restored glow animation for category chip * Changed back to a single sheen effect * [Review] Move visible countdown types to getter (#1999) * Move visible countdown types to getter * Punchier shimmer animation * Restored encounter/narrative * Lang cleanup * . --------- Co-authored-by: Murilo Brito Co-authored-by: Carlos Fernandez --- lang/en.json | 7 +- module/applications/ui/countdownEdit.mjs | 6 +- module/applications/ui/countdowns.mjs | 135 +++++++++++++++--- module/config/flagsConfig.mjs | 3 +- module/config/generalConfig.mjs | 36 ++--- module/data/countdowns.mjs | 19 ++- module/data/fields/action/countdownField.mjs | 4 +- module/systemRegistration/handlebars.mjs | 1 + module/systemRegistration/migrations.mjs | 4 +- module/systemRegistration/settings.mjs | 5 +- styles/less/ui/countdown/countdown.less | 68 ++++++++- .../ui/{ => countdowns}/countdown-edit.hbs | 4 +- templates/ui/countdowns/countdowns-view.hbs | 24 ++++ .../ui/{ => countdowns/parts}/countdowns.hbs | 28 ++-- 14 files changed, 267 insertions(+), 77 deletions(-) rename templates/ui/{ => countdowns}/countdown-edit.hbs (97%) create mode 100644 templates/ui/countdowns/countdowns-view.hbs rename templates/ui/{ => countdowns/parts}/countdowns.hbs (70%) diff --git a/lang/en.json b/lang/en.json index 9f16828e..5bab0e64 100755 --- a/lang/en.json +++ b/lang/en.json @@ -484,7 +484,6 @@ "startFormula": "Randomized Start Value Formula", "currentCountdownCurrent": "Current: {value}", "currentCountdownStart": "Start: {value}", - "category": "Category", "progressionType": "Progression", "decreasing": "Decreasing", "looping": "Looping", @@ -1162,7 +1161,7 @@ "autoAppliedByLabel": "Max Stress" } }, - "CountdownType": { + "CountdownProgressType": { "actionRoll": "Action Roll", "characterAttack": "Character Attack", "characterSpotlight": "Character Spotlight", @@ -1170,6 +1169,10 @@ "fear": "Fear", "spotlight": "Spotlight" }, + "CountdownType": { + "encounter": { "label": "Short Term", "shortLabel": "Short" }, + "narrative": { "label": "Long Term", "shortLabel": "Long" } + }, "DaggerheartDiceAnimationEvents": { "critical": { "name": "Critical" }, "higher": { "name": "Highest Roll" } diff --git a/module/applications/ui/countdownEdit.mjs b/module/applications/ui/countdownEdit.mjs index 5974e1f2..1dbc56be 100644 --- a/module/applications/ui/countdownEdit.mjs +++ b/module/applications/ui/countdownEdit.mjs @@ -35,7 +35,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio static PARTS = { countdowns: { - template: 'systems/daggerheart/templates/ui/countdown-edit.hbs', + template: 'systems/daggerheart/templates/ui/countdowns/countdown-edit.hbs', scrollable: ['.expanded-view', '.edit-content'] } }; @@ -45,7 +45,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio context.isGM = game.user.isGM; context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels; context.defaultOwnership = this.data.defaultOwnership; - context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes; + context.countdownTypes = CONFIG.DH.GENERAL.countdownTypes; context.countdownProgressionTypes = CONFIG.DH.GENERAL.countdownProgressionTypes; context.countdownLoopingTypes = CONFIG.DH.GENERAL.countdownLoopingTypes; context.hideNewCountdowns = this.hideNewCountdowns; @@ -62,7 +62,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic; acc[key] = { ...countdown, - typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label), + typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownTypes[countdown.type].label), progress: { ...countdown.progress, typeName: game.i18n.localize( diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 2a2a113e..73e36241 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -11,9 +11,15 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; */ export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) { + previousCountdownData = null; + changedCountdownsForAnimation = new Set(); + countdownChangeAnimationTimeout = null; + constructor(options = {}) { super(options); + this.previousCountdownData = + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns; this.setupHooks(); } @@ -32,6 +38,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application }, actions: { toggleViewMode: DhCountdowns.#onToggleViewMode, + toggleCountdownTypes: DhCountdowns.#onToggleCountdownTypes, editCountdowns: DhCountdowns.#onEditCountdowns, loopCountdown: DhCountdowns.#onLoopCountdown, decreaseCountdown: (_, target) => this.editCountdown(false, target), @@ -48,11 +55,20 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application static PARTS = { resources: { root: true, - template: 'systems/daggerheart/templates/ui/countdowns.hbs' + template: 'systems/daggerheart/templates/ui/countdowns/countdowns-view.hbs' } }; - /**@inheritdoc */ + /** + * Returns all visible countdown types + * @returns {string[]} + */ + get visibleCountdownTypes() { + const { encounter, narrative } = CONFIG.DH.GENERAL.countdownTypes; + return game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes) + ?? [encounter.id, narrative.id]; + } + async _renderFrame(options) { const frame = await super._renderFrame(options); @@ -65,6 +81,51 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return frame; } + + async _onRender(context, options) { + await super._onRender(context, options); + + /* Handle rendering/hiding/positioning of the countdown UI */ + this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0; + if (options?.force) { + document.getElementById('ui-right-column-1')?.appendChild(this.element); + } + + this.previousCountdownData = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns) + .countdowns; + + /* Handle animations to draw attention to countdown values changing */ + if (this.changedCountdownsForAnimation.size) { + if (this.countdownChangeAnimationTimeout) + clearTimeout(this.countdownChangeAnimationTimeout); + + this.countdownChangeAnimationTimeout = setTimeout(() => { + this.changedCountdownsForAnimation.clear(); + const selector = '.countdown-container, .header-type-toggles .header-type'; + for (const element of this.element.querySelectorAll(selector)) { + element.classList.remove('change-glow'); + } + }, 3000); + + /* If the countdown is not currently visible, add a glow to the CountdownType pill */ + const visibleTypes = this.visibleCountdownTypes; + for (const countdownKey of this.changedCountdownsForAnimation) { + const countdown = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns) + .countdowns[countdownKey]; + if (!visibleTypes.includes(countdown?.type)) { + this.element.querySelector(`.header-type-toggles .header-type[data-type="${countdown.type}"]`) + .classList.add('change-glow'); + } + + /* If the countdown element is not rendered the user doesn't have permissions to it. No animation needed on the elment itself */ + const countdownElement = this.element.querySelector(`.countdown-container[data-countdown="${countdownKey}"]`); + if (!countdownElement) continue; + + countdownElement.classList.add('change-glow'); + } + } + } + /** Returns countdown data filtered by ownership */ #getCountdowns() { const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); @@ -76,16 +137,10 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE); } - /** @override */ - async _prepareContext(options) { - const context = await super._prepareContext(options); - context.isGM = game.user.isGM; - - context.iconOnly = - game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) === - CONFIG.DH.GENERAL.countdownAppMode.iconOnly; + _getCountdownData() { const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { + + return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { const playersWithAccess = game.users.reduce((acc, user) => { const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown); if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) { @@ -108,7 +163,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application !countdownEditable || (isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0')); - acc[key] = { + acc[countdown.type][key] = { ...countdown, editable: countdownEditable, noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0, @@ -117,7 +172,38 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application loopTooltip: isLooping && game.i18n.localize(loopTooltip) }; return acc; - }, {}); + }, Object.keys(CONFIG.DH.GENERAL.countdownTypes).reduce((acc, key) => { + acc[key] = {}; + return acc; + }, {})); + } + + /** @override */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + context.isGM = game.user.isGM; + + context.iconOnly = + game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) + === CONFIG.DH.GENERAL.countdownAppMode.iconOnly; + + context.userCountdownTypes = this.visibleCountdownTypes; + + context.typeToggles = + Object.values(CONFIG.DH.GENERAL.countdownTypes).map(type => ({ + type: type.id, + label: game.i18n.localize(type.shortLabel), + active: context.userCountdownTypes.includes(type.id) + })); + + context.countdowns = this._getCountdownData(); + context.countdownTypesWithVisibleEntries = this.#getCountdowns().reduce((acc, data) => { + if (context.userCountdownTypes.includes(data.countdown.type) && !acc.includes(data.countdown.type)) + acc.push(data.countdown.type); + + return acc; + }, []); + return context; } @@ -147,6 +233,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return true; } + /** @this {DhCountdowns} */ static async #onToggleViewMode() { const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode); const appMode = CONFIG.DH.GENERAL.countdownAppMode; @@ -158,10 +245,24 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application this.render(); } + /** @this {DhCountdowns} */ + static async #onToggleCountdownTypes(event, target) { + const currentTypes = this.visibleCountdownTypes; + const { type } = target.dataset; + const newTypes = event.shiftKey ? + [type] : + currentTypes.includes(type) ? currentTypes.filter(x => x !== type) : [...currentTypes, type]; + await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes, newTypes); + + this.render(); + } + + /** @this {DhCountdowns} */ static async #onEditCountdowns() { new game.system.api.applications.ui.CountdownEdit().render(true); } + /** @this {DhCountdowns} */ static async #onLoopCountdown(_, target) { if (!DhCountdowns.canPerformEdit()) return; @@ -269,12 +370,4 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application refreshType: RefreshType.Countdown }); } - - async _onRender(context, options) { - await super._onRender(context, options); - this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0; - if (options?.force) { - document.getElementById('ui-right-column-1')?.appendChild(this.element); - } - } } diff --git a/module/config/flagsConfig.mjs b/module/config/flagsConfig.mjs index 817ac89d..cb05bd72 100644 --- a/module/config/flagsConfig.mjs +++ b/module/config/flagsConfig.mjs @@ -24,7 +24,8 @@ export const itemAttachmentSource = 'attachmentSource'; export const userFlags = { welcomeMessage: 'welcome-message', - countdownMode: 'countdown-mode' + countdownMode: 'countdown-mode', + countdownTypeModes: 'countdown-type-modes' }; export const combatToggle = 'combat-toggle-origin'; diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 6fc85ec5..188efafb 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -867,27 +867,27 @@ export const abilityCosts = { export const countdownProgressionTypes = { actionRoll: { id: 'actionRoll', - label: 'DAGGERHEART.CONFIG.CountdownType.actionRoll' + label: 'DAGGERHEART.CONFIG.CountdownProgressType.actionRoll' }, characterAttack: { id: 'characterAttack', - label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack' + label: 'DAGGERHEART.CONFIG.CountdownProgressType.characterAttack' }, characterSpotlight: { id: 'characterSpotlight', - label: 'DAGGERHEART.CONFIG.CountdownType.characterSpotlight' + label: 'DAGGERHEART.CONFIG.CountdownProgressType.characterSpotlight' }, custom: { id: 'custom', - label: 'DAGGERHEART.CONFIG.CountdownType.custom' + label: 'DAGGERHEART.CONFIG.CountdownProgressType.custom' }, fear: { id: 'fear', - label: 'DAGGERHEART.CONFIG.CountdownType.fear' + label: 'DAGGERHEART.CONFIG.CountdownProgressType.fear' }, spotlight: { id: 'spotlight', - label: 'DAGGERHEART.CONFIG.CountdownType.spotlight' + label: 'DAGGERHEART.CONFIG.CountdownProgressType.spotlight' } }; export const rollTypes = { @@ -935,17 +935,6 @@ export const simpleOwnershiplevels = { ...basicOwnershiplevels }; -export const countdownBaseTypes = { - narrative: { - id: 'narrative', - label: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative' - }, - encounter: { - id: 'encounter', - label: 'DAGGERHEART.APPLICATIONS.Countdown.types.encounter' - } -}; - export const countdownLoopingTypes = { noLooping: { id: 'noLooping', @@ -970,6 +959,19 @@ export const countdownAppMode = { iconOnly: 'icon-only' }; +export const countdownTypes = { + encounter: { + id: 'encounter', + label: 'DAGGERHEART.CONFIG.CountdownType.encounter.label', + shortLabel: 'DAGGERHEART.CONFIG.CountdownType.encounter.shortLabel' + }, + narrative: { + id: 'narrative', + label: 'DAGGERHEART.CONFIG.CountdownType.narrative.label', + shortLabel: 'DAGGERHEART.CONFIG.CountdownType.narrative.shortLabel' + } +}; + export const sceneRangeMeasurementSetting = { disable: { id: 'disable', diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 54971d34..6630c302 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -13,6 +13,20 @@ export default class DhCountdowns extends foundry.abstract.DataModel { }) }; } + + handleChange() { + const previousCountdowns = foundry.ui.countdowns.previousCountdownData; + const changedCountdowns = Object.entries(this.countdowns).reduce((acc, [key, countdown]) => { + const previousCountdown = previousCountdowns[key]; + if (!previousCountdown || (previousCountdown.progress.current !== countdown.progress.current)) { + acc.push(key); + } + + return acc; + }, []); + + foundry.ui.countdowns.changedCountdownsForAnimation.add(...changedCountdowns); + } } export class DhCountdown extends foundry.abstract.DataModel { @@ -21,7 +35,8 @@ export class DhCountdown extends foundry.abstract.DataModel { return { type: new fields.StringField({ required: true, - choices: CONFIG.DH.GENERAL.countdownBaseTypes, + choices: CONFIG.DH.GENERAL.countdownTypes, + initial: CONFIG.DH.GENERAL.countdownTypes.encounter.id, label: 'DAGGERHEART.GENERAL.type' }), name: new fields.StringField({ @@ -85,7 +100,7 @@ export class DhCountdown extends foundry.abstract.DataModel { : undefined; return { - type: type ?? CONFIG.DH.GENERAL.countdownBaseTypes.narrative.id, + type: type ?? CONFIG.DH.GENERAL.countdownTypes.encounter.id, name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'), img: 'icons/magic/time/hourglass-yellow-green.webp', ownership: ownership, diff --git a/module/data/fields/action/countdownField.mjs b/module/data/fields/action/countdownField.mjs index 96d9dd91..53e132c9 100644 --- a/module/data/fields/action/countdownField.mjs +++ b/module/data/fields/action/countdownField.mjs @@ -8,8 +8,8 @@ export default class CountdownField extends fields.ArrayField { ...game.system.api.data.countdowns.DhCountdown.defineSchema(), type: new fields.StringField({ required: true, - choices: CONFIG.DH.GENERAL.countdownBaseTypes, - initial: CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id, + choices: CONFIG.DH.GENERAL.countdownTypes, + initial: CONFIG.DH.GENERAL.countdownTypes.encounter.id, label: 'DAGGERHEART.GENERAL.type' }), name: new fields.StringField({ diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 1b08e0ad..bf315358 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -50,6 +50,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/ui/chat/parts/target-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', + 'systems/daggerheart/templates/ui/countdowns/parts/countdowns.hbs', 'systems/daggerheart/templates/scene/dh-config.hbs', 'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs', 'systems/daggerheart/templates/sheets/activeEffect/typeChanges/armorChange.hbs' diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index b718a127..ec546c92 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -178,8 +178,8 @@ export async function runMigrations() { await countdownSettings.updateSource({ countdowns: { - ...getCountdowns(countdownSettings.narrative, CONFIG.DH.GENERAL.countdownBaseTypes.narrative.id), - ...getCountdowns(countdownSettings.encounter, CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id) + ...getCountdowns(countdownSettings.narrative, 'narrative'), + ...getCountdowns(countdownSettings.encounter, 'encounter') } }); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSettings); diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index a66323c7..1a985274 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -199,7 +199,10 @@ const registerNonConfigSettings = () => { game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, { scope: 'world', config: false, - type: DhCountdowns + type: DhCountdowns, + onChange: value => { + value.handleChange(); + } }); game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings, { diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 63e539ba..d7cc83ff 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -22,7 +22,6 @@ pointer-events: all; align-self: flex-end; transition: 0.3s right ease-in-out; - display: flex; flex-direction: column; @@ -65,12 +64,35 @@ padding: 0 0.5rem; overflow: hidden; font-size: var(--font-size-13); + .window-title { font-family: @font-body; - flex: 1; } - .header-control + .header-control { - margin-left: 8px; + + + .header-type-toggles { + flex: 1; + display: flex; + align-items: center; + gap: 4px; + + .header-type { + flex: 1; + border: 1px solid @beige; + border-radius: 12px; + padding: 2px 4px; + text-align: center; + background-color: @dark-blue; + color: @beige; + + &.inactive { + opacity: 0.4; + } + + &.change-glow { + animation: glow 1s ease-in-out infinite; + } + } } } @@ -82,9 +104,27 @@ overflow: auto; max-height: 312px; + .countdown-category-container { + display: flex; + flex-direction: column; + gap: 8px; + + &.hidden { + display: none; + } + } + .countdown-container { display: flex; width: 100%; + border-radius: 6px; + + &.change-glow { + animation: shimmer 1s ease-out; + background: linear-gradient(-45deg, transparent 30%, light-dark(@dark-blue-40, @golden-40) 35%, transparent 40%); + background-size: 300%; + background-position-x: 100% + } &.icon-only { gap: 8px; @@ -105,6 +145,7 @@ display: flex; align-items: center; gap: 16px; + border-radius: 6px; img { width: 2.75rem; @@ -184,4 +225,23 @@ // } } } + + @keyframes glow { + 0% { + box-shadow: 0 0 1px 1px @golden; + } + + 100% { + box-shadow: 0 0 2px 2px @golden; + } + } + + @keyframes shimmer { + from { + background-position-x: 98%; + } + to { + background-position-x: 0% + } + } } diff --git a/templates/ui/countdown-edit.hbs b/templates/ui/countdowns/countdown-edit.hbs similarity index 97% rename from templates/ui/countdown-edit.hbs rename to templates/ui/countdowns/countdown-edit.hbs index 6b7d22d4..eebefe11 100644 --- a/templates/ui/countdown-edit.hbs +++ b/templates/ui/countdowns/countdown-edit.hbs @@ -78,9 +78,9 @@
  • - +
    diff --git a/templates/ui/countdowns/countdowns-view.hbs b/templates/ui/countdowns/countdowns-view.hbs new file mode 100644 index 00000000..7727c4b6 --- /dev/null +++ b/templates/ui/countdowns/countdowns-view.hbs @@ -0,0 +1,24 @@ +
    +
    + + {{#unless iconOnly}} + {{localize "DAGGERHEART.UI.Countdowns.title"}} + {{/unless}} + + {{#if isGM}} + + + + {{/if}} + + + +
    +
    + {{> "systems/daggerheart/templates/ui/countdowns/parts/countdowns.hbs" }} +
    +
    \ No newline at end of file diff --git a/templates/ui/countdowns.hbs b/templates/ui/countdowns/parts/countdowns.hbs similarity index 70% rename from templates/ui/countdowns.hbs rename to templates/ui/countdowns/parts/countdowns.hbs index 4a77dfd7..4f0ee086 100644 --- a/templates/ui/countdowns.hbs +++ b/templates/ui/countdowns/parts/countdowns.hbs @@ -1,23 +1,11 @@ -
    -
    - - {{localize "DAGGERHEART.UI.Countdowns.title"}} - {{#if isGM}} - - - - {{/if}} - - - -
    -
    - {{#each countdowns as | countdown id |}} -
    +{{#each countdowns as | category key |}} +
    + {{#each category as |countdown id|}} +
    - +
    - {{#unless ../iconOnly}} + {{#unless @root.iconOnly}}
    {{countdown.name}}
    {{/unless}}
    @@ -29,7 +17,7 @@ {{#if countdown.editable}}{{/if}}
    - {{#if (not ../iconOnly)}} + {{#if (not @root.iconOnly)}} {{#if (and countdown.noPlayerAccess @root.isGM)}} {{/if}} @@ -53,4 +41,4 @@
    {{/each}}
    -
    \ No newline at end of file +{{/each}} \ No newline at end of file From 975c6558287df6079cf9875aeb6da523f95448da Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 17 Jun 2026 00:13:18 +0200 Subject: [PATCH 36/63] [Feature] Improved Countdown Animations (#2010) --- module/applications/ui/countdowns.mjs | 59 ++++++++++++++----------- module/data/countdowns.mjs | 3 +- styles/less/ui/countdown/countdown.less | 33 ++------------ 3 files changed, 37 insertions(+), 58 deletions(-) diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 73e36241..d559582f 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -13,7 +13,6 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) { previousCountdownData = null; changedCountdownsForAnimation = new Set(); - countdownChangeAnimationTimeout = null; constructor(options = {}) { super(options); @@ -95,35 +94,41 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application .countdowns; /* Handle animations to draw attention to countdown values changing */ - if (this.changedCountdownsForAnimation.size) { - if (this.countdownChangeAnimationTimeout) - clearTimeout(this.countdownChangeAnimationTimeout); + const typesToAnimate = new Set(); + for (const countdownKey of this.changedCountdownsForAnimation) { + const shimmerAnimation = [ + { backgroundPositionX: '98%' }, + { backgroundPositionX: '0%' } + ]; + const shimmerTiming = { + duration: 1000, + iterations: 1 + }; - this.countdownChangeAnimationTimeout = setTimeout(() => { - this.changedCountdownsForAnimation.clear(); - const selector = '.countdown-container, .header-type-toggles .header-type'; - for (const element of this.element.querySelectorAll(selector)) { - element.classList.remove('change-glow'); - } - }, 3000); + const element = this.element.querySelector(`.countdown-container[data-countdown="${countdownKey}"]`); + element?.animate(shimmerAnimation, shimmerTiming); - /* If the countdown is not currently visible, add a glow to the CountdownType pill */ - const visibleTypes = this.visibleCountdownTypes; - for (const countdownKey of this.changedCountdownsForAnimation) { - const countdown = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns) - .countdowns[countdownKey]; - if (!visibleTypes.includes(countdown?.type)) { - this.element.querySelector(`.header-type-toggles .header-type[data-type="${countdown.type}"]`) - .classList.add('change-glow'); - } - - /* If the countdown element is not rendered the user doesn't have permissions to it. No animation needed on the elment itself */ - const countdownElement = this.element.querySelector(`.countdown-container[data-countdown="${countdownKey}"]`); - if (!countdownElement) continue; - - countdownElement.classList.add('change-glow'); - } + const countdown = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns) + .countdowns[countdownKey]; + if (!this.visibleCountdownTypes.includes(countdown?.type)) + typesToAnimate.add(countdown.type); } + + for (const type of typesToAnimate) { + const pulseAnimation = [ + { boxShadow: '0 0 1px 1px var(--golden)' }, + { boxShadow: '0 0 2px 2px var(--golden)' } + ]; + const pulseTiming = { + duration: 1000, + iterations: 3 + }; + + const element = this.element.querySelector(`.header-type-toggles .header-type[data-type="${type}"]`); + element?.animate(pulseAnimation, pulseTiming); + } + + this.changedCountdownsForAnimation.clear(); } /** Returns countdown data filtered by ownership */ diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 6630c302..8e55ed31 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -25,7 +25,8 @@ export default class DhCountdowns extends foundry.abstract.DataModel { return acc; }, []); - foundry.ui.countdowns.changedCountdownsForAnimation.add(...changedCountdowns); + for (const countdownKey of changedCountdowns) + foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey); } } diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index d7cc83ff..96e01ffd 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -88,10 +88,6 @@ &.inactive { opacity: 0.4; } - - &.change-glow { - animation: glow 1s ease-in-out infinite; - } } } } @@ -118,13 +114,9 @@ display: flex; width: 100%; border-radius: 6px; - - &.change-glow { - animation: shimmer 1s ease-out; - background: linear-gradient(-45deg, transparent 30%, light-dark(@dark-blue-40, @golden-40) 35%, transparent 40%); - background-size: 300%; - background-position-x: 100% - } + background: linear-gradient(-45deg, transparent 30%, light-dark(@dark-blue-40, @golden-40) 35%, transparent 40%); + background-size: 300%; + background-position-x: 100%; &.icon-only { gap: 8px; @@ -225,23 +217,4 @@ // } } } - - @keyframes glow { - 0% { - box-shadow: 0 0 1px 1px @golden; - } - - 100% { - box-shadow: 0 0 2px 2px @golden; - } - } - - @keyframes shimmer { - from { - background-position-x: 98%; - } - to { - background-position-x: 0% - } - } } From 9e25b7eff0889c6a961450cd692b66a38d066833 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 18 Jun 2026 17:04:57 -0400 Subject: [PATCH 37/63] Tweak spacing of adversary header elements (#2011) --- .../sheets/actors/actor-sheet-shared.less | 4 +++ .../less/sheets/actors/adversary/header.less | 28 +++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index b3eb0469..89617103 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -37,6 +37,10 @@ color: @color-text-subtle; } + .window-header > .attribution-header-label { + margin-right: var(--spacer-4); + } + .tab.inventory { .gold-section { display: grid; diff --git a/styles/less/sheets/actors/adversary/header.less b/styles/less/sheets/actors/adversary/header.less index 1e5e4fa5..549a219d 100644 --- a/styles/less/sheets/actors/adversary/header.less +++ b/styles/less/sheets/actors/adversary/header.less @@ -3,16 +3,21 @@ .application.sheet.daggerheart.actor.dh-style.adversary { .adversary-header-sheet { - padding: 0 15px; padding-top: var(--header-height); width: 100%; + > *:not(line-div, .tab-navigation) { + padding-left: 15px; + padding-right: 15px; + } + .name-row { display: flex; gap: 5px; align-items: center; justify-content: space-between; - padding: 8px 0; + padding-top: var(--spacer-4); + padding-bottom: var(--spacer-4); flex: 1; h1 { @@ -34,8 +39,8 @@ .tags { display: flex; - gap: 10px; - padding-bottom: 8px; + gap: 8px; + padding-bottom: var(--spacer-12); .tag { display: flex; @@ -44,8 +49,7 @@ justify-content: center; align-items: center; padding: 3px 5px; - font-size: var(--font-size-12); - font: @font-body; + font: var(--font-size-12) @font-body; background: light-dark(@dark-15, @beige-15); border: 1px solid light-dark(@dark, @beige); @@ -64,8 +68,16 @@ .adversary-info { display: flex; flex-direction: column; - gap: 12px; - padding: 16px 0; + gap: var(--spacer-8); + padding-top: var(--spacer-12); + padding-bottom: var(--spacer-12); + } + + .tab-navigation { + margin-top: 0; + button[data-action="openSettings"] { + margin-right: 12px; + } } } } From 961e124ef2fdfde1881769e11042defaae22034c Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 18 Jun 2026 19:05:26 -0400 Subject: [PATCH 38/63] Fade adversary notes tab when notes are empty (#2015) --- module/applications/sheets/actors/adversary.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index c8d5a299..f39bec0c 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -131,6 +131,15 @@ export default class AdversarySheet extends DHBaseActorSheet { return context; } + /** @inheritdoc */ + _prepareTabs(group) { + const result = super._prepareTabs(group); + if (group === 'primary') { + result.notes.empty = !this.document.system.notes?.trim(); + } + return result; + } + /**@inheritdoc */ _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); From 0b7ae8a76c1dfd92d3459d17005b540389622e17 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 19 Jun 2026 04:46:20 -0400 Subject: [PATCH 39/63] Increase size of pause image and text (#2012) --- styles/less/ui/game-pause/game-pause.less | 17 ++++++++++++++--- styles/less/utils/fonts.less | 1 + 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/styles/less/ui/game-pause/game-pause.less b/styles/less/ui/game-pause/game-pause.less index 60d08bec..daf9a1e4 100644 --- a/styles/less/ui/game-pause/game-pause.less +++ b/styles/less/ui/game-pause/game-pause.less @@ -1,23 +1,34 @@ @import '../../utils/mixin.less'; #pause.dh-style { + animation: none; + + img { + width: 125px; + height: 125px; + } + figcaption { + --base-shadow: drop-shadow(2px 2px 2px black); position: absolute; margin-top: 24px; animation: pause-pulse 3s ease-in-out infinite; + font-size: var(--font-size-30); + letter-spacing: 0.18em; + filter: var(--base-shadow); } @keyframes pause-pulse { 0% { - filter: drop-shadow(0 0 5px @secondary-blue); + filter: var(--base-shadow) drop-shadow(0 0 5px @secondary-blue); } 50% { - filter: drop-shadow(0 0 5px @golden); + filter: var(--base-shadow) drop-shadow(0 0 5px @golden-secondary); } 100% { - filter: drop-shadow(0 0 5px @secondary-blue); + filter: var(--base-shadow) drop-shadow(0 0 5px @secondary-blue); } } } \ No newline at end of file diff --git a/styles/less/utils/fonts.less b/styles/less/utils/fonts.less index 07da3389..72ee1a85 100755 --- a/styles/less/utils/fonts.less +++ b/styles/less/utils/fonts.less @@ -10,6 +10,7 @@ --font-size-8: 0.5rem; --font-size-9: 0.5625rem; --font-size-22: 1.375rem; + --font-size-30: 1.875rem; } @font-title: ~"var(--dh-font-title, 'Cinzel Decorative'), serif"; From b64e600a6b667620b4165c56f2fe8291a0dd51e6 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 20 Jun 2026 13:44:39 -0400 Subject: [PATCH 40/63] Fix old armorscore AEs clobbering data (#2018) --- module/data/actor/character.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 8ae78ff8..3b12da6f 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -315,7 +315,12 @@ export default class DhCharacter extends DhCreature { label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice' }) }) - }) + }), + /** Accumulated armor score from all sources */ + armorScore: new fields.SchemaField({ + value: new fields.NumberField(), + max: new fields.NumberField() + }, { persisted: false }) }; } From 6f1da427352a55f4eedc7642b6155bd4f8035431 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 20 Jun 2026 13:48:12 -0400 Subject: [PATCH 41/63] Make dragging features work more seamlessly (#2016) --- .../sheets-configs/adversary-settings.mjs | 2 +- .../applications/sheets/actors/adversary.mjs | 2 +- .../sheets/api/application-mixin.mjs | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index 57405675..cd627f1c 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -54,7 +54,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings { async _prepareContext(options) { const context = await super._prepareContext(options); - const featureForms = ['passive', 'action', 'reaction']; + const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); 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) diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index f39bec0c..85380392 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -103,7 +103,7 @@ export default class AdversarySheet extends DHBaseActorSheet { context.resources.stress.emptyPips = context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; - const featureForms = ['passive', 'action', 'reaction']; + const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); 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) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 752dc80b..876278fa 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -387,6 +387,30 @@ export default function DHApplicationMixin(Base) { return super._onDrop?.(event); } + /** @inheritdoc */ + _onSortItem(event, item) { + // If we are dragging a feature past its allowed feature form, put it in the front or in the back + const doc = this.actor.items.get(item.id); + const dropTargetEl = event.target.closest('[data-item-id]'); + const dropTarget = this.actor.items.get(dropTargetEl?.dataset.itemId); + if (doc?.type === 'feature' && dropTarget?.type === 'feature' && doc.system.featureForm !== dropTarget.system.featureForm) { + const siblings = this.actor.itemTypes.feature + .filter(f => f.system.featureForm === doc.system.featureForm) + .sort((a, b) => a.sort - b.sort); + if (siblings.length > 1) { + const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); + const thisFeatureIdx = featureForms.indexOf(doc.system.featureForm); + const targetFeatureIdx = featureForms.indexOf(dropTarget.system.featureForm); + const target = targetFeatureIdx < thisFeatureIdx ? siblings[0] : siblings.at(-1); + const sortUpdates = foundry.utils.performIntegerSort(doc, { target, siblings }); + const updateData = sortUpdates.map(u => ({ ...u.update, _id: u.target._id })); + return this.actor.updateEmbeddedDocuments('Item', updateData); + } + } + + return super._onSortItem?.(event, item); + } + /* -------------------------------------------- */ /* Context Menu */ /* -------------------------------------------- */ From 6b80a6243c81f2d550353ae57fe7552c14ddcd95 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 20 Jun 2026 13:55:51 -0400 Subject: [PATCH 42/63] Support drag resort on features in actor setting sheets (#2023) --- .../sheets-configs/adversary-settings.mjs | 28 -------------- .../sheets-configs/environment-settings.mjs | 35 ++++++++---------- .../sheets-configs/npc-settings.mjs | 30 --------------- .../sheets/actors/environment.mjs | 1 - .../applications/sheets/api/actor-setting.mjs | 37 ++++++++++++++++--- .../sheets/api/application-mixin.mjs | 3 +- .../adversary-settings/features.hbs | 2 +- .../environment-settings/features.hbs | 2 +- .../sheets-settings/npc-settings/features.hbs | 2 +- 9 files changed, 52 insertions(+), 88 deletions(-) diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index cd627f1c..583f37b7 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -97,32 +97,4 @@ export default class DHAdversarySettings extends DHBaseActorSettings { await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del }); } - - 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-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index 6d74f9c6..d6744eb8 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -15,7 +15,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { dragDrop: [ { dragSelector: null, dropSelector: '.category-container' }, { dragSelector: null, dropSelector: '.tab.features' }, - { dragSelector: '.feature-item', dropSelector: null } + { dragSelector: '.feature-item, .inventory-item[data-type="adversary"]', dropSelector: null } ] }; @@ -110,33 +110,30 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { } 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); + const element = event.currentTarget.closest('.inventory-item[data-type=adversary]'); + if (element) { + const adversaryData = { type: 'Actor', uuid: element.dataset.itemUuid }; + event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData)); + event.dataTransfer.setDragImage(element, 60, 0); + } else { + return super._onDragStart(event); } } async _onDrop(event) { event.stopPropagation(); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); - const item = await fromUuid(data.uuid); - if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return; - - if (item.type === 'adversary' && event.target.closest('.category-container')) { + const doc = await fromUuid(data.uuid); + if (doc?.type === 'adversary' && event.target.closest('.category-container')) { const target = event.target.closest('.category-container'); const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; const current = foundry.utils.getProperty(this.actor, path).map(x => x.uuid); - await this.actor.update({ - [path]: [...current, item.uuid] - }); - this.render(); - } else if (item.type === 'feature' && event.target.closest('.tab.features')) { - await this.actor.createEmbeddedDocuments('Item', [item]); - this.render(); + if (!current.includes(doc.uuid)) { + await this.actor.update({ [path]: [...current, doc.uuid] }); + } + return; } + + return super._onDrop(event); } } diff --git a/module/applications/sheets-configs/npc-settings.mjs b/module/applications/sheets-configs/npc-settings.mjs index c187877c..d2132a91 100644 --- a/module/applications/sheets-configs/npc-settings.mjs +++ b/module/applications/sheets-configs/npc-settings.mjs @@ -52,34 +52,4 @@ export default class DHNPCSettings extends DHBaseActorSettings { 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/environment.mjs b/module/applications/sheets/actors/environment.mjs index f8ff74a6..9a88dba6 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -78,7 +78,6 @@ export default class DhpEnvironment extends DHBaseActorSheet { switch (partId) { case 'header': await this._prepareHeaderContext(context, options); - break; case 'features': await this._prepareFeaturesContext(context, options); diff --git a/module/applications/sheets/api/actor-setting.mjs b/module/applications/sheets/api/actor-setting.mjs index 738f7002..65497cec 100644 --- a/module/applications/sheets/api/actor-setting.mjs +++ b/module/applications/sheets/api/actor-setting.mjs @@ -1,13 +1,15 @@ import DHApplicationMixin from './application-mixin.mjs'; -const { DocumentSheetV2 } = foundry.applications.api; +const { ActorSheetV2 } = foundry.applications.sheets; -/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ +/** + * @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction + */ /** * Base settings sheet for Daggerheart actors. - * @extends {DHApplicationMixin} + * @extends {DHApplicationMixin} */ -export default class DHBaseActorSettings extends DHApplicationMixin(DocumentSheetV2) { +export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2) { /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['dialog'], @@ -34,7 +36,7 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee return options; } - /**@returns {foundry.documents.Actor} */ + /** @returns {foundry.documents.Actor} */ get actor() { return this.document; } @@ -73,4 +75,29 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee return context; } + + async _onDragStart(event) { + const featureItemEl = event.currentTarget.closest('.feature-item'); + const feature = this.actor.items.get(featureItemEl?.dataset.itemId); + if (feature && event.target.closest('.tab.features')) { + const featureData = { ...feature.toDragData(), fromInternal: true }; + event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); + event.dataTransfer.setDragImage(featureItemEl.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 super._onDrop(event); + } + + const itemData = item.toObject(); + delete itemData._id; + await this.actor.createEmbeddedDocuments('Item', [itemData]); + } + } } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 876278fa..f3d612e9 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -381,8 +381,7 @@ export default function DHApplicationMixin(Base) { * @protected */ _onDrop(event) { - // Fallback to super, but note that config sheets don't have this option - // We still need this to avoid setting apps having issues + // Potentially handle subclasses that dont descend from actor/item sheet. event.stopPropagation(); return super._onDrop?.(event); } diff --git a/templates/sheets-settings/adversary-settings/features.hbs b/templates/sheets-settings/adversary-settings/features.hbs index 2f2f5f47..3e0ed654 100644 --- a/templates/sheets-settings/adversary-settings/features.hbs +++ b/templates/sheets-settings/adversary-settings/features.hbs @@ -10,7 +10,7 @@ {{localize tabs.features.label}}
      {{#each @root.features as |feature|}} -
    • +
    • {{feature.name}} diff --git a/templates/sheets-settings/environment-settings/features.hbs b/templates/sheets-settings/environment-settings/features.hbs index 579fe74e..ecda0e6b 100644 --- a/templates/sheets-settings/environment-settings/features.hbs +++ b/templates/sheets-settings/environment-settings/features.hbs @@ -10,7 +10,7 @@ {{localize tabs.features.label}}
        {{#each @root.features as |feature|}} -
      • +
      • {{feature.name}} diff --git a/templates/sheets-settings/npc-settings/features.hbs b/templates/sheets-settings/npc-settings/features.hbs index 2f2f5f47..3e0ed654 100644 --- a/templates/sheets-settings/npc-settings/features.hbs +++ b/templates/sheets-settings/npc-settings/features.hbs @@ -10,7 +10,7 @@ {{localize tabs.features.label}}
          {{#each @root.features as |feature|}} -
        • +
        • {{feature.name}} From 29be8c139538a3f543535c390ed7b18c227e8d65 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 21 Jun 2026 00:13:45 +0200 Subject: [PATCH 43/63] Removed the unused ResourceMap total property (#2024) --- module/applications/dialogs/groupRollDialog.mjs | 8 ++++---- module/applications/dialogs/tagTeamDialog.mjs | 8 ++++---- module/applications/sheets/actors/character.mjs | 2 +- module/data/action/baseAction.mjs | 3 +-- module/dice/dualityRoll.mjs | 12 ++++++------ module/dice/helpers.mjs | 6 +++--- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index 7196d848..58ed03b4 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -483,13 +483,13 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat const resourceMap = new ResourceUpdateMap(actor); if (totalRoll.isCritical) { resourceMap.addResources([ - { key: 'stress', value: -1, total: 1 }, - { key: 'hope', value: 1, total: 1 } + { key: 'stress', value: -1 }, + { key: 'hope', value: 1 } ]); } else if (totalRoll.withHope) { - resourceMap.addResources([{ key: 'hope', value: 1, total: 1 }]); + resourceMap.addResources([{ key: 'hope', value: 1 }]); } else { - resourceMap.addResources([{ key: 'fear', value: 1, total: 1 }]); + resourceMap.addResources([{ key: 'fear', value: 1 }]); } resourceMap.updateResources(); diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index b2ce0258..5c83f075 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -752,7 +752,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio /* Handle resource updates from the finished TagTeamRoll */ const tagTeamData = this.party.system.tagTeam; - const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; + const fearUpdate = { key: 'fear', value: null, enabled: true }; for (let memberId in tagTeamData.members) { const resourceUpdates = []; const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; @@ -762,11 +762,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ? 1 - tagTeamData.initiator.cost : -tagTeamData.initiator.cost : 1; - resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true }); + resourceUpdates.push({ key: 'hope', value: value, enabled: true }); } else if (rollGivesHope) { - resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true }); + resourceUpdates.push({ key: 'hope', value: 1, enabled: true }); } - if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); + if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, enabled: true }); if (finalRoll.withFear) { fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index f0f8326f..3a60e7ca 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -809,7 +809,7 @@ export default class CharacterSheet extends DHBaseActorSheet { /* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */ const costResources = - result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) || + result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value })) || {}; result.resourceUpdates.addResources(costResources); await result.resourceUpdates.updateResources(); diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index c71f5ef9..ea4361b9 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -459,8 +459,7 @@ export class ResourceUpdateMap extends Map { } else if (!existing?.clear) { this.set(resource.key, { ...existing, - value: existing.value + (resource.value ?? 0), - total: existing.total + (resource.total ?? 0) + value: existing.value + (resource.value ?? 0) }); } } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 1cfed094..70e98242 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -334,15 +334,15 @@ export default class DualityRoll extends D20Roll { const fear = (config.roll.result.duality === -1 ? 1 : 0) - (config.rerolledRoll.result.duality === -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 }); + if (hope !== 0) updates.push({ key: 'hope', value: hope, enabled: true }); + if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, enabled: true }); + if (fear !== 0) updates.push({ key: 'fear', value: fear, enabled: true }); } } else { if (config.roll.isCritical || config.roll.result.duality === 1) - updates.push({ key: 'hope', value: 1, total: -1, enabled: true }); - if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true }); - if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true }); + updates.push({ key: 'hope', value: 1, enabled: true }); + if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, enabled: true }); + if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, enabled: true }); } if (updates.length) { diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs index 35adb8b7..5f8a7bbb 100644 --- a/module/dice/helpers.mjs +++ b/module/dice/helpers.mjs @@ -9,9 +9,9 @@ export function updateResourcesForDualityReroll(oldDuality, newDuality, actor) { 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 }); + if (hope !== 0) updates.push({ key: 'hope', value: hope, enabled: true }); + if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, enabled: true }); + if (fear !== 0) updates.push({ key: 'fear', value: fear, enabled: true }); const resourceUpdates = new ResourceUpdateMap(actor); resourceUpdates.addResources(updates); From 08b95e48d6190b1c1853009104410f0cf13f2133 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 21 Jun 2026 04:50:09 +0200 Subject: [PATCH 44/63] [Fix] TagTag/GroupRoll Resource Handling (#2022) --- .../applications/dialogs/groupRollDialog.mjs | 28 ++++--- module/applications/dialogs/tagTeamDialog.mjs | 73 ++++++++++++------- module/dice/dualityRoll.mjs | 6 +- module/helpers/utils.mjs | 5 ++ .../tag-team-dialog/initialization.less | 14 +++- .../dialogs/tagTeamDialog/initialization.hbs | 13 +++- 6 files changed, 92 insertions(+), 47 deletions(-) diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index 58ed03b4..ebc80d39 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -1,4 +1,5 @@ import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; +import { shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import Party from '../sheets/actors/party.mjs'; @@ -480,19 +481,22 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat await cls.create(msgData); - const resourceMap = new ResourceUpdateMap(actor); - if (totalRoll.isCritical) { - resourceMap.addResources([ - { key: 'stress', value: -1 }, - { key: 'hope', value: 1 } - ]); - } else if (totalRoll.withHope) { - resourceMap.addResources([{ key: 'hope', value: 1 }]); - } else { - resourceMap.addResources([{ key: 'fear', value: 1 }]); - } + /* Handle resource updates for the finished GroupRoll */ + if (shouldUseHopeFearAutomation({ gmAsPlayer: true })) { + const resourceMap = new ResourceUpdateMap(actor); + if (totalRoll.isCritical) { + resourceMap.addResources([ + { key: 'stress', value: -1 }, + { key: 'hope', value: 1 } + ]); + } else if (totalRoll.withHope) { + resourceMap.addResources([{ key: 'hope', value: 1 }]); + } else { + resourceMap.addResources([{ key: 'fear', value: 1 }]); + } - resourceMap.updateResources(); + resourceMap.updateResources(); + } /* Fin */ this.cancelRoll({ confirm: false }); diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 5c83f075..19869a00 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -1,5 +1,6 @@ +import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; import { MemberData } from '../../data/tagTeamData.mjs'; -import { getCritDamageBonus } from '../../helpers/utils.mjs'; +import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import Party from '../sheets/actors/party.mjs'; @@ -9,6 +10,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio constructor(party) { super({ id: `TagTeamDialog-${party.id}` }); + this.usesTagTeamHopeCost = true; this.party = party; this.partyMembers = party.system.partyMembers .filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) @@ -20,7 +22,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) })); - this.initiator = { cost: 3 }; + this.initiator = { cost: + this.party.system.schema.fields.tagTeam.fields.initiator.fields.cost.initial + }; this.openForAllPlayers = true; this.tabGroups.application = Object.keys(party.system.tagTeam.members).length @@ -94,9 +98,13 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ?.addEventListener('input', this.updateInitiatorMemberField.bind(this)); htmlElement - .querySelector('.initiator-cost-field') + .querySelector('.initiator-cost-input') ?.addEventListener('input', this.updateInitiatorCostField.bind(this)); + htmlElement + .querySelector('.initiator-cost-enabled-checkbox') + ?.addEventListener('change', this.toggleInitiatorCostEnabled.bind(this)); + htmlElement .querySelector('.openforall-field') ?.addEventListener('change', this.updateOpenForAllField.bind(this)); @@ -156,6 +164,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio .map(x => ({ value: x.id, label: x.name })); partContext.initiatorDisabled = !selectedMembers.length; partContext.openForAllPlayers = this.openForAllPlayers; + partContext.usesTagTeamHopeCost = this.usesTagTeamHopeCost; break; case 'tagTeamRoll': @@ -397,6 +406,13 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio this.render(); } + toggleInitiatorCostEnabled(_event) { + this.usesTagTeamHopeCost = !this.usesTagTeamHopeCost; + this.initiator.cost = this.usesTagTeamHopeCost ? + this.party.system.schema.fields.tagTeam.fields.initiator.fields.cost.initial : 0; + this.render(); + } + updateOpenForAllField(event) { this.openForAllPlayers = event.target.checked; this.render(); @@ -752,32 +768,37 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio /* Handle resource updates from the finished TagTeamRoll */ const tagTeamData = this.party.system.tagTeam; - const fearUpdate = { key: 'fear', value: null, enabled: true }; - for (let memberId in tagTeamData.members) { - const resourceUpdates = []; - const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; - if (memberId === tagTeamData.initiator.memberId) { - const value = tagTeamData.initiator.cost - ? rollGivesHope - ? 1 - tagTeamData.initiator.cost - : -tagTeamData.initiator.cost - : 1; - resourceUpdates.push({ key: 'hope', value: value, enabled: true }); - } else if (rollGivesHope) { - resourceUpdates.push({ key: 'hope', value: 1, enabled: true }); - } - if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, enabled: true }); - if (finalRoll.withFear) { - fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; - fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1; - } - game.actors.get(memberId).modifyResource(resourceUpdates); + const actorResourceMaps = Object.keys(tagTeamData.members).reduce((acc, key) => { + acc[key] = new ResourceUpdateMap(game.actors.get(key)); + return acc; + }, {}); + + if (shouldUseHopeFearAutomation({ gmAsPlayer: true })) { + const fearResourceMap = actorResourceMaps[tagTeamData.initiator.memberId]; + for (const memberId in tagTeamData.members) { + const resourceMap = actorResourceMaps[memberId]; + if (finalRoll.isCritical) { + resourceMap.addResources([ + { key: 'stress', value: -1, enabled: true }, + { key: 'hope', value: 1, enabled: true } + ]); + } else if (finalRoll.withHope) { + resourceMap.addResources([{ key: 'hope', value: 1, enabled: true }]); + } else if (finalRoll.withFear) { + fearResourceMap.addResources([{ key: 'fear', value: 1, enabled: true }]); + } + } + } + + /* Even with Hope/Fear automation off, the hope cost of performing the TagTeamRoll can still optionally be subtracted */ + if (tagTeamData.initiator.cost) { + const resourceMap = actorResourceMaps[tagTeamData.initiator.memberId]; + resourceMap.addResources([{ key: 'hope', value: -tagTeamData.initiator.cost, enabled: true }]); } - if (fearUpdate.value) { - mainActor.modifyResource([fearUpdate]); - } + for (const resourceMap of Object.values(actorResourceMaps)) + resourceMap.updateResources(); /* Fin */ this.cancelRoll({ confirm: false }); diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 70e98242..e2d967a3 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -1,6 +1,6 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20Roll from './d20Roll.mjs'; -import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; +import { parseRallyDice, setDiceSoNiceForDualityRoll, shouldUseHopeFearAutomation } from '../helpers/utils.mjs'; import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; import { updateResourcesForDualityReroll } from './helpers.mjs'; @@ -312,11 +312,9 @@ export default class DualityRoll extends D20Roll { } static async addDualityResourceUpdates(config) { - const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); - const hopeFearAutomation = automationSettings.hopeFear; if ( !config.source?.actor || - (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) || + !shouldUseHopeFearAutomation() || config.actionType === 'reaction' || config.skips?.resources ) diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 3c5192be..6467edd7 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -885,3 +885,8 @@ export async function triggerChatRollFx(rolls, options = { whisper: false, blind foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); } } + +export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) { + const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm; +} \ No newline at end of file diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index d6f7ad29..e79ed65c 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -88,9 +88,21 @@ grid-template-columns: 1fr 1fr; gap: 8px; - &.inactive { + .inactive { opacity: 0.4; } + + .initiator-cost-fields { + display: flex; + flex-direction: column; + align-items: flex-start; + + .initiator-cost-inputs { + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + } + } } footer { diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs index 0b92e68e..40491e1b 100644 --- a/templates/dialogs/tagTeamDialog/initialization.hbs +++ b/templates/dialogs/tagTeamDialog/initialization.hbs @@ -26,11 +26,16 @@
        -
        +
        -
        - -
        +
        + + +
        From 7237a3651d168338c47f9acf9bd4c56e72cf7d5d Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 21 Jun 2026 05:29:07 -0400 Subject: [PATCH 45/63] Fix svg sizing on firefox (#2027) --- assets/icons/documents/actors/capybara.svg | 2 +- assets/icons/documents/actors/dark-squad.svg | 2 +- assets/icons/documents/actors/dragon-head.svg | 2 +- assets/icons/documents/actors/drama-masks.svg | 2 +- assets/icons/documents/actors/forest.svg | 2 +- assets/icons/documents/items/battered-axe.svg | 2 +- assets/icons/documents/items/card-play.svg | 2 +- assets/icons/documents/items/chest-armor.svg | 2 +- assets/icons/documents/items/family-tree.svg | 2 +- assets/icons/documents/items/laurel-crown.svg | 2 +- assets/icons/documents/items/laurels.svg | 2 +- assets/icons/documents/items/open-treasure-chest.svg | 2 +- assets/icons/documents/items/round-potion.svg | 2 +- assets/icons/documents/items/stars-stack.svg | 2 +- assets/icons/documents/items/village.svg | 2 +- assets/icons/documents/items/wolf-head.svg | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/assets/icons/documents/actors/capybara.svg b/assets/icons/documents/actors/capybara.svg index 90deb64a..8fbe2c06 100644 --- a/assets/icons/documents/actors/capybara.svg +++ b/assets/icons/documents/actors/capybara.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/actors/dark-squad.svg b/assets/icons/documents/actors/dark-squad.svg index f21b4c5b..39ca794a 100644 --- a/assets/icons/documents/actors/dark-squad.svg +++ b/assets/icons/documents/actors/dark-squad.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/actors/dragon-head.svg b/assets/icons/documents/actors/dragon-head.svg index d9e008f5..324ffbbb 100644 --- a/assets/icons/documents/actors/dragon-head.svg +++ b/assets/icons/documents/actors/dragon-head.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/actors/drama-masks.svg b/assets/icons/documents/actors/drama-masks.svg index 84307da0..98b9b68b 100644 --- a/assets/icons/documents/actors/drama-masks.svg +++ b/assets/icons/documents/actors/drama-masks.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/actors/forest.svg b/assets/icons/documents/actors/forest.svg index 8f7117e8..fcdb8a9d 100644 --- a/assets/icons/documents/actors/forest.svg +++ b/assets/icons/documents/actors/forest.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/battered-axe.svg b/assets/icons/documents/items/battered-axe.svg index 5d7be27d..436884bd 100644 --- a/assets/icons/documents/items/battered-axe.svg +++ b/assets/icons/documents/items/battered-axe.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/card-play.svg b/assets/icons/documents/items/card-play.svg index 587cb1c1..5c29fc43 100644 --- a/assets/icons/documents/items/card-play.svg +++ b/assets/icons/documents/items/card-play.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/chest-armor.svg b/assets/icons/documents/items/chest-armor.svg index 2cef80a6..03b82d48 100644 --- a/assets/icons/documents/items/chest-armor.svg +++ b/assets/icons/documents/items/chest-armor.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/family-tree.svg b/assets/icons/documents/items/family-tree.svg index d95c935d..031f23da 100644 --- a/assets/icons/documents/items/family-tree.svg +++ b/assets/icons/documents/items/family-tree.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/laurel-crown.svg b/assets/icons/documents/items/laurel-crown.svg index 34a54d2a..38dbcd04 100644 --- a/assets/icons/documents/items/laurel-crown.svg +++ b/assets/icons/documents/items/laurel-crown.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/laurels.svg b/assets/icons/documents/items/laurels.svg index 2c3cdf63..ae7398ec 100644 --- a/assets/icons/documents/items/laurels.svg +++ b/assets/icons/documents/items/laurels.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/open-treasure-chest.svg b/assets/icons/documents/items/open-treasure-chest.svg index 172a8003..d66399f4 100644 --- a/assets/icons/documents/items/open-treasure-chest.svg +++ b/assets/icons/documents/items/open-treasure-chest.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/round-potion.svg b/assets/icons/documents/items/round-potion.svg index 7f981914..0c235732 100644 --- a/assets/icons/documents/items/round-potion.svg +++ b/assets/icons/documents/items/round-potion.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/stars-stack.svg b/assets/icons/documents/items/stars-stack.svg index 19c197f6..2d446683 100644 --- a/assets/icons/documents/items/stars-stack.svg +++ b/assets/icons/documents/items/stars-stack.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/village.svg b/assets/icons/documents/items/village.svg index c28d742b..b7c776a1 100644 --- a/assets/icons/documents/items/village.svg +++ b/assets/icons/documents/items/village.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/wolf-head.svg b/assets/icons/documents/items/wolf-head.svg index 2be500c1..7e57bbad 100644 --- a/assets/icons/documents/items/wolf-head.svg +++ b/assets/icons/documents/items/wolf-head.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 3840f39c9791d441073a02e83a1252d7fc438460 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 21 Jun 2026 05:34:49 -0400 Subject: [PATCH 46/63] Fade environment adversaries and notes tabs when empty (#2028) --- module/applications/sheets/actors/environment.mjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/module/applications/sheets/actors/environment.mjs b/module/applications/sheets/actors/environment.mjs index 9a88dba6..60fb9071 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -72,6 +72,16 @@ export default class DhpEnvironment extends DHBaseActorSheet { return applicationOptions; } + /** @inheritdoc */ + _prepareTabs(group) { + const result = super._prepareTabs(group); + if (group === 'primary') { + result.potentialAdversaries.empty = foundry.utils.isEmpty(this.document.system.potentialAdversaries); + result.notes.empty = !this.document.system.notes?.trim(); + } + return result; + } + /**@inheritdoc */ async _preparePartContext(partId, context, options) { context = await super._preparePartContext(partId, context, options); From f3859829878545f7793c2bd2257af23cc979f5af Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 21 Jun 2026 23:04:28 +0200 Subject: [PATCH 47/63] [Fix] Action Uses Not Spent (#2029) --- module/applications/dialogs/d20RollDialog.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index e9140b56..048797d8 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -160,7 +160,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio } if (rest.hasOwnProperty('trait')) { this.config.roll.trait = rest.trait; - if (!this.config.source.item) + if (!this.config.source.item && this.config.roll.trait) this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: game.i18n.localize(abilities[this.config.roll.trait]?.label) }); @@ -225,7 +225,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio } static async submitRoll(event) { - event.preventDefault(); await this.close({ submitted: true }); } From 0d59e37a806d71b7daa6faef7229d942709abf01 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 21 Jun 2026 23:35:38 +0200 Subject: [PATCH 48/63] [Fix] Trigger Error Info (#2030) * Made trigger errors more informative * . --- lang/en.json | 2 +- module/data/registeredTriggers.mjs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lang/en.json b/lang/en.json index 5bab0e64..a4a5edf0 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1451,7 +1451,7 @@ }, "triggerType": "Trigger Type", "triggeringActorType": "Triggering Actor Type", - "triggerError": "{trigger} trigger failed for {actor}. It's probably configured wrong." + "triggerError": "{trigger} trigger on {item}({itemUuid}) failed for {actor}. It's probably configured wrong." }, "WeaponFeature": { "barrier": { diff --git a/module/data/registeredTriggers.mjs b/module/data/registeredTriggers.mjs index 0b637930..64e7f93e 100644 --- a/module/data/registeredTriggers.mjs +++ b/module/data/registeredTriggers.mjs @@ -150,11 +150,14 @@ export default class RegisteredTriggers extends Map { const result = await command(...args); if (result?.updates?.length) updates.push(...result.updates); } catch { + const item = await foundry.utils.fromUuid(itemUuid); const triggerName = game.i18n.localize(triggerData.label); - ui.notifications.error( + console.error( game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', { trigger: triggerName, - actor: currentActor?.name + actor: currentActor?.name, + item: item?.name ?? '', + itemUuid: item?.uuid ?? '' }) ); } From cc16a726bffb9bb4bd0a9f12f1d8e68b76bdc556 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 21 Jun 2026 17:53:41 -0400 Subject: [PATCH 49/63] [Feature] In setting sheets, split by feature type and include descriptions (#2026) --- .../sheets-configs/adversary-settings.mjs | 25 +++++----- .../sheets-configs/environment-settings.mjs | 22 +++++--- .../sheets-configs/npc-settings.mjs | 19 ++++--- .../applications/sheets/api/actor-setting.mjs | 7 ++- .../sheets/api/application-mixin.mjs | 9 ++-- .../adversary-settings/features.hbs | 50 ++++++++++--------- .../environment-settings/features.hbs | 47 +++++++++-------- .../sheets-settings/npc-settings/features.hbs | 50 ++++++++++--------- .../global/partials/inventory-item-V2.hbs | 24 ++++----- 9 files changed, 143 insertions(+), 110 deletions(-) diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index 583f37b7..ff3f3039 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -10,11 +10,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings { actions: { addExperience: DHAdversarySettings.#addExperience, removeExperience: DHAdversarySettings.#removeExperience - }, - dragDrop: [ - { dragSelector: null, dropSelector: '.tab.features' }, - { dragSelector: '.feature-item', dropSelector: null } - ] + } }; /**@override */ @@ -38,7 +34,8 @@ export default class DHAdversarySettings extends DHBaseActorSettings { }, features: { id: 'features', - template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/features.hbs' + template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/features.hbs', + scrollable: [''] } }; @@ -54,12 +51,16 @@ export default class DHAdversarySettings extends DHBaseActorSettings { async _prepareContext(options) { const context = await super._prepareContext(options); - const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); - 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 - ); + // Get feature groups. Uncategorized go to actions + const featureFormsTypes = ['passive', 'action', 'reaction']; + const features = this.document.system.features.sort((a, b) => a.sort - b.sort); + const featureGroups = featureFormsTypes.map(t => ({ + featureForm: t, + label: _loc(CONFIG.DH.ITEM.featureForm[t]), + features: features.filter(f => f.system.featureForm === t) + })); + featureGroups[1].features.push(...features.filter(f => !featureFormsTypes.includes(f.system.featureForm))); + context.featureGroups = featureGroups; return context; } diff --git a/module/applications/sheets-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index d6744eb8..954d6381 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -32,11 +32,13 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { }, features: { id: 'features', - template: 'systems/daggerheart/templates/sheets-settings/environment-settings/features.hbs' + template: 'systems/daggerheart/templates/sheets-settings/environment-settings/features.hbs', + scrollable: [''] }, adversaries: { id: 'adversaries', - template: 'systems/daggerheart/templates/sheets-settings/environment-settings/adversaries.hbs' + template: 'systems/daggerheart/templates/sheets-settings/environment-settings/adversaries.hbs', + scrollable: [''] } }; @@ -52,12 +54,16 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { 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 - ); + // Get feature groups. Uncategorized go to actions + const featureFormsTypes = ['passive', 'action', 'reaction']; + const features = this.document.system.features.sort((a, b) => a.sort - b.sort); + const featureGroups = featureFormsTypes.map(t => ({ + featureForm: t, + label: _loc(CONFIG.DH.ITEM.featureForm[t]), + features: features.filter(f => f.system.featureForm === t) + })); + featureGroups[1].features.push(...features.filter(f => !featureFormsTypes.includes(f.system.featureForm))); + context.featureGroups = featureGroups; return context; } diff --git a/module/applications/sheets-configs/npc-settings.mjs b/module/applications/sheets-configs/npc-settings.mjs index d2132a91..33cbc270 100644 --- a/module/applications/sheets-configs/npc-settings.mjs +++ b/module/applications/sheets-configs/npc-settings.mjs @@ -27,7 +27,8 @@ export default class DHNPCSettings extends DHBaseActorSettings { }, features: { id: 'features', - template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs' + template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs', + scrollable: [''] } }; @@ -43,12 +44,16 @@ export default class DHNPCSettings extends DHBaseActorSettings { 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 - ); + // Get feature groups. Uncategorized go to actions + const featureFormsTypes = ['passive', 'action', 'reaction']; + const features = this.document.system.features.sort((a, b) => a.sort - b.sort); + const featureGroups = featureFormsTypes.map(t => ({ + featureForm: t, + label: _loc(CONFIG.DH.ITEM.featureForm[t]), + features: features.filter(f => f.system.featureForm === t) + })); + featureGroups[1].features.push(...features.filter(f => !featureFormsTypes.includes(f.system.featureForm))); + context.featureGroups = featureGroups; return context; } diff --git a/module/applications/sheets/api/actor-setting.mjs b/module/applications/sheets/api/actor-setting.mjs index 65497cec..fa434034 100644 --- a/module/applications/sheets/api/actor-setting.mjs +++ b/module/applications/sheets/api/actor-setting.mjs @@ -25,7 +25,7 @@ export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2 }, dragDrop: [ { dragSelector: null, dropSelector: '.tab.features' }, - { dragSelector: '.feature-item', dropSelector: null } + { dragSelector: '.feature-item, .inventory-item[data-type="feature"]', dropSelector: null } ] }; @@ -77,7 +77,7 @@ export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2 } async _onDragStart(event) { - const featureItemEl = event.currentTarget.closest('.feature-item'); + const featureItemEl = event.currentTarget.closest('.feature-item, .inventory-item[data-type="feature"]'); const feature = this.actor.items.get(featureItemEl?.dataset.itemId); if (feature && event.target.closest('.tab.features')) { const featureData = { ...feature.toDragData(), fromInternal: true }; @@ -100,4 +100,7 @@ export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2 await this.actor.createEmbeddedDocuments('Item', [itemData]); } } + + /** Setting sheets do not auto extend */ + async _autoExpandDescriptions() {} } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index f3d612e9..d89237df 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -4,6 +4,7 @@ import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../. const typeSettingsMap = { character: 'extendCharacterDescriptions', adversary: 'extendAdversaryDescriptions', + npc: 'extendAdversaryDescriptions', environment: 'extendEnvironmentDescriptions', ancestry: 'extendItemDescriptions', community: 'extendItemDescriptions', @@ -262,7 +263,7 @@ export default function DHApplicationMixin(Base) { if (!!this.options.contextMenus.length) this._createContextMenus(); - this.#autoExtendDescriptions(context); + this._autoExpandDescriptions(context); } /** @inheritDoc */ @@ -630,8 +631,9 @@ export default function DHApplicationMixin(Base) { /** * Extend inventory description when enabled in settings. * @returns {Promise} + * @protected */ - async #autoExtendDescriptions(context) { + async _autoExpandDescriptions(context) { const inventoryItems = this.element.querySelectorAll('.inventory-item[data-item-uuid]'); for (const el of inventoryItems) { // Get the doc uuid from the element @@ -734,7 +736,7 @@ export default function DHApplicationMixin(Base) { * @type {ApplicationClickAction} */ static async #onCreateDoc(event, target) { - const { documentClass, type, inVault, disabled } = target.dataset; + const { documentClass, type, inVault, disabled, featureForm } = target.dataset; const parentIsItem = this.document.documentName === 'Item'; const featureOnCharacter = this.document.parent?.type === 'character' && type === 'feature'; const parent = featureOnCharacter @@ -752,6 +754,7 @@ export default function DHApplicationMixin(Base) { identifier: this.document.system.isMulticlass ? 'multiclass' : null }; } + if (featureForm) systemData.featureForm = featureForm; const cls = type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass); diff --git a/templates/sheets-settings/adversary-settings/features.hbs b/templates/sheets-settings/adversary-settings/features.hbs index 3e0ed654..1cab8cfe 100644 --- a/templates/sheets-settings/adversary-settings/features.hbs +++ b/templates/sheets-settings/adversary-settings/features.hbs @@ -3,27 +3,31 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > - -
        - {{localize tabs.features.label}} -
          - {{#each @root.features as |feature|}} -
        • - -
          - {{feature.name}} -
          -
          - - -
          -
        • - {{/each}} -
        -
        - {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} -
        -
        + {{#each featureGroups as |group|}} +
        + + {{group.label}} + + + + +
          + {{#each group.features as |feature|}} + {{> 'daggerheart.inventory-item' + item=feature + type='feature' + actorType=@root.document.type + hideTags=true + hideContextMenu=true + hideResources=true + showActions=false + hideTooltip=true + }} + {{/each}} +
        +
        + {{/each}} +
        + {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
        \ No newline at end of file diff --git a/templates/sheets-settings/environment-settings/features.hbs b/templates/sheets-settings/environment-settings/features.hbs index ecda0e6b..1cab8cfe 100644 --- a/templates/sheets-settings/environment-settings/features.hbs +++ b/templates/sheets-settings/environment-settings/features.hbs @@ -3,24 +3,31 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > - -
        - {{localize tabs.features.label}} -
          - {{#each @root.features as |feature|}} -
        • - -
          - {{feature.name}} -
          -
          - - -
          -
        • - {{/each}} -
        -
        + {{#each featureGroups as |group|}} +
        + + {{group.label}} + + + + +
          + {{#each group.features as |feature|}} + {{> 'daggerheart.inventory-item' + item=feature + type='feature' + actorType=@root.document.type + hideTags=true + hideContextMenu=true + hideResources=true + showActions=false + hideTooltip=true + }} + {{/each}} +
        +
        + {{/each}} +
        + {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
        \ No newline at end of file diff --git a/templates/sheets-settings/npc-settings/features.hbs b/templates/sheets-settings/npc-settings/features.hbs index 3e0ed654..1cab8cfe 100644 --- a/templates/sheets-settings/npc-settings/features.hbs +++ b/templates/sheets-settings/npc-settings/features.hbs @@ -3,27 +3,31 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > - -
        - {{localize tabs.features.label}} -
          - {{#each @root.features as |feature|}} -
        • - -
          - {{feature.name}} -
          -
          - - -
          -
        • - {{/each}} -
        -
        - {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} -
        -
        + {{#each featureGroups as |group|}} +
        + + {{group.label}} + + + + +
          + {{#each group.features as |feature|}} + {{> 'daggerheart.inventory-item' + item=feature + type='feature' + actorType=@root.document.type + hideTags=true + hideContextMenu=true + hideResources=true + showActions=false + hideTooltip=true + }} + {{/each}} +
        +
        + {{/each}} +
        + {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
        \ No newline at end of file diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index 523e9304..f7d22a30 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -27,17 +27,17 @@ Parameters: >
        {{!-- Image --}} -
        - - {{#if item.usable}} - {{#if @root.isNPC}} - d20 - {{else}} - 2d12 +
        + + {{#if (and item.usable (ne showActions false))}} + {{#if @root.isNPC}} + d20 + {{else}} + 2d12 + {{/if}} {{/if}} - {{/if}}
        {{!-- Name & Tags --}} @@ -46,10 +46,10 @@ Parameters: {{localize item.name}} {{#unless (or noExtensible (not item.system.description))}}{{/unless}} {{!-- Tags Start --}} - {{#if (not ../hideTags)}} + {{#if (not hideTags)}} {{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}} {{#if (eq ../type 'feature')}} - {{#if (and system.featureForm (or (eq @root.document.type "adversary") (eq @root.document.type "environment")))}} + {{#if (and system.featureForm (ne @root.document.type "character"))}}
        {{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}}
        From 149249199843ccada03ec7857894230f1c97e202 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 22 Jun 2026 00:14:40 +0200 Subject: [PATCH 50/63] [Fix] Reroll Countdown Automation (#2031) * Fixed so that automated countdowns reverse progress from Fear when rerolling dice if the duality result changes * . * Removed overprep * Apply suggestion from @CarlosFdez --------- Co-authored-by: Carlos Fernandez --- module/applications/ui/countdowns.mjs | 14 +++++++------ module/dice/helpers.mjs | 29 ++++++++++++++++++--------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index d559582f..5cf79100 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -342,29 +342,31 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application * Sends updates of the countdowns to the GM player. Since this is asynchronous, be sure to * update all the countdowns at the same time. * - * @param {...any} progressTypes Countdowns to be updated + * @param {...(string | { type: string; undo?: boolean })} progressTypes Countdowns to be updated */ static async updateCountdowns(...progressTypes) { + progressTypes = progressTypes.map(p => typeof p === 'string' ? { type: p } : p); const { countdownAutomation } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); if (!countdownAutomation) return; const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => { const countdown = countdownSetting.countdowns[key]; - if (progressTypes.indexOf(countdown.progress.type) !== -1 && countdown.progress.current > 0) { - acc.push(key); + const progressData = progressTypes.find(x => x.type === countdown.progress.type); + if (progressData && countdown.progress.current > 0) { + acc[key] = { value: progressData.undo ? 1 : -1 }; } return acc; - }, []); + }, {}); const countdownData = countdownSetting.toObject(); const settings = { ...countdownData, countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => { const countdown = foundry.utils.deepClone(countdownData.countdowns[key]); - if (updatedCountdowns.includes(key)) { - countdown.progress.current -= 1; + if (updatedCountdowns[key]) { + countdown.progress.current += updatedCountdowns[key].value; } acc[key] = countdown; diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs index 5f8a7bbb..d03970d0 100644 --- a/module/dice/helpers.mjs +++ b/module/dice/helpers.mjs @@ -1,19 +1,28 @@ import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; 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, enabled: true }); - if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, enabled: true }); - if (fear !== 0) updates.push({ key: 'fear', value: fear, enabled: true }); + const { hopeFear, countdownAutomation } = + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); - const resourceUpdates = new ResourceUpdateMap(actor); - resourceUpdates.addResources(updates); - resourceUpdates.updateResources(); + if (game.user.isGM ? hopeFear.gm : hopeFear.players) { + const updates = []; + if (hope !== 0) updates.push({ key: 'hope', value: hope, enabled: true }); + if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, enabled: true }); + if (fear !== 0) updates.push({ key: 'fear', value: fear, enabled: true }) + + const resourceUpdates = new ResourceUpdateMap(actor); + resourceUpdates.addResources(updates); + resourceUpdates.updateResources(); + } + + if (countdownAutomation && fear !== 0) { + game.system.api.applications.ui.DhCountdowns.updateCountdowns({ + type: CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id, + undo: fear === 1 ? false : true + }); + } } From 8e930259474f631d4923b37d45dade63070e6adb Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 22 Jun 2026 00:24:33 +0200 Subject: [PATCH 51/63] [Fix] Weapon Spellcasting Active Effects (#2032) * . * . * Apply suggestion from @CarlosFdez --------- Co-authored-by: Carlos Fernandez --- .../sheets/api/application-mixin.mjs | 2 +- module/applications/sheets/api/base-actor.mjs | 2 +- module/data/action/baseAction.mjs | 39 +++++++++++++------ module/documents/actor.mjs | 2 +- module/documents/chatMessage.mjs | 2 +- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index d89237df..98f38f03 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -518,7 +518,7 @@ export default function DHApplicationMixin(Base) { 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( + config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects( this.document, doc ); diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index e65745c0..007b641b 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -212,7 +212,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { 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( + config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects( this.document, doc ); diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index ea4361b9..f568436e 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -228,7 +228,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel let config = this.prepareConfig(event, configOptions); if (!config) return; - config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item); + config.effects = + await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(this.actor, this.item); if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; @@ -333,27 +334,43 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } /** - * Get the all potentially applicable effects on the actor + * Get the all potentially applicable effects on the actor for the action's RollDialog * @param {DHActor} actor The actor performing the action * @param {DHItem|DhActor} effectParent The parent of the effect * @returns {DhActiveEffect[]} */ - static async getEffects(actor, effectParent) { + static async getActionRelevantEffects(actor, effectParent) { if (!actor) return []; - return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).filter( - effect => { - /* Effects on weapons only ever apply for the weapon itself */ + // Changes on weapon effects are not typically only applicable to show in the roll dialog for the weapon itself + // The exemptions to this rule are listed below + const weaponTransferredEffectKeys = [ + 'system.bonuses.roll.spellcast.bonus' + ]; + + return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).reduce( + (acc, effect) => { + const effectData = effect.toObject(); + /* Effects on weapons only ever apply for the weapon itself, with a few defined exceptions */ if (effect.parent.type === 'weapon') { /* Unless they're secondary - then they apply only to other primary weapons */ if (effect.parent.system.secondary) { - if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false; - } else if (effectParent?.id !== effect.parent.id) return false; + if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) { + effectData.system.changes = + effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); + } + } else if (effectParent?.id !== effect.parent.id) { + effectData.system.changes = + effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); + } } - return !effect.isSuppressed; - } - ); + if (!effect.isSuppressed) { + acc.push(effectData); + } + + return acc; + }, []); } /** diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 1642ed30..8ef64f65 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -549,7 +549,7 @@ export default class DhpActor extends Actor { headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }), - effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this), + effects: await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(this), roll: { trait: trait, type: 'trait' diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 480f8c69..b555dfca 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -167,7 +167,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (this.system.action) { const actor = await foundry.utils.fromUuid(config.source.actor); const item = actor?.items.get(config.source.item) ?? null; - config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(actor, item); + config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(actor, item); await this.system.action.workflow.get('damage')?.execute(config, this._id, true); } } From 329d820aab0ce538ab4c1696afa27b1e4c827ad4 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 22 Jun 2026 00:26:24 +0200 Subject: [PATCH 52/63] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index f5e13a62..08693074 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.4", + "version": "2.4.0", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -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.4/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.0/system.zip", "authors": [ { "name": "WBHarry" From 9f29229c9455efefbe331abf2c9d6bb55ca99d11 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 23 Jun 2026 02:57:22 -0400 Subject: [PATCH 53/63] Fix resolving formulas in weapon change effects (#2035) --- module/data/action/baseAction.mjs | 42 ++++++++++++++++--------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f568436e..27383b7a 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -348,29 +348,31 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel 'system.bonuses.roll.spellcast.bonus' ]; - return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).reduce( - (acc, effect) => { - const effectData = effect.toObject(); - /* Effects on weapons only ever apply for the weapon itself, with a few defined exceptions */ - if (effect.parent.type === 'weapon') { - /* Unless they're secondary - then they apply only to other primary weapons */ - if (effect.parent.system.secondary) { - if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) { - effectData.system.changes = - effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); - } - } else if (effectParent?.id !== effect.parent.id) { - effectData.system.changes = - effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); + const results = []; + const applicableEffects = await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true }); + for (const effect of [...applicableEffects].filter(e => !e.isSuppressed)) { + if (effect.parent.type === 'weapon') { + // Effects on weapons only ever apply for the weapon itself (with a few exceptions) + const restricted = + effect.parent.system.secondary + // Secondary applies only to other primary weapons + ? effectParent?.type !== 'weapon' || effectParent?.system.secondary + // Primary only applies to itself + : effectParent?.id !== effect.parent.id; + if (restricted) { + const sourceChanges = effect._source.system.changes; + const changes = sourceChanges.filter(x => weaponTransferredEffectKeys.includes(x.key)); + if (changes.length) { + results.push(effect.clone({ 'system.changes': changes })); } + continue; } + } + + results.push(effect); + } - if (!effect.isSuppressed) { - acc.push(effectData); - } - - return acc; - }, []); + return results; } /** From f5fa59b3bd6d6dcdd8e3b1f3fda68168cf2c3bec Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 23 Jun 2026 03:40:12 -0400 Subject: [PATCH 54/63] Make prosemirror editor look a bit nicer (#2034) --- styles/less/global/prose-mirror.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index e4b1249f..fc8e49f9 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -3,6 +3,8 @@ .application.daggerheart { prose-mirror { + --menu-padding: 4px 0px; + --menu-height: calc(var(--menu-button-height) + 8px); height: 100% !important; width: 100%; From 958eaa310c2b33414527b68afbe6b4ecc64f02c3 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 23 Jun 2026 06:22:03 -0400 Subject: [PATCH 55/63] Simplify ActiveEffect sheet tags (#2037) * Simplify ActiveEffect sheet tags * Show origin actor and exclude tag if it is a top level actor tag without origin --- lang/en.json | 3 ++- module/documents/activeEffect.mjs | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lang/en.json b/lang/en.json index a4a5edf0..3f21f5eb 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2010,7 +2010,8 @@ "Attachments": { "attachHint": "Drop items here to attach them", "transferHint": "If checked, this effect will be applied to any actor that owns this Effect's parent Item. The effect is always applied if this Item is attached to another one." - } + }, + "OriginTag": "Origin: {name}" }, "GENERAL": { "Ability": { diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 0e7f5d1e..083b3950 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -224,12 +224,13 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { * @returns {string[]} An array of localized tag strings. */ _getTags() { - const tags = [ - `${game.i18n.localize(this.parent.system.metadata.label)}: ${this.parent.name}`, - game.i18n.localize( - this.isTemporary ? 'DAGGERHEART.EFFECTS.Duration.temporary' : 'DAGGERHEART.EFFECTS.Duration.passive' - ) - ]; + const tags = []; + const originActor = DhActiveEffect.#resolveParentDocument(fromUuidSync(this.origin), Actor); + if (originActor && originActor !== this.actor) { + tags.push(_loc('DAGGERHEART.EFFECTS.OriginTag', { name: originActor.name })); + } else if (!(this.parent instanceof Actor)) { + tags.push(`${_loc(this.parent.system.metadata.label)}: ${this.parent.name}`); + } for (const statusId of this.statuses) { const status = CONFIG.statusEffects.find(s => s.id === statusId); From 07b7c8209440dfc350c1b668ab95487d7cfcf799 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 23 Jun 2026 17:26:53 -0400 Subject: [PATCH 56/63] Fetch origin fetch when in compendium (#2042) --- module/documents/activeEffect.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 083b3950..cdfc9a52 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -225,7 +225,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { */ _getTags() { const tags = []; - const originActor = DhActiveEffect.#resolveParentDocument(fromUuidSync(this.origin), Actor); + const originActor = DhActiveEffect.#resolveParentDocument(fromUuidSync(this.origin, { strict: false }), Actor); if (originActor && originActor !== this.actor) { tags.push(_loc('DAGGERHEART.EFFECTS.OriginTag', { name: originActor.name })); } else if (!(this.parent instanceof Actor)) { From ca82cbcf669240ba6034d0200f84781df5e185de Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:28:04 +0200 Subject: [PATCH 57/63] . (#2041) --- module/documents/combat.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index 20996b77..e74127e9 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -46,7 +46,9 @@ export default class DhpCombat extends Combat { for (let actor of actors) { await actor.createEmbeddedDocuments( 'ActiveEffect', - effects.filter(x => x.effectTargetTypes.includes(actor.type)) + effects + .filter(x => x.effectTargetTypes.includes(actor.type)) + .map(x => foundry.utils.deepClone(x)) ); } } else { From 2c1f52413d60004c8d4933caf586258ff4baf1cd Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 23 Jun 2026 23:33:29 +0200 Subject: [PATCH 58/63] Raised verison --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 08693074..4660a196 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.4.0", + "version": "2.4.1", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -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.4.0/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.1/system.zip", "authors": [ { "name": "WBHarry" From 1ebbad47973b631bb6779340e5cab85dd4532a7a Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 24 Jun 2026 17:37:00 -0400 Subject: [PATCH 59/63] [Fix] quirks involving expanding items and hovering over them (#2033) --- .../sheets/api/application-mixin.mjs | 2 +- module/data/action/baseAction.mjs | 4 + module/data/item/armor.mjs | 8 +- module/data/item/weapon.mjs | 8 +- module/documents/activeEffect.mjs | 4 + module/documents/item.mjs | 4 + styles/less/global/inventory-item.less | 28 +- .../global/partials/inventory-item-V2.hbs | 266 +++++++++--------- 8 files changed, 163 insertions(+), 161 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 98f38f03..0168f46d 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -603,7 +603,7 @@ export default function DHApplicationMixin(Base) { const doc = await fromUuid(itemUuid); //get inventory-item description element - const descriptionElement = el.querySelector('.invetory-description'); + const descriptionElement = el.querySelector('.inventory-description'); if (!doc || !descriptionElement) continue; // localize the description (idk if it's still necessary) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 27383b7a..f3008704 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -54,6 +54,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return {}; } + get hasDescription() { + return Boolean(this.description); + } + /** * Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property. * diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 21c56f9a..15bb620d 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -52,6 +52,10 @@ export default class DHArmor extends AttachableItem { ); } + get itemFeatures() { + return this.armorFeatures; + } + /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; @@ -169,8 +173,4 @@ export default class DHArmor extends AttachableItem { const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`]; return labels; } - - get itemFeatures() { - return this.armorFeatures; - } } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 84e4de7f..39c0fc8e 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -113,6 +113,10 @@ export default class DHWeapon extends AttachableItem { ); } + get itemFeatures() { + return this.weaponFeatures; + } + /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; @@ -269,8 +273,4 @@ export default class DHWeapon extends AttachableItem { return labels; } - - get itemFeatures() { - return this.weaponFeatures; - } } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index cdfc9a52..4a9f3cc4 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -65,6 +65,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { ); } + get hasDescription() { + return Boolean(this.description); + } + /* -------------------------------------------- */ /* Event Handlers */ /* -------------------------------------------- */ diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 32543ebd..14717538 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -89,6 +89,10 @@ export default class DHItem extends foundry.documents.Item { return !pack?.locked && this.isOwner && isValidType && hasActions; } + get hasDescription() { + return Boolean(this.system.description) || Boolean(this.system.itemFeatures?.length); + } + /** @inheritdoc */ static async createDialog(data = {}, createOptions = {}, options = {}) { const { folders, types, template, context = {}, ...dialogOptions } = options; diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index 3a5a9321..fc73ba95 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -43,16 +43,19 @@ } } + .item-main { + border-radius: 5px; + padding: 2px; + margin: -2px; + } + &:hover { .inventory-item-header .item-label .item-name .expanded-icon { margin-left: 10px; display: inline-block; } - &:has(.inventory-item-content.extensible) { - .inventory-item-header, - .inventory-item-content { - background: light-dark(@dark-blue-40, @golden-40); - } + .item-main { + background: light-dark(@dark-blue-40, @golden-40); } &:has(.inventory-item-content.extended) { .inventory-item-header .item-label .item-name .expanded-icon { @@ -60,19 +63,6 @@ } } } - - &:has(.inventory-item-content.extensible) { - .inventory-item-header { - border-radius: 5px 5px 0 0; - } - .inventory-item-content { - border-radius: 0 0 5px 5px; - } - } - - &:not(:has(.inventory-item-content.extensible)) .inventory-item-header { - border-radius: 5px; - } } .inventory-item-header, @@ -171,7 +161,7 @@ grid-template-rows: 1fr; padding-top: 4px; } - .invetory-description { + .inventory-description { overflow: hidden; h1 { diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index f7d22a30..775690d4 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -25,146 +25,146 @@ Parameters: data-type="{{type}}" data-item-type="{{item.type}}" data-item-uuid="{{item.uuid}}" data-no-compendium-edit="{{noCompendiumEdit}}" > -
        - {{!-- Image --}} -
        - - {{#if (and item.usable (ne showActions false))}} - {{#if @root.isNPC}} - d20 - {{else}} - 2d12 - {{/if}} - {{/if}} -
        - - {{!-- Name & Tags --}} -
        - {{!-- Item Name --}} - {{localize item.name}} {{#unless (or noExtensible (not item.system.description))}}{{/unless}} - - {{!-- Tags Start --}} - {{#if (not hideTags)}} - {{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}} - {{#if (eq ../type 'feature')}} - {{#if (and system.featureForm (ne @root.document.type "character"))}} -
        - {{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}} -
        +
        +
        + {{!-- Image --}} +
        + + {{#if (and item.usable (ne showActions false))}} + {{#if @root.isNPC}} + d20 + {{else}} + 2d12 {{/if}} {{/if}} - {{/ "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs"}} - {{/if}} +
        - {{!--Tags End --}} -
        + {{!-- Name & Tags --}} +
        + {{!-- Item Name --}} + {{localize item.name}} {{#unless (or noExtensible (not item.hasDescription))}}{{/unless}} - {{!-- Simple Resource --}} - {{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}} - {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} - {{/if}} - {{#if (or isQuantifiable (or (eq item.system.quantity 0) (gt item.system.quantity 1)))}} -
        - + {{!-- Tags Start --}} + {{#if (not hideTags)}} + {{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}} + {{#if (and (eq ../type 'feature') system.featureForm (ne @root.document.type "character"))}} +
        + {{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}} +
        + {{/if}} + {{/ "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs"}} + {{/if}} + + {{!--Tags End --}} +
        + + {{!-- Simple Resource --}} + {{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}} + {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} + {{/if}} + {{#if (or isQuantifiable (or (eq item.system.quantity 0) (gt item.system.quantity 1)))}} +
        + +
        + {{/if}} + + {{!-- Controls --}} + {{#unless hideControls}} +
        + {{!-- Toggle/Equip buttons --}} + {{#if @root.editable}} + {{#if (and (eq actorType 'character') (eq type 'weapon'))}} + + + + {{/if}} + {{#if (and (eq actorType 'character') (eq type 'armor'))}} + + + + {{/if}} + {{#if (and (eq type 'domainCard'))}} + + + + {{/if}} + {{#if (and (and (eq type 'effect') (not (eq item.type 'beastform'))))}} + + + + {{/if}} + {{/if}} + + {{!-- Send to Chat --}} + {{#if (hasProperty item "toChat")}} + + + + {{/if}} + + {{!-- Document management buttons or context menu --}} + {{#if (and (not isActor) (not hideContextMenu))}} + + + + {{else if (and @root.editable (not hideModifyControls))}} + + + + {{#if (not isActor)}} + + + + {{else if (eq type 'adversary')}} + + + + {{/if}} + {{/if}} +
        + {{/unless}}
        + {{#unless hideDescription}} +
        + {{!-- Description --}} +
        +
        + {{/unless}} +
        + {{!-- Dice Resource --}} + {{#if (and (not hideResources) (eq item.system.resource.type 'diceValue'))}} + {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} {{/if}} - - {{!-- Controls --}} - {{#unless hideControls}} -
        - {{!-- Toggle/Equip buttons --}} - {{#if @root.editable}} - {{#if (and (eq actorType 'character') (eq type 'weapon'))}} - - - - {{/if}} - {{#if (and (eq actorType 'character') (eq type 'armor'))}} - - - - {{/if}} - {{#if (and (eq type 'domainCard'))}} - - - - {{/if}} - {{#if (and (and (eq type 'effect') (not (eq item.type 'beastform'))))}} - - - - {{/if}} + {{!-- Actions Buttons --}} + {{#if (and showActions item.system.actions.size)}} +
        + {{#each item.system.actions as | action |}} +
        + {{#if (and (eq action.type 'beastform') @root.beastformActive)}} + + {{else}} + {{/if}} - - {{!-- Send to Chat --}} - {{#if (hasProperty item "toChat")}} - - - - {{/if}} - - {{!-- Document management buttons or context menu --}} - {{#if (and (not isActor) (not hideContextMenu))}} - - - - {{else if (and @root.editable (not hideModifyControls))}} - - - - {{#if (not isActor)}} - - - - {{else if (eq type 'adversary')}} - - - - {{/if}} + {{#if action.uses.max}} +
        + {{/if}}
        - {{/unless}} -
        -
        - {{!-- Description --}} - {{#unless hideDescription}} -
        - {{/unless}} -
        - {{!-- Dice Resource --}} - {{#if (and (not hideResources) (eq item.system.resource.type 'diceValue'))}} - {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} - {{/if}} - {{!-- Actions Buttons --}} - {{#if (and showActions item.system.actions.size)}} -
        - {{#each item.system.actions as | action |}} -
        - {{#if (and (eq action.type 'beastform') @root.beastformActive)}} - - {{else}} - - {{/if}} - {{#if action.uses.max}} -
        - - {{/if}} + {{/each}}
        - {{/each}} -
        - {{/if}} -
      • \ No newline at end of file + {{/if}} + From 9c58f7058e3ba99af32bb6ebfbc4af26b59d91d6 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:22:12 +0200 Subject: [PATCH 60/63] [Fix] V13 Migration Fixes (#2044) * Fixed so that damageParts without an applyTo field is assumed to be hitPoints * Added the applyTo field to the basic attack for Adversaries and Companions * Tentative blindfix to issues of entities being null. Can't replicate it * Added some safety for missing things if someone was on a REALLY old system version and then updated all the way * Moved v13 countdown migration over to a migrateData * . --- module/data/action/baseAction.mjs | 8 +++ module/data/actor/adversary.mjs | 1 + module/data/actor/character.mjs | 1 + module/data/actor/companion.mjs | 1 + module/data/countdowns.mjs | 33 ++++++++++++ module/systemRegistration/migrations.mjs | 68 ++++++------------------ 6 files changed, 61 insertions(+), 51 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f3008704..58be672b 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -451,7 +451,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel static migrateData(source) { if (source.damage?.parts && Array.isArray(source.damage.parts)) { + let hitPointsExists = source.damage.parts.some(x => x.applyTo === 'hitPoints'); source.damage.parts = source.damage.parts.reduce((acc, part) => { + if (!part.applyTo && hitPointsExists) return acc; + + if (!part.applyTo) { + hitPointsExists = true; + part.applyTo = 'hitPoints'; + } + acc[part.applyTo] = part; return acc; }, {}); diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index d6d0dcdf..ae17c128 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -87,6 +87,7 @@ export default class DhpAdversary extends DhCreature { parts: { hitPoints: { type: ['physical'], + applyTo: 'hitPoints', value: { multiplier: 'flat' } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 3b12da6f..b39c64aa 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -107,6 +107,7 @@ export default class DhCharacter extends DhCreature { parts: { hitPoints: { type: ['physical'], + applyTo: 'hitPoints', value: { custom: { enabled: true, diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 300bd698..2ca7fd5b 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -102,6 +102,7 @@ export default class DhCompanion extends DhCreature { parts: { hitPoints: { type: ['physical'], + applyTo: 'hitPoints', value: { dice: 'd6', multiplier: 'prof' diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 8e55ed31..ffe4d26b 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -28,6 +28,39 @@ export default class DhCountdowns extends foundry.abstract.DataModel { for (const countdownKey of changedCountdowns) foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey); } + + static migrateData(source) { + const migrateOldCountdowns = (data, type) => { + for (const key of Object.keys(data.countdowns)) { + const countdown = data.countdowns[key]; + source.countdowns[key] = { + ...countdown, + type: type, + ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => { + acc[key] = + countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type; + return acc; + }, {}), + progress: { + ...countdown.progress, + type: countdown.progress.type.value + } + }; + } + + source[type] = null; + }; + + if (source.narrative) { + migrateOldCountdowns(source.narrative, 'narrative'); + } + + if (source.encounter) { + migrateOldCountdowns(source.encounter, 'encounter'); + } + + return super.migrateData(source); + } } export class DhCountdown extends foundry.abstract.DataModel { diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index ec546c92..6971c34c 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -1,5 +1,4 @@ import { defaultRestOptions } from '../config/generalConfig.mjs'; -import { RefreshType, socketEvent } from './socket.mjs'; export async function runMigrations() { let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion); @@ -153,61 +152,26 @@ export async function runMigrations() { await pack.configure({ locked: true }); } - /* Migrate old countdown structure */ - const countdownSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const getCountdowns = (data, type) => { - return Object.keys(data.countdowns).reduce((acc, key) => { - const countdown = data.countdowns[key]; - acc[key] = { - ...countdown, - type: type, - ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => { - acc[key] = - countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type; - return acc; - }, {}), - progress: { - ...countdown.progress, - type: countdown.progress.type.value - } - }; - - return acc; - }, {}); - }; - - await countdownSettings.updateSource({ - countdowns: { - ...getCountdowns(countdownSettings.narrative, 'narrative'), - ...getCountdowns(countdownSettings.encounter, 'encounter') - } - }); - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSettings); - - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.Countdown } - }); - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); - lastMigrationVersion = '1.2.0'; } if (foundry.utils.isNewerVersion('1.2.7', lastMigrationVersion)) { - const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll'); - const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator); - const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => { - if (!game.actors.some(actor => actor.id === id)) { - acc[id] = _del; - } - return acc; - }, {}); + try { + const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll'); + const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator); + const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => { + if (!game.actors.some(actor => actor.id === id)) { + acc[id] = _del; + } + return acc; + }, {}); - await tagTeam.updateSource({ - initiator: initatorMissing ? null : tagTeam.initiator, - members: missingMembers - }); - await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam); + await tagTeam.updateSource({ + initiator: initatorMissing ? null : tagTeam.initiator, + members: missingMembers + }); + await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam); + } catch { } lastMigrationVersion = '1.2.7'; } @@ -303,6 +267,8 @@ export async function runMigrations() { /* Migrate existing effects modifying armor, creating new Armor Effects instead */ const migrateEffects = async entity => { + if (!entity?.effects) return; + for (const effect of entity.effects) { if (effect.system.changes.every(x => x.key !== 'system.armorScore')) continue; From 8c6a470d84d80ac70966e7b0c985daf396637c70 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 29 Jun 2026 14:25:16 +0200 Subject: [PATCH 61/63] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 4660a196..0ecc2e75 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.4.1", + "version": "2.4.2", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -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.4.1/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.2/system.zip", "authors": [ { "name": "WBHarry" From 2cc52fae1f0d4358fb4267310c1f44a3af15d210 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 30 Jun 2026 05:18:13 -0400 Subject: [PATCH 62/63] Remove fieldset from top level notes (#2048) --- styles/less/global/global.less | 3 ++ styles/less/global/prose-mirror.less | 4 ++- .../sheets/actors/actor-sheet-shared.less | 31 +++++++++++++++++++ templates/sheets/actors/adversary/notes.hbs | 12 +++---- templates/sheets/actors/environment/notes.hbs | 5 +-- templates/sheets/actors/npc/notes.hbs | 6 ++-- templates/sheets/actors/party/notes.hbs | 11 +++---- 7 files changed, 49 insertions(+), 23 deletions(-) diff --git a/styles/less/global/global.less b/styles/less/global/global.less index c0e7f3fc..19a9e519 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -12,6 +12,9 @@ } .daggerheart.dh-style { + /** Not an actual scrollbar width (it can't be configured on all browsers) but actually a compensation value for scrollbar gutter purposes */ + --scrollbar-width: 10px; + * { scrollbar-width: thin; scrollbar-color: light-dark(@dark-blue, @golden) transparent; diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index fc8e49f9..27048ddf 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -1,5 +1,6 @@ @import '../utils/colors.less'; @import '../utils/fonts.less'; +@import '../utils/mixin.less'; .application.daggerheart { prose-mirror { @@ -12,6 +13,7 @@ background-color: transparent; } .editor-content { + .with-scroll-shadows(); h1 { font-size: var(--font-size-32); } @@ -42,7 +44,7 @@ ul { list-style: disc; } - } + } // Fixes centering and makes it not render over scrollbar &:hover button.toggle:enabled { display: flex; diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 89617103..5eb5b43c 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -54,6 +54,37 @@ } } + .tab.notes.active { + padding: 0; + margin: 0; + margin-top: -10px; // will be removed once tab-navigation bottom margin is removed on all actor sheets + scrollbar-gutter: unset; + + // Add padding around top level level prosemirrors used for note tabs + > prose-mirror { + @right-padding: calc(16px - var(--scrollbar-width)); + .editor-content { + scrollbar-gutter: stable; + padding-right: @right-padding; + } + &.inactive { + button.toggle { + top: 16px; + } + .editor-content { + padding: 16px @right-padding 4px 16px; + } + } + &.active { + padding: 8px 0 4px 16px; + } + } + + .artist-attribution { + padding-left: 16px; + } + } + .search-section { display: flex; gap: 10px; diff --git a/templates/sheets/actors/adversary/notes.hbs b/templates/sheets/actors/adversary/notes.hbs index a5c3f706..d329d318 100644 --- a/templates/sheets/actors/adversary/notes.hbs +++ b/templates/sheets/actors/adversary/notes.hbs @@ -1,13 +1,9 @@
        -
        - {{localize tabs.notes.label}} - {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} -
        - + {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} {{#if (and showAttribution document.system.attribution.artist)}} {{/if}} diff --git a/templates/sheets/actors/environment/notes.hbs b/templates/sheets/actors/environment/notes.hbs index 4f6b131e..1acf0e93 100644 --- a/templates/sheets/actors/environment/notes.hbs +++ b/templates/sheets/actors/environment/notes.hbs @@ -3,10 +3,7 @@ data-tab='{{tabs.notes.id}}' data-group='{{tabs.notes.group}}' > -
        - {{localize tabs.notes.label}} - {{formInput notes.field value=notes.value enriched=notes.value toggled=true}} -
        + {{formInput notes.field value=notes.value enriched=notes.value toggled=true}} {{#if (and showAttribution document.system.attribution.artist)}} diff --git a/templates/sheets/actors/npc/notes.hbs b/templates/sheets/actors/npc/notes.hbs index bc9ac3cf..7dd5432d 100644 --- a/templates/sheets/actors/npc/notes.hbs +++ b/templates/sheets/actors/npc/notes.hbs @@ -1,7 +1,7 @@
        {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} diff --git a/templates/sheets/actors/party/notes.hbs b/templates/sheets/actors/party/notes.hbs index 663a484a..af21b29f 100644 --- a/templates/sheets/actors/party/notes.hbs +++ b/templates/sheets/actors/party/notes.hbs @@ -1,10 +1,7 @@
        -
        - {{localize tabs.notes.label}} - {{formInput notes.field value=notes.value enriched=notes.value toggled=true}} -
        + {{formInput notes.field value=notes.value enriched=notes.value toggled=true}}
        \ No newline at end of file From 70388dbd736631679fc8c22d17d43f0b8912f7d9 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:05:35 +0200 Subject: [PATCH 63/63] [Fix] SummonAction Actor Choice (#2045) * Fixed logic for picking which actor to summon * Moved getWorldActor function to utils and updated logic * . * Improved logic --- module/data/fields/action/summonField.mjs | 17 ++--------------- module/helpers/utils.mjs | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/module/data/fields/action/summonField.mjs b/module/data/fields/action/summonField.mjs index a2275fa5..6845d2ba 100644 --- a/module/data/fields/action/summonField.mjs +++ b/module/data/fields/action/summonField.mjs @@ -1,4 +1,4 @@ -import { itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs'; +import { getWorldActor, itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs'; import FormulaField from '../formulaField.mjs'; const fields = foundry.data.fields; @@ -42,7 +42,7 @@ export default class DHSummonField extends fields.ArrayField { const count = roll.total; if (!roll.isDeterministic) rolls.push(roll); - const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); + const actor = await 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. */ summon.actor = actor.toObject(); @@ -62,19 +62,6 @@ export default class DHSummonField extends fields.ArrayField { DHSummonField.handleSummon(summonData, this.actor); } - /* Check for any available instances of the actor present in the world if we're missing artwork in the compendium. If none exists, create one. */ - static async getWorldActor(baseActor) { - const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`]; - if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) { - const worldActorCopy = game.actors.find(x => x.name === baseActor.name); - if (worldActorCopy) return worldActorCopy; - - return await game.system.api.documents.DhpActor.create(baseActor.toObject()); - } - - return baseActor; - } - static async handleSummon(summonData, actionActor) { await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation }); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 6467edd7..cb79a76b 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -889,4 +889,27 @@ export async function triggerChatRollFx(rolls, options = { whisper: false, blind export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) { const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm; +} + +export async function getWorldActor(baseActor) { + if (baseActor.inCompendium) { + const worldActorCopy = game.actors.find(x => + x._stats.compendiumSource === baseActor.uuid && + (!x.prototypeToken.actorLink || x.name === baseActor.name) + ); + + if (worldActorCopy) + return worldActorCopy; + + const baseActorData = baseActor; + return await game.system.api.documents.DhpActor.create({ + ...baseActorData, + _stats: { + ...baseActorData._stats, + compendiumSource: baseActor.uuid + } + }); + } + + return baseActor; } \ No newline at end of file