From 5f001a9f83ff5f89f2312401ccb1d11de9792741 Mon Sep 17 00:00:00 2001 From: Nick Salyzyn Date: Sun, 28 Dec 2025 09:26:39 -0700 Subject: [PATCH 01/10] Adding scrollable to various tabs in the homebrew settings (#1483) --- templates/settings/homebrew-settings/domains.hbs | 2 +- templates/settings/homebrew-settings/downtime.hbs | 2 +- templates/settings/homebrew-settings/itemFeatures.hbs | 2 +- templates/settings/homebrew-settings/settings.hbs | 2 +- templates/settings/homebrew-settings/types.hbs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/settings/homebrew-settings/domains.hbs b/templates/settings/homebrew-settings/domains.hbs index 0946f211..b65422c5 100644 --- a/templates/settings/homebrew-settings/domains.hbs +++ b/templates/settings/homebrew-settings/domains.hbs @@ -1,5 +1,5 @@
diff --git a/templates/settings/homebrew-settings/downtime.hbs b/templates/settings/homebrew-settings/downtime.hbs index 890afddc..8612f3d5 100644 --- a/templates/settings/homebrew-settings/downtime.hbs +++ b/templates/settings/homebrew-settings/downtime.hbs @@ -1,5 +1,5 @@
diff --git a/templates/settings/homebrew-settings/itemFeatures.hbs b/templates/settings/homebrew-settings/itemFeatures.hbs index 1f8595de..22c23af6 100644 --- a/templates/settings/homebrew-settings/itemFeatures.hbs +++ b/templates/settings/homebrew-settings/itemFeatures.hbs @@ -1,5 +1,5 @@
diff --git a/templates/settings/homebrew-settings/settings.hbs b/templates/settings/homebrew-settings/settings.hbs index 5da053f4..cdcbd461 100644 --- a/templates/settings/homebrew-settings/settings.hbs +++ b/templates/settings/homebrew-settings/settings.hbs @@ -1,5 +1,5 @@
diff --git a/templates/settings/homebrew-settings/types.hbs b/templates/settings/homebrew-settings/types.hbs index f9d3bba3..bdb92ba0 100644 --- a/templates/settings/homebrew-settings/types.hbs +++ b/templates/settings/homebrew-settings/types.hbs @@ -1,5 +1,5 @@
From 3405b53900fe9be2ac4afc7a69ec49d3689a6e65 Mon Sep 17 00:00:00 2001 From: Nick Salyzyn Date: Sun, 28 Dec 2025 10:31:48 -0700 Subject: [PATCH 02/10] [PR] Adding recall functionality to the loadout tab (#1482) * Adding a Recall button that doesn't yet show a popup * Adding an action with a cost dialog for stress - if there is a stress cost --- lang/en.json | 1 + .../applications/sheets/actors/character.mjs | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lang/en.json b/lang/en.json index 828b2303..68ab0a12 100755 --- a/lang/en.json +++ b/lang/en.json @@ -326,6 +326,7 @@ "equip": "Equip", "sendToChat": "Send To Chat", "toLoadout": "Send to Loadout", + "recall": "Recall", "toVault": "Send to Vault", "unequip": "Unequip", "useItem": "Use Item" diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 66ed6315..b48d459d 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -318,6 +318,40 @@ export default class CharacterSheet extends DHBaseActorSheet { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached')); } }, + { + name: 'recall', + icon: 'fa-solid fa-bolt-lightning', + condition: target => { + const doc = getDocFromElementSync(target); + return doc && doc.system.inVault; + }, + callback: async (target, event) => { + const doc = await getDocFromElement(target); + const actorLoadout = doc.actor.system.loadoutSlot; + if (!actorLoadout.available) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached')); + return; + } + if (doc.system.recallCost == 0) { + return doc.update({ 'system.inVault': false }); + } + const type = 'effect'; + const cls = game.system.api.models.actions.actionsTypes[type]; + const action = new cls({ + ...cls.getSourceConfig(doc.system), + type: type, + chatDisplay: false, + cost: [{ + key: 'stress', + value: doc.system.recallCost + }] + }, { parent: doc.system }); + const config = await action.use(event); + if (config) { + return doc.update({ 'system.inVault': false }); + } + } + }, { name: 'toVault', icon: 'fa-solid fa-arrow-down', From c83fe25a476b25bc7e1bdb395f77c7021c984142 Mon Sep 17 00:00:00 2001 From: Nick Salyzyn Date: Sun, 28 Dec 2025 10:50:26 -0700 Subject: [PATCH 03/10] Using the same drag-drop approach in environments and adversaries as in the character sheet (#1481) --- module/applications/sheets/actors/adversary.mjs | 17 ++++++++++++++++- .../applications/sheets/actors/environment.mjs | 7 ++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 345f6fed..789543cf 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -26,7 +26,12 @@ export default class AdversarySheet extends DHBaseActorSheet { } ] }, - dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }] + dragDrop: [ + { + dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', + dropSelector: null + } + ], }; static PARTS = { @@ -164,6 +169,16 @@ 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/environment.mjs b/module/applications/sheets/actors/environment.mjs index 9a09cd94..01a60ec1 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -25,7 +25,12 @@ export default class DhpEnvironment extends DHBaseActorSheet { toggleResourceDice: DhpEnvironment.#toggleResourceDice, handleResourceDice: DhpEnvironment.#handleResourceDice }, - dragDrop: [{ dragSelector: '.inventory-item', dropSelector: null }] + dragDrop: [ + { + dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', + dropSelector: null + } + ], }; /**@override */ From f11b018bd727e1e348124b47f765d404f3afd35e Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 28 Dec 2025 13:07:12 -0500 Subject: [PATCH 04/10] Support drag/drop resorting of adversary features (#1469) --- module/applications/sheets/actors/adversary.mjs | 7 +++++++ templates/sheets/actors/adversary/features.hbs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 789543cf..98282d9f 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -93,6 +93,13 @@ 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']; + context.features = this.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); + return context; } diff --git a/templates/sheets/actors/adversary/features.hbs b/templates/sheets/actors/adversary/features.hbs index 98c1cedb..a24342fc 100644 --- a/templates/sheets/actors/adversary/features.hbs +++ b/templates/sheets/actors/adversary/features.hbs @@ -4,7 +4,7 @@ {{> 'daggerheart.inventory-items' title=tabs.features.label type='feature' - collection=document.system.features + collection=@root.features hideContextMenu=true canCreate=true showActions=true From d0e55aeb8d6bf989439dc5a3ce0a2d7c0905f0ff Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:16:24 +0100 Subject: [PATCH 05/10] Resource Generation Fix --- module/dice/dualityRoll.mjs | 2 +- module/documents/actor.mjs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 91c0a197..d2e20213 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -298,7 +298,7 @@ export default class DualityRoll extends D20Roll { if (looseSpotlight && game.combat?.active) { const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId); - if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id); + if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id); } } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 9f4db5e2..6080eb87 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -543,6 +543,7 @@ export default class DhpActor extends Actor { /* system gets repeated infinately which causes issues when trying to use the data for document creation */ delete rollData.system; + rollData.id = this.id; rollData.name = this.name; rollData.system = this.system.getRollData(); rollData.prof = this.system.proficiency ?? 1; From c070c6cc2d2f9378c340e30f6cab21b847689696 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 29 Dec 2025 04:44:49 +0100 Subject: [PATCH 06/10] [Fix] Evolved Beastform Wildcard (#1486) * Fixed so picking an evolved beastform will properly handle wildcard images * . --- module/applications/dialogs/beastformDialog.mjs | 15 +++++++++++---- module/data/fields/action/beastformField.mjs | 2 +- system.json | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/module/applications/dialogs/beastformDialog.mjs b/module/applications/dialogs/beastformDialog.mjs index 3dd88d6c..09a9222b 100644 --- a/module/applications/dialogs/beastformDialog.mjs +++ b/module/applications/dialogs/beastformDialog.mjs @@ -278,19 +278,26 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat 'close', async () => { const selected = app.selected.toObject(); + const evolved = app.evolved.form ? app.evolved.form.toObject() : null; const data = await game.system.api.data.items.DHBeastform.getWildcardImage( app.configData.data.parent, - app.selected + evolved ?? app.selected ); if (data) { if (!data.selectedImage) selected = null; else { - if (data.usesDynamicToken) selected.system.tokenRingImg = data.selectedImage; - else selected.system.tokenImg = data.selectedImage; + const imageSource = evolved ?? selected; + if (imageSource.usesDynamicToken) imageSource.system.tokenRingImg = data.selectedImage; + else imageSource.system.tokenImg = data.selectedImage; } } - resolve({ selected: selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem }); + resolve({ + selected: selected, + evolved: { ...app.evolved, form: evolved }, + hybrid: app.hybrid, + item: featureItem + }); }, { once: true } ); diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs index 6185f0f8..e19807c7 100644 --- a/module/data/fields/action/beastformField.mjs +++ b/module/data/fields/action/beastformField.mjs @@ -76,7 +76,7 @@ export default class BeastformField extends fields.SchemaField { * @returns */ static async transform(selectedForm, evolvedData, hybridData) { - const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm; + const formData = evolvedData?.form ?? selectedForm; const beastformEffect = formData.effects.find(x => x.type === 'beastform'); if (!beastformEffect) { ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); diff --git a/system.json b/system.json index 0dcbc398..ce361568 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.4.2", + "version": "1.4.3", "compatibility": { "minimum": "13.346", "verified": "13.351", From f6bd1430e3e6763afd524e9b0477a4a62e1f3085 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 29 Dec 2025 13:02:22 +0100 Subject: [PATCH 07/10] Fixed drag/drop of features for environments (#1488) --- .../sheets-configs/adversary-settings.mjs | 19 ++++++++++++++--- .../sheets-configs/environment-settings.mjs | 13 ++++++++++++ .../sheets/actors/environment.mjs | 21 ++++++++++++++++++- .../adversary-settings/features.hbs | 2 +- .../environment-settings/features.hbs | 2 +- .../sheets/actors/environment/features.hbs | 2 +- 6 files changed, 52 insertions(+), 7 deletions(-) diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index bcc8b1c9..d3d215be 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -51,6 +51,19 @@ export default class DHAdversarySettings 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 + ); + + return context; + } + /* -------------------------------------------- */ /** @@ -98,16 +111,16 @@ export default class DHAdversarySettings extends DHBaseActorSettings { async _onDrop(event) { 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 2efa3b38..15f5701d 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -49,6 +49,19 @@ 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 + ); + + return context; + } + /** * Adds a new category entry to the actor. * @type {ApplicationClickAction} diff --git a/module/applications/sheets/actors/environment.mjs b/module/applications/sheets/actors/environment.mjs index 01a60ec1..f8ff74a6 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -30,7 +30,7 @@ export default class DhpEnvironment extends DHBaseActorSheet { dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', dropSelector: null } - ], + ] }; /**@override */ @@ -79,6 +79,9 @@ export default class DhpEnvironment extends DHBaseActorSheet { case 'header': await this._prepareHeaderContext(context, options); + break; + case 'features': + await this._prepareFeaturesContext(context, options); break; case 'notes': await this._prepareNotesContext(context, options); @@ -115,6 +118,22 @@ export default class DhpEnvironment extends DHBaseActorSheet { } } + /** + * Prepare render context for the features part. + * @param {ApplicationRenderContext} context + * @param {ApplicationRenderOptions} options + * @returns {Promise} + * @protected + */ + async _prepareFeaturesContext(context, _options) { + const featureForms = ['passive', 'action', 'reaction']; + context.features = this.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); + } + /** * Prepare render context for the Header part. * @param {ApplicationRenderContext} context diff --git a/templates/sheets-settings/adversary-settings/features.hbs b/templates/sheets-settings/adversary-settings/features.hbs index bc6a1ddf..2f2f5f47 100644 --- a/templates/sheets-settings/adversary-settings/features.hbs +++ b/templates/sheets-settings/adversary-settings/features.hbs @@ -9,7 +9,7 @@
{{localize tabs.features.label}}
    - {{#each document.system.features as |feature|}} + {{#each @root.features as |feature|}}
  • diff --git a/templates/sheets-settings/environment-settings/features.hbs b/templates/sheets-settings/environment-settings/features.hbs index 13a76f06..579fe74e 100644 --- a/templates/sheets-settings/environment-settings/features.hbs +++ b/templates/sheets-settings/environment-settings/features.hbs @@ -9,7 +9,7 @@
    {{localize tabs.features.label}}
      - {{#each document.system.features as |feature|}} + {{#each @root.features as |feature|}}
    • diff --git a/templates/sheets/actors/environment/features.hbs b/templates/sheets/actors/environment/features.hbs index 4c0e3519..3ad36023 100644 --- a/templates/sheets/actors/environment/features.hbs +++ b/templates/sheets/actors/environment/features.hbs @@ -7,7 +7,7 @@ {{> 'daggerheart.inventory-items' title=tabs.features.label type='feature' - collection=document.system.features + collection=@root.features hideContextMenu=true canCreate=true showActions=true From e8c541c0020cbbaeeb3753d16f7db01278d60f45 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:00:40 +0100 Subject: [PATCH 08/10] Added damage reduction rules (#1491) --- module/data/actor/adversary.mjs | 22 ++-------- module/data/actor/base.mjs | 30 ++++++++++--- module/data/actor/character.mjs | 77 +++++++++++++++------------------ module/documents/actor.mjs | 15 ++++++- 4 files changed, 77 insertions(+), 67 deletions(-) diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 32f5c979..16e7e37a 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -1,6 +1,6 @@ import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs'; import { ActionField } from '../fields/actionField.mjs'; -import BaseDataActor from './base.mjs'; +import BaseDataActor, { commonActorRules } from './base.mjs'; import { resourceField, bonusField } from '../fields/actorField.mjs'; export default class DhpAdversary extends BaseDataActor { @@ -56,25 +56,11 @@ export default class DhpAdversary extends BaseDataActor { }) }), resources: new fields.SchemaField({ - hitPoints: resourceField( - 0, - 0, - 'DAGGERHEART.GENERAL.HitPoints.plural', - true - ), - stress: resourceField( - 0, - 0, - 'DAGGERHEART.GENERAL.stress', - true - ) + hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true), + stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true) }), rules: new fields.SchemaField({ - conditionImmunities: new fields.SchemaField({ - hidden: new fields.BooleanField({ initial: false }), - restrained: new fields.BooleanField({ initial: false }), - vulnerable: new fields.BooleanField({ initial: false }) - }) + ...commonActorRules() }), attack: new ActionField({ initial: { diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 29b0af28..b90361e2 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -2,21 +2,23 @@ import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs import DHItem from '../../documents/item.mjs'; import { getScrollTextData } from '../../helpers/utils.mjs'; +const fields = foundry.data.fields; + const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) => - new foundry.data.fields.SchemaField({ - resistance: new foundry.data.fields.BooleanField({ + new fields.SchemaField({ + resistance: new fields.BooleanField({ initial: false, label: `${resistanceLabel}.label`, hint: `${resistanceLabel}.hint`, isAttributeChoice: true }), - immunity: new foundry.data.fields.BooleanField({ + immunity: new fields.BooleanField({ initial: false, label: `${immunityLabel}.label`, hint: `${immunityLabel}.hint`, isAttributeChoice: true }), - reduction: new foundry.data.fields.NumberField({ + reduction: new fields.NumberField({ integer: true, initial: 0, label: `${reductionLabel}.label`, @@ -24,6 +26,25 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) => }) }); +/* Common rules applying to Characters and Adversaries */ +export const commonActorRules = (extendedData = { damageReduction: {} }) => ({ + conditionImmunities: new fields.SchemaField({ + hidden: new fields.BooleanField({ initial: false }), + restrained: new fields.BooleanField({ initial: false }), + vulnerable: new fields.BooleanField({ initial: false }) + }), + damageReduction: new fields.SchemaField({ + thresholdImmunities: new fields.SchemaField({ + minor: new fields.BooleanField({ initial: false }) + }), + reduceSeverity: new fields.SchemaField({ + magical: new fields.NumberField({ initial: 0, min: 0 }), + physical: new fields.NumberField({ initial: 0, min: 0 }) + }), + ...extendedData.damageReduction + }) +}); + /** * Describes metadata about the actor data model type * @typedef {Object} ActorDataModelMetadata @@ -54,7 +75,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { /** @inheritDoc */ static defineSchema() { - const fields = foundry.data.fields; const schema = {}; if (this.metadata.hasAttribution) { diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 5bce5c55..eba46f10 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -1,7 +1,7 @@ import { burden } from '../../config/generalConfig.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import DhLevelData from '../levelData.mjs'; -import BaseDataActor from './base.mjs'; +import BaseDataActor, { commonActorRules } from './base.mjs'; import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { ActionField } from '../fields/actionField.mjs'; import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs'; @@ -217,44 +217,41 @@ export default class DhCharacter extends BaseDataActor { }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), rules: new fields.SchemaField({ - damageReduction: new fields.SchemaField({ - maxArmorMarked: new fields.SchemaField({ - value: new fields.NumberField({ - required: true, + ...commonActorRules({ + damageReduction: { + magical: new fields.BooleanField({ initial: false }), + physical: new fields.BooleanField({ initial: false }), + maxArmorMarked: new fields.SchemaField({ + value: new fields.NumberField({ + required: true, + integer: true, + initial: 1, + label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus' + }), + stressExtra: new fields.NumberField({ + required: true, + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label', + hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint' + }) + }), + stressDamageReduction: new fields.SchemaField({ + severe: stressDamageReductionRule( + 'DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe' + ), + major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'), + minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'), + any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any') + }), + increasePerArmorMark: new fields.NumberField({ integer: true, initial: 1, - label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus' + label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label', + hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint' }), - stressExtra: new fields.NumberField({ - required: true, - integer: true, - initial: 0, - label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label', - hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint' - }) - }), - stressDamageReduction: new fields.SchemaField({ - severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'), - major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'), - minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'), - any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any') - }), - increasePerArmorMark: new fields.NumberField({ - integer: true, - initial: 1, - label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label', - hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint' - }), - magical: new fields.BooleanField({ initial: false }), - physical: new fields.BooleanField({ initial: false }), - thresholdImmunities: new fields.SchemaField({ - minor: new fields.BooleanField({ initial: false }) - }), - reduceSeverity: new fields.SchemaField({ - magical: new fields.NumberField({ initial: 0, min: 0 }), - physical: new fields.NumberField({ initial: 0, min: 0 }) - }), - disabledArmor: new fields.BooleanField({ intial: false }) + disabledArmor: new fields.BooleanField({ intial: false }) + } }), attack: new fields.SchemaField({ damage: new fields.SchemaField({ @@ -283,11 +280,6 @@ export default class DhCharacter extends BaseDataActor { }) }) }), - conditionImmunities: new fields.SchemaField({ - hidden: new fields.BooleanField({ initial: false }), - restrained: new fields.BooleanField({ initial: false }), - vulnerable: new fields.BooleanField({ initial: false }) - }), runeWard: new fields.BooleanField({ initial: false }), burden: new fields.SchemaField({ ignore: new fields.BooleanField() @@ -453,8 +445,7 @@ export default class DhCharacter extends BaseDataActor { if ( item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation || - (item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization && - subclassState >= 2) || + (item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) || (item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3) ) { return true; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 6080eb87..f6666a5e 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -1,7 +1,7 @@ import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; import { LevelOptionType } from '../data/levelTier.mjs'; import DHFeature from '../data/item/feature.mjs'; -import { createScrollText, damageKeyToNumber } from '../helpers/utils.mjs'; +import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs'; import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs'; import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; @@ -631,6 +631,19 @@ export default class DhpActor extends Actor { } } } + if (this.type === 'adversary') { + const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => { + return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value); + }, 0); + hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0); + + if ( + hpDamage.value && + this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)] + ) { + hpDamage.value -= 1; + } + } } updates.forEach( From 3b7b6258a12cf4295b40d0da4261b0973599bac0 Mon Sep 17 00:00:00 2001 From: Nick Salyzyn Date: Mon, 29 Dec 2025 13:55:13 -0700 Subject: [PATCH 09/10] [PR] Adding the ability to target downtime actions. (#1475) * Adding the ability to target downtime actions. * No longer using an arbitrary 100 healing. Changing the action's parent --- lang/en.json | 4 +++- module/applications/dialogs/downtime.mjs | 11 +++++++++-- module/applications/ui/chatLog.mjs | 7 +++++-- module/config/generalConfig.mjs | 8 ++++---- module/data/action/baseAction.mjs | 6 ++++-- module/data/fields/action/targetField.mjs | 7 +++++-- styles/less/ui/chat/downtime.less | 23 +++++++++++++++++++++++ templates/ui/chat/downtime.hbs | 19 ++++++++++++++++--- 8 files changed, 69 insertions(+), 16 deletions(-) diff --git a/lang/en.json b/lang/en.json index 68ab0a12..3f8c4321 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1801,7 +1801,9 @@ "label": "Long Rest: Bonus Long Rest Moves", "hint": "The number of extra Long Rest Moves the character can take during a Long Rest." } - } + }, + "target": "Target", + "targetSelf": "Self" }, "maxLoadout": { "label": "Max Loadout Cards Bonus" diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 3d5b7f0f..f03524f0 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -181,12 +181,17 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV .filter(x => category.moves[x].selected) .flatMap(key => { const move = category.moves[key]; + const needsTarget = move.actions.filter(x => x.target?.type && x.target.type !== 'self').length > 0; return [...Array(move.selected).keys()].map(_ => ({ ...move, - movePath: `${categoryKey}.moves.${key}` + movePath: `${categoryKey}.moves.${key}`, + needsTarget: needsTarget })); }); }); + const characters = game.actors.filter(x => x.type === 'character') + .filter(x => x.testUserPermission(game.user, 'LIMITED')) + .filter(x => x.uuid !== this.actor.uuid); const cls = getDocumentClass('ChatMessage'); const msg = { @@ -206,7 +211,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV `DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title` ), actor: { name: this.actor.name, img: this.actor.img }, - moves: moves + moves: moves, + characters: characters, + selfId: this.actor.uuid } ), flags: { diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 45d9c0b3..cc42df2f 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -134,7 +134,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo async actionUseButton(event, message) { const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset; - const parent = await foundry.utils.fromUuid(message.system.actor); + const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value; + const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor) + const actionType = message.system.moves[moveIndex].actions[actionIndex]; const cls = game.system.api.models.actions.actionsTypes[actionType.type]; const action = new cls( @@ -146,7 +148,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo type: CONFIG.DH.ITEM.originItemType.restMove, itemPath: movePath, actionIndex: actionIndex - } + }, + targetUuid: targetUuid }, { parent: parent.system } ); diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 2c2b1316..3f49f7aa 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -232,7 +232,7 @@ export const defaultRestOptions = { actionType: 'action', chatDisplay: false, target: { - type: 'self' + type: 'friendly' }, damage: { parts: [ @@ -298,7 +298,7 @@ export const defaultRestOptions = { actionType: 'action', chatDisplay: false, target: { - type: 'self' + type: 'friendly' }, damage: { parts: [ @@ -341,7 +341,7 @@ export const defaultRestOptions = { actionType: 'action', chatDisplay: false, target: { - type: 'self' + type: 'friendly' }, damage: { parts: [ @@ -407,7 +407,7 @@ export const defaultRestOptions = { actionType: 'action', chatDisplay: false, target: { - type: 'self' + type: 'friendly' }, damage: { parts: [ diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 6c7b8c59..18a09904 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -33,7 +33,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel initial: 'action', nullable: false, required: true - }) + }), + targetUuid: new fields.StringField({ initial: undefined }) }; this.extraSchemas.forEach(s => { @@ -241,7 +242,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel selectedRollMode: game.settings.get('core', 'rollMode'), data: this.getRollData(), evaluate: this.hasRoll, - resourceUpdates: new ResourceUpdateMap(this.actor) + resourceUpdates: new ResourceUpdateMap(this.actor), + targetUuid: this.targetUuid }; DHBaseAction.applyKeybindings(config); diff --git a/module/data/fields/action/targetField.mjs b/module/data/fields/action/targetField.mjs index 73766118..3a4f12df 100644 --- a/module/data/fields/action/targetField.mjs +++ b/module/data/fields/action/targetField.mjs @@ -25,9 +25,12 @@ export default class TargetField extends fields.SchemaField { config.hasTarget = true; let targets; // If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens - if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) + if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) { targets = [this.actor.token ?? this.actor.prototypeToken]; - else { + } else if (config.targetUuid) { + const actor = fromUuidSync(config.targetUuid); + targets = [actor.token ?? actor.prototypeToken]; + } else { targets = Array.from(game.user.targets); if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) { targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type)); diff --git a/styles/less/ui/chat/downtime.less b/styles/less/ui/chat/downtime.less index 5496a2a3..a99bde33 100644 --- a/styles/less/ui/chat/downtime.less +++ b/styles/less/ui/chat/downtime.less @@ -99,12 +99,35 @@ } } + .action-use-button-parent { + width: 100%; + + .action-use-target { + display:flex; + align-items: center; + justify-content: space-between; + gap: 4px; + width: 100%; + padding: 4px 8px 10px 40px; + font-size: var(--font-size-12); + + label { + font-weight: bold; + } + + select { + flex: 1; + } + } + } + .action-use-button { width: -webkit-fill-available; margin: 0 8px; font-weight: 600; height: 40px; } + } } } diff --git a/templates/ui/chat/downtime.hbs b/templates/ui/chat/downtime.hbs index ef1f44c4..373724dc 100644 --- a/templates/ui/chat/downtime.hbs +++ b/templates/ui/chat/downtime.hbs @@ -15,9 +15,22 @@
      {{#each move.actions as | action index |}} - +
      + + {{#if move.needsTarget}} +
      + + +
      + {{/if}} +
      {{/each}} {{/each}}
    From bca7e0d3c9d8778191e6ffad96d829a5b18a45a0 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 31 Dec 2025 04:52:19 +0100 Subject: [PATCH 10/10] [Fix] Beastforms Getting Stuck (#1495) * Fixed beastforms getting stuck * Raised version --- module/data/activeEffect/beastformEffect.mjs | 20 ++++++++++++++------ module/data/item/beastform.mjs | 20 ++++++++++++++------ system.json | 2 +- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index b041b59e..5311b827 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -66,12 +66,20 @@ export default class BeastformEffect extends BaseEffect { }; const updateToken = token => { - const { x, y } = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( - token.object.scene.grid, - { x: token.x, y: token.y, elevation: token.elevation }, - baseUpdate.width, - baseUpdate.height - ); + let x = null, + y = null; + if (token.object?.scene?.grid) { + const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( + token.object.scene.grid, + { x: token.x, y: token.y, elevation: token.elevation }, + baseUpdate.width, + baseUpdate.height + ); + + x = positionData.x; + y = positionData.y; + } + return { ...baseUpdate, x, diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index 669cd4b1..1840e26a 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -218,12 +218,20 @@ export default class DHBeastform extends BaseDataItem { } }; const tokenUpdate = token => { - const { x, y } = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( - token.object.scene.grid, - { x: token.x, y: token.y, elevation: token.elevation }, - width ?? token.width, - height ?? token.height - ); + let x = null, + y = null; + if (token.object?.scene?.grid) { + const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( + token.object.scene.grid, + { x: token.x, y: token.y, elevation: token.elevation }, + width ?? token.width, + height ?? token.height + ); + + x = positionData.x; + y = positionData.y; + } + return { ...prototypeTokenUpdate, x, diff --git a/system.json b/system.json index ce361568..9f1e46c7 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.4.3", + "version": "1.4.4", "compatibility": { "minimum": "13.346", "verified": "13.351",