From c5308617e1880eba81d76b8e932fac169ac0bce6 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 27 Jun 2026 22:31:36 +0200 Subject: [PATCH 1/6] Moved v13 countdown migration over to a migrateData --- module/data/countdowns.mjs | 33 ++++++++++++++++++++ module/systemRegistration/migrations.mjs | 38 ------------------------ 2 files changed, 33 insertions(+), 38 deletions(-) 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..a77c6d8b 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,43 +152,6 @@ 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'; } From f02e97f0cdf6483c63e33aeb11ef915e69b55c72 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 27 Jun 2026 23:10:33 +0200 Subject: [PATCH 2/6] Revert "Moved v13 countdown migration over to a migrateData" This reverts commit c5308617e1880eba81d76b8e932fac169ac0bce6. --- module/data/countdowns.mjs | 33 -------------------- module/systemRegistration/migrations.mjs | 38 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index ffe4d26b..8e55ed31 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -28,39 +28,6 @@ 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 a77c6d8b..ec546c92 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -1,4 +1,5 @@ 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); @@ -152,6 +153,43 @@ 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'; } From b3d96c25d931345f2ff7bc787a2f53bc0e0c4736 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 3 Jul 2026 04:37:43 -0400 Subject: [PATCH 3/6] Fix adjusting tiers of higher level creatures and an error that may occur with hordes (#2059) --- module/data/actor/tierAdjustment.mjs | 56 +++++++++++++++++----------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs index bc6ad176..8b9e5bdc 100644 --- a/module/data/actor/tierAdjustment.mjs +++ b/module/data/actor/tierAdjustment.mjs @@ -2,8 +2,14 @@ import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpe import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; import { parseInlineParams } from '../../enrichers/parser.mjs'; +/** + * Accepts source data for an adversary and a target tier, and returns new source data + * @type {object} source + * @type {number} tier + * @returns {object} adjusted source data + */ export function getTierAdjustedAdversary(source, tier) { - const currentTier = source.tier ?? 1; + const currentTier = source.system.tier ?? 1; /** @type {(2 | 3 | 4)[]} */ const tiers = new Array(Math.abs(tier - currentTier)) @@ -35,7 +41,7 @@ export function getTierAdjustedAdversary(source, tier) { // Store initial attack damage for abilities that have you deal a "standard attack" const initialAttack = { type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(), - value: getDamagePartsFormula(source.system.attack.damage?.parts.hitPoints?.value) + value: getFormula(source.system.attack.damage?.parts.hitPoints?.value) }; // Update damage of base attack. @@ -45,9 +51,9 @@ export function getTierAdjustedAdversary(source, tier) { for (const property of ['value', 'valueAlt']) { const data = damage.parts.hitPoints[property]; - const previousFormula = getDamagePartsFormula(data); - const { value, formula } = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); - applyAdjustedDamage(data, value, formula); + const previousFormula = getFormula(data); + const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); + applyAdjustedDamage(data, value); } } catch (err) { ui.notifications.warn('Failed to convert attack damage of adversary'); @@ -65,7 +71,7 @@ export function getTierAdjustedAdversary(source, tier) { if (!formula) return match; try { - const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula; + const newFormula = getFormula(calculateAdjustedDamage(formula, 'action', damageMeta)); descriptionFormulas.push(formula); return match.replace(formula, newFormula); } catch { @@ -82,15 +88,15 @@ export function getTierAdjustedAdversary(source, tier) { const result = []; for (const property of ['value', 'valueAlt']) { const { [property]: data, type: damageType } = action.damage.parts.hitPoints; - const previousFormula = getDamagePartsFormula(data); + const previousFormula = getFormula(data); const isActuallyAttack = previousFormula === initialAttack.value && foundry.utils.equals(damageType.toSorted(), initialAttack.type) && !descriptionFormulas.includes(previousFormula); const type = isActuallyAttack ? 'attack' : 'action'; - const { value, formula } = calculateAdjustedDamage(previousFormula, type, damageMeta); - applyAdjustedDamage(data, value, formula); - result.push({ previousFormula, formula }); + const value = calculateAdjustedDamage(previousFormula, type, damageMeta); + applyAdjustedDamage(data, value); + result.push({ previousFormula, formula: getFormula(value) }); } // Override text in the description with those values @@ -189,24 +195,30 @@ function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageR value.bonus = Math.round(expected - getBaseAverage()); } - const newFormula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus] - .filter(p => !!p) - .join('+'); - return { value, formula: newFormula }; + return value; } -function getDamagePartsFormula(data) { - return data.custom.enabled - ? data.custom.formula - : [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0].filter(p => !!p).join('+'); +/** + * Get formula from either damage parts *or* a simple formula object. + * @returns {string} the new formula data + */ +function getFormula(data) { + if (data.custom?.enabled) { + return data.custom.formula; + } + + const diceQuantity = data.flatMultiplier ?? data.diceQuantity; + const dice = data.faces ? `d${data.faces}` : data.dice; + const mod = data.bonus; + return [diceQuantity ? `${diceQuantity}${dice}` : 0, mod].filter(p => !!p).join('+'); } /** * Updates damage to reflect a specific value. - * @throws if damage structure is invalid for conversion - * @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage + * @param {object} diceData + * @param {object} value */ -function applyAdjustedDamage(diceData, value, formula) { +function applyAdjustedDamage(diceData, value) { if (value.diceQuantity) { diceData.custom.enabled = false; diceData.bonus = value.bonus; @@ -214,6 +226,6 @@ function applyAdjustedDamage(diceData, value, formula) { diceData.flatMultiplier = value.diceQuantity; } else if (!value.diceQuantity) { diceData.custom.enabled = true; - diceData.custom.formula = formula; + diceData.custom.formula = getFormula(value); } } From 44dd3bd2e1b8f0b1d3d0cef8112a2a074aae9e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20Br=C3=A4utigam?= Date: Fri, 3 Jul 2026 11:05:28 +0200 Subject: [PATCH 4/6] [Feature] add resources to weapons (#2058) * feat: add resources to weapons Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * remove sidebar resource view Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> --------- Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> --- README.md | 18 ++++++++++++------ module/data/item/weapon.mjs | 3 ++- .../sheets/actors/character/inventory.hbs | 1 - templates/sheets/items/weapon/settings.hbs | 1 + 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 177636c7..6c90e73e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,17 @@ ## Table of Contents -- [Overview](#overview) -- [User Install Guide](#user-install) -- [Documentation](#documentation) -- [Developer Setup](#development-setup) -- [Contribution Info](#contributing) +- [Foundryborne Daggerheart](#foundryborne-daggerheart) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [User Install](#user-install) + - [Documentation](#documentation) + - [Development Setup](#development-setup) + - [Available Scripts](#available-scripts) + - [Notes](#notes) + - [Contributing](#contributing) + - [AI Policy](#ai-policy) + - [Disclaimer](#disclaimer) ## Overview @@ -72,7 +78,7 @@ Looking to contribute to the project? Look no further, check out our [contributi 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: +## Disclaimer **Daggerheart System** Daggerheart is a trademark of Darrington Press LLC. All original content, mechanics, and intellectual property related to the Daggerheart roleplaying game are © Darrington Press LLC. diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 39c0fc8e..42326f93 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -9,7 +9,8 @@ export default class DHWeapon extends AttachableItem { type: 'weapon', hasDescription: true, isInventoryItem: true, - hasActions: true + hasActions: true, + hasResource: true }); } diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index c3ddb0ad..61f98ab5 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -23,7 +23,6 @@ collection=@root.inventory.weapons isGlassy=true canCreate=@root.editable - hideResources=true }} {{> 'daggerheart.inventory-items' title='TYPES.Item.armor' diff --git a/templates/sheets/items/weapon/settings.hbs b/templates/sheets/items/weapon/settings.hbs index ef47b323..c9805e7c 100644 --- a/templates/sheets/items/weapon/settings.hbs +++ b/templates/sheets/items/weapon/settings.hbs @@ -45,4 +45,5 @@ {{localize "TYPES.Item.feature"}} + {{> "systems/daggerheart/templates/sheets/global/partials/resource-section/resource-section.hbs" }} \ No newline at end of file From 98fedf399079b566aee8a389aa735a920f42837c Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:14:25 +0200 Subject: [PATCH 5/6] [Feature] Transform Linked Actors (#2046) * Added ability to transform linked actors * Updated to use the utils getWorldActor function * Animate change and fix error when converting to unlinked --------- Co-authored-by: Carlos Fernandez --- daggerheart.d.ts | 23 ++++++++ lang/en.json | 1 + module/applications/ui/effectsDisplay.mjs | 6 ++ module/data/action/baseAction.mjs | 7 ++- module/data/fields/action/transformField.mjs | 61 ++++++++++++-------- module/documents/_types.d.ts | 13 ++++- module/documents/actor.mjs | 8 ++- module/documents/token.mjs | 3 +- module/helpers/utils.mjs | 11 ++++ 9 files changed, 101 insertions(+), 32 deletions(-) diff --git a/daggerheart.d.ts b/daggerheart.d.ts index 1641faa8..891a3a2a 100644 --- a/daggerheart.d.ts +++ b/daggerheart.d.ts @@ -4,6 +4,15 @@ import '@common/primitives/global.mjs'; import Canvas from '@client/canvas/board.mjs'; import { ResourceUpdateMap } from './module/data/action/baseAction.mjs'; +import * as applications from './module/applications/_module.mjs'; +import * as data from './module/data/_module.mjs'; +import * as models from './module/data/_module.mjs'; +import * as documents from './module/documents/_module.mjs'; +import { macros } from './module/_module.mjs'; +import * as dice from './module/dice/_module.mjs'; +import * as fields from './module/data/fields/_module.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 @@ -80,3 +89,17 @@ declare global { damageOptions: object; } } + +declare module '@client/packages/system.mjs' { + export default interface System { + api: { + applications: typeof applications, + data: typeof data, + models: typeof models, + documents: typeof documents, + macros: typeof macros, + dice: typeof dice, + fields: typeof fields + }; + } +} diff --git a/lang/en.json b/lang/en.json index 0180b085..b9a81b29 100755 --- a/lang/en.json +++ b/lang/en.json @@ -84,6 +84,7 @@ "transformActorMissing": "The assigned actor to transform into does not exist. It was probably deleted or moved in/out of a compendium", "canvasError": "There is no active scene.", "prototypeError": "You can only use a transform action from a Token", + "linkedSelectedError": "To transform a linked actor there either needs to be only a single token of it on the canvas, or you need to left-click select only one of them.", "actorLinkError": "You cannot transform a token with Actor Link set to true" } }, diff --git a/module/applications/ui/effectsDisplay.mjs b/module/applications/ui/effectsDisplay.mjs index a64b1b22..c9739f56 100644 --- a/module/applications/ui/effectsDisplay.mjs +++ b/module/applications/ui/effectsDisplay.mjs @@ -39,6 +39,12 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica } }; + /** + * Debounce and slightly delayed request to re-render this panel. Necessary for situations where it is not possible + * to properly wait for promises to resolve before refreshing the UI. + */ + refresh = foundry.utils.debounce(this.render.bind(this), 50); + get element() { return document.body.querySelector('.daggerheart.dh-style.effects-display'); } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 58be672b..be7224cd 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -114,7 +114,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return this._id; } - /** Returns true if the current user is the owner of the containing item */ + /** + * Returns true if the current user is the owner of the containing item. + * @returns {boolean} + */ get isOwner() { return this.item?.isOwner ?? true; } @@ -143,6 +146,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /** * Return the first Actor parent found. + * @returns {DhpActor | null} */ get actor() { return this.item instanceof DhpActor @@ -155,6 +159,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /** * Returns true if the action is usable. * An action is usable on any actor type. For example, an adversary might have a base attack action. + * @returns {boolean} */ get usable() { const actor = this.actor; diff --git a/module/data/fields/action/transformField.mjs b/module/data/fields/action/transformField.mjs index 07b32166..0fc35742 100644 --- a/module/data/fields/action/transformField.mjs +++ b/module/data/fields/action/transformField.mjs @@ -1,5 +1,11 @@ +import { getWorldActor } from '../../../helpers/utils.mjs'; + const fields = foundry.data.fields; +/** + * @import DHSummonAction from '../../action/summonAction.mjs' + */ + export default class DHSummonField extends fields.SchemaField { /** * Action Workflow order @@ -20,6 +26,11 @@ export default class DHSummonField extends fields.SchemaField { super(transformFields, options, context); } + /** + * Runs the execute. This is run on behalf of DHSummonAction. + * @todo move this function to be on the summon action. + * @this DHSummonAction + */ static async execute() { if (!this.transform.actorUUID) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.noTransformActor')); @@ -37,26 +48,37 @@ export default class DHSummonField extends fields.SchemaField { return false; } - if (this.actor.prototypeToken.actorLink) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.actorLinkError')); + const activeTokens = this.actor.getActiveTokens(false, true); + const controlledMatchingTokens = canvas.tokens.controlled + .filter(x => x.actor && x.actor.uuid === this.actor.uuid) + .map(x => x.document); + /** @type {typeof game.system.api.documents.DhToken | null} */ + const token = this.actor.token ?? ( + activeTokens.length === 1 ? activeTokens[0] : + (controlledMatchingTokens.length === 1 ? controlledMatchingTokens[0] : null) + ); + + if (!this.actor.token && !token) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.linkedSelectedError')); return false; } - if (!this.actor.token) { + if (!token) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.prototypeError')); return false; } - const actor = await DHSummonField.getWorldActor(baseActor); + const actor = await getWorldActor(baseActor); const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; const tokenSize = actor?.system.metadata.usesSize ? tokenSizes[actor.system.size] : actor.prototypeToken.width; - await this.actor.token.update( - { ...actor.prototypeToken.toJSON(), actorId: actor.id, width: tokenSize, height: tokenSize }, - { diff: false, recursive: false, noHook: true } + // Update token. Avoid using recursive: false, since that prevents animations + await token.update( + { ...actor.prototypeToken.toObject(), actorId: actor.id, width: tokenSize, height: tokenSize }, + { diff: false, noHook: true } ); - if (this.actor.token.combatant) { + if (token.combatant) { this.actor.token.combatant.update({ actorId: actor.id, img: actor.prototypeToken.texture.src }); } @@ -64,17 +86,17 @@ export default class DHSummonField extends fields.SchemaField { if (!this.transform.resourceRefresh.hitPoints) { marks.hitPoints = Math.min( this.actor.system.resources.hitPoints.value, - this.actor.token.actor.system.resources.hitPoints.max - 1 + token.actor.system.resources.hitPoints.max - 1 ); } if (!this.transform.resourceRefresh.stress) { marks.stress = Math.min( this.actor.system.resources.stress.value, - this.actor.token.actor.system.resources.stress.max - 1 + token.actor.system.resources.stress.max - 1 ); } if (marks.hitPoints || marks.stress) { - this.actor.token.actor.update({ + token.actor.update({ 'system.resources': { hitPoints: { value: marks.hitPoints }, stress: { value: marks.stress } @@ -84,20 +106,9 @@ export default class DHSummonField extends fields.SchemaField { const prevPosition = { ...this.actor.sheet.position }; this.actor.sheet.close(); - this.actor.token.actor.sheet.render({ force: true, position: prevPosition }); - } - - /* Check for any available instances of the actor present in the world, or create a world actor based on compendium */ - static async getWorldActor(baseActor) { - if (!baseActor.inCompendium) return baseActor; - - const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`]; - if (dataType && baseActor.img === dataType.DEFAULT_ICON) { - const worldActorCopy = game.actors.find(x => x.name === baseActor.name); - if (worldActorCopy) return worldActorCopy; + token.actor.sheet.render({ force: true, position: prevPosition }); + if (token.object.controlled) { + ui.effectsDisplay.refresh(); } - - const worldActor = await game.system.api.documents.DhpActor.create(baseActor.toObject()); - return worldActor; } } diff --git a/module/documents/_types.d.ts b/module/documents/_types.d.ts index a94d6395..a3af1b33 100644 --- a/module/documents/_types.d.ts +++ b/module/documents/_types.d.ts @@ -3,17 +3,26 @@ 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'; +import DHToken from './token.mjs'; +import Actor from '@client/documents/actor.mjs'; +import Item from '@client/documents/item.mjs'; declare module './actor.mjs' { - export default interface DhpActor { + export default interface DhpActor extends Actor { system: T; items: EmbeddedCollection; effects: EmbeddedCollection; + get token(): DHToken | null; + + /** @inheritdoc */ + getActiveTokens(linked?: boolean, document?: boolean): (DHToken | foundry.canvas.placeables.Token)[]; + getActiveTokens(linked?: boolean, document: true): DHToken[]; + getActiveTokens(linked?: boolean, document: false): foundry.canvas.placeables.Token[]; } } declare module './item.mjs' { - export default interface DHItem { + export default interface DHItem extends Item { parent: DhpActor; actor: DhpActor; system: T; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 8ef64f65..30901374 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -34,12 +34,14 @@ export default class DhpActor extends Actor { super.prepareData(); // Update effects if it is the user's character or is controlled - if (canvas.ready) { + // A timeout avoids an infinite loop when accessing token actors before the delta is finished constructing + window.setTimeout(() => { + if (!canvas.ready) return; const controlled = canvas.tokens.controlled.some(t => t.actor === this); if (game.user.character === this || controlled) { - ui.effectsDisplay.render(); + ui.effectsDisplay.refresh(); } - } + }, 0); } /* -------------------------------------------- */ diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 8e91d4f0..d70480db 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -38,7 +38,8 @@ export default class DHToken extends CONFIG.Token.documentClass { tokens.filter(x => x.actor).map(x => x.actor) ); } - super.createCombatants(tokens, combat ?? {}); + + await super.createCombatants(tokens, combat ?? {}); } /**@inheritdoc */ diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 71f43997..84bcacf2 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -1,6 +1,10 @@ import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs'; import Tagify from '@yaireo/tagify'; +/** + * @import DhpActor from '../documents/actor.mjs'; + */ + export const capitalize = string => { return string.charAt(0).toUpperCase() + string.slice(1); }; @@ -891,6 +895,13 @@ export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) { return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm; } +/** + * Returns the given actor if its a world actor, + * finds a world actor equivalent, + * or imports the actor and returns the imported actor. + * @param {DhpActor} baseActor + * @returns {Promise} a world actor + */ export async function getWorldActor(baseActor) { if (baseActor.inCompendium) { const worldActorCandidates = game.actors.filter(x => From 1dcfc92a0347b20c9b07563beb939e3d6b3e4654 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 3 Jul 2026 11:16:55 +0200 Subject: [PATCH 6/6] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 0ecc2e75..d754b8bf 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.2", + "version": "2.5.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.4.2/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.5.0/system.zip", "authors": [ { "name": "WBHarry"