From 0d7469801e7123588f736b43debdaa6e29eccd4e Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:22:25 +0200 Subject: [PATCH 01/54] Updated the longrest repair armor to the new armor max path along with a migration (#1777) --- module/config/generalConfig.mjs | 2 +- module/systemRegistration/migrations.mjs | 13 +++++++++++++ system.json | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index f3484e43..24f1de92 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -484,7 +484,7 @@ export const defaultRestOptions = { value: { custom: { enabled: true, - formula: '@system.armorScore' + formula: '@system.armorScore.max' } } } diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index 458ee6ef..2851f7d4 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -1,3 +1,4 @@ +import { defaultRestOptions } from '../config/generalConfig.mjs'; import { RefreshType, socketEvent } from './socket.mjs'; export async function runMigrations() { @@ -341,6 +342,18 @@ export async function runMigrations() { lastMigrationVersion = '2.0.0'; } + + if (foundry.utils.isNewerVersion('2.0.4', lastMigrationVersion)) { + const downtimeMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew); + if (restMoves.longRest.moves.repairArmor) { + await downtimeMoves.updateSource({ + 'restMoves.longRest.moves.repairArmor': defaultRestOptions.longRest().repairArmor + }); + game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, downtimeMoves.toObject()); + } + + lastMigrationVersion = '2.0.4'; + } //#endregion await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion); diff --git a/system.json b/system.json index 28d849b3..e237f538 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.0.3", + "version": "2.0.4", "compatibility": { "minimum": "14.359", "verified": "14.359", From 90f433989810d7a9098023a9f279a06c43d32788 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 5 Apr 2026 10:23:02 +0200 Subject: [PATCH 02/54] Restoring current version number --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index e237f538..28d849b3 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.0.4", + "version": "2.0.3", "compatibility": { "minimum": "14.359", "verified": "14.359", From dbcef140a263297c0783dcea4dd1489d33ad40d7 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 5 Apr 2026 11:09:00 +0200 Subject: [PATCH 03/54] Fixed armorEffects erroring on isSuppressed when not on an actor --- module/data/activeEffect/changeTypes/armor.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs index 713ef03d..2f3b9765 100644 --- a/module/data/activeEffect/changeTypes/armor.mjs +++ b/module/data/activeEffect/changeTypes/armor.mjs @@ -111,6 +111,8 @@ export default class ArmorChange extends foundry.abstract.DataModel { }; get isSuppressed() { + if (!this.parent.parent?.actor) return false; + switch (this.value.interaction) { case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id: return !this.parent.parent?.actor.system.armor; From fdfd8c5a8d40cc1eb99b84fbfe8cf29dd6d89ab5 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 5 Apr 2026 11:28:41 +0200 Subject: [PATCH 04/54] Fixed selecting which roll to use in TagTeamRolls becoming impossible when using an Ability option --- module/applications/dialogs/tagTeamDialog.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 5236afb8..054331b5 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -366,8 +366,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio let rollIsSelected = false; for (const member of Object.values(members)) { const rollFinished = Boolean(member.rollData); - const damageFinished = - member.rollData?.options?.hasDamage !== undefined ? member.rollData.options.damage : true; + const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true; rollsAreFinished = rollsAreFinished && rollFinished && damageFinished; rollIsSelected = rollIsSelected || member.selected; From 67d142df3d5ae908293fbae74da8d35cb96f5b4b Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 5 Apr 2026 17:27:02 +0200 Subject: [PATCH 05/54] Fixed migration --- module/systemRegistration/migrations.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index 2851f7d4..c2c53f4e 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -345,7 +345,7 @@ export async function runMigrations() { if (foundry.utils.isNewerVersion('2.0.4', lastMigrationVersion)) { const downtimeMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew); - if (restMoves.longRest.moves.repairArmor) { + if (downtimeMoves.restMoves.longRest.moves.repairArmor) { await downtimeMoves.updateSource({ 'restMoves.longRest.moves.repairArmor': defaultRestOptions.longRest().repairArmor }); From 4c2d31b2f4dd7b76e4c6374bd6af3cf40a1cf56a Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:28:27 +0200 Subject: [PATCH 06/54] Fixed so that expanded damage info without any dice will show the correct value (#1780) --- templates/ui/chat/parts/damage-part.hbs | 41 +++++++++++++------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/templates/ui/chat/parts/damage-part.hbs b/templates/ui/chat/parts/damage-part.hbs index 02519a86..45b09b72 100644 --- a/templates/ui/chat/parts/damage-part.hbs +++ b/templates/ui/chat/parts/damage-part.hbs @@ -33,31 +33,32 @@
{{total}}
{{/if}}
- {{#each dice}} - {{#each results}} - {{#unless discarded}} -
-
- {{#if hasRerolls}}{{/if}} - {{result}} + {{#if dice.length}} + {{#each dice}} + {{#each results}} + {{#unless discarded}} +
+
+ {{#if hasRerolls}}{{/if}} + {{result}} +
-
- {{/unless}} + {{/unless}} + {{/each}} {{/each}} - {{/each}} - {{#if modifierTotal}} -
-
{{modifierTotal}}
-
- {{/if}} - {{#unless dice.length}} + {{#if modifierTotal}} +
+
{{modifierTotal}}
+
+ {{/if}} + {{else}}
{{total}}
- {{/unless}} + {{/if}}
{{/each}} From fad830580ccdde5347aaeb7364449f1ff6a29494 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 6 Apr 2026 00:18:43 +0200 Subject: [PATCH 07/54] Added checkboxes for muting the sounds of dice animations (#1781) --- lang/en.json | 1 + module/config/generalConfig.mjs | 4 ++-- module/data/settings/Appearance.mjs | 3 +++ .../appearance-settings/diceSoNice.less | 24 +++++++++++++++++++ .../appearance-settings/diceSoNice.hbs | 11 +++++++-- .../appearance-settings/diceSoNiceTab.hbs | 10 ++++++-- 6 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lang/en.json b/lang/en.json index d19dfb58..7b1e5744 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2857,6 +2857,7 @@ "system": "Dice Preset", "font": "Font", "critical": "Duality Critical Animation", + "muted": "Muted", "diceAppearance": "Dice Appearance", "animations": "Animations", "defaultAnimations": "Set Animations As Player Defaults", diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 24f1de92..918cb417 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -708,14 +708,14 @@ const getDiceSoNiceSFX = sfxOptions => { if (sfxOptions.critical && criticalAnimationData.class) { return { specialEffect: criticalAnimationData.class, - options: {} + options: { ...criticalAnimationData.options } }; } if (sfxOptions.higher && sfxOptions.data.higher) { return { specialEffect: sfxOptions.data.higher.class, - options: {} + options: { ...sfxOptions.data.higher.options } }; } diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs index 9da3afac..4db27be0 100644 --- a/module/data/settings/Appearance.mjs +++ b/module/data/settings/Appearance.mjs @@ -8,6 +8,9 @@ export default class DhAppearance extends foundry.abstract.DataModel { initial: null, blank: true, choices: CONFIG.DH.GENERAL.diceSoNiceSFXClasses + }), + options: new foundry.data.fields.SchemaField({ + muteSound: new foundry.data.fields.BooleanField() }) }); diff --git a/styles/less/ui/settings/appearance-settings/diceSoNice.less b/styles/less/ui/settings/appearance-settings/diceSoNice.less index 079bebc5..a4846596 100644 --- a/styles/less/ui/settings/appearance-settings/diceSoNice.less +++ b/styles/less/ui/settings/appearance-settings/diceSoNice.less @@ -68,5 +68,29 @@ text-align: center; white-space: nowrap; } + + color-picker { + gap: 4px; + background: inherit; + } + } + + .animation-container { + display: flex; + align-items: center; + justify-content: space-between; + + .animation-inner-container { + display: flex; + align-items: center; + justify-content: right; + gap: 8px; + + .animation-control { + display: flex; + align-items: center; + gap: 2px; + } + } } } diff --git a/templates/settings/appearance-settings/diceSoNice.hbs b/templates/settings/appearance-settings/diceSoNice.hbs index afe7dd5a..6cd4e52e 100644 --- a/templates/settings/appearance-settings/diceSoNice.hbs +++ b/templates/settings/appearance-settings/diceSoNice.hbs @@ -9,9 +9,16 @@ {{/if}}
+
- {{formInput fields.diceSoNice.fields.sfx.fields.critical.fields.class value=setting.diceSoNice.sfx.critical.class blank="" localize=true}} -
+
+ {{formInput fields.diceSoNice.fields.sfx.fields.critical.fields.class value=setting.diceSoNice.sfx.critical.class blank="" localize=true}} +
+ +
+ {{formInput fields.diceSoNice.fields.sfx.fields.critical.fields.options.fields.muteSound value=setting.diceSoNice.sfx.critical.options.muteSound localize=true}} +
+
diff --git a/templates/settings/appearance-settings/diceSoNiceTab.hbs b/templates/settings/appearance-settings/diceSoNiceTab.hbs index a15b63ec..89b587af 100644 --- a/templates/settings/appearance-settings/diceSoNiceTab.hbs +++ b/templates/settings/appearance-settings/diceSoNiceTab.hbs @@ -42,9 +42,15 @@ {{#if animations}}

{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.animations"}}

-
+
- {{formInput fields.sfx.fields.higher.fields.class value=values.sfx.higher.class blank="" localize=true}} +
+ {{formInput fields.sfx.fields.higher.fields.class value=values.sfx.higher.class blank="" localize=true}} +
+ + {{formInput fields.sfx.fields.higher.fields.options.fields.muteSound value=values.sfx.higher.options.muteSound localize=true}} +
+
{{/if}} From 087e69694cc9cfe0ba0ad63df34bc725be7b7cb7 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 6 Apr 2026 11:40:57 +0200 Subject: [PATCH 08/54] Changed the character setup button to be more obvious (#1782) --- lang/en.json | 2 +- templates/sheets/actors/character/header.hbs | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lang/en.json b/lang/en.json index 7b1e5744..55ffc5ad 100755 --- a/lang/en.json +++ b/lang/en.json @@ -353,7 +353,7 @@ "selectSecondaryWeapon": "Select Secondary Weapon", "selectSubclass": "Select Subclass", "setupSkipTitle": "Skipping Character Setup", - "setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking arrows in the top-right. Are you sure you want to continue?", + "setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking button in the top-right. Are you sure you want to continue?", "startingItems": "Starting Items", "story": "Story", "storyExplanation": "Select which background and connection prompts you want to copy into your character's background.", diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs index 06f464fa..4ceba54d 100644 --- a/templates/sheets/actors/character/header.hbs +++ b/templates/sheets/actors/character/header.hbs @@ -4,17 +4,27 @@

{{source.name}}

- {{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}} + {{#if document.system.needsCharacterSetup}} + {{else if document.system.levelData.canLevelUp}} + {{/if}} - {{localize 'DAGGERHEART.GENERAL.level'}} - + {{#unless document.system.needsCharacterSetup}} + {{localize 'DAGGERHEART.GENERAL.level'}} + + {{/unless}}

From b505e15eb2cfbd62427d3ad387e01d48493c10c3 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 9 Apr 2026 15:36:48 -0400 Subject: [PATCH 09/54] Fix editing of bar attributes in v14 (#1786) --- module/data/fields/actorField.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs index 7a57aa46..ae6f060c 100644 --- a/module/data/fields/actorField.mjs +++ b/module/data/fields/actorField.mjs @@ -89,13 +89,13 @@ class ResourcesField extends fields.TypedObjectField { */ _getField(path) { if (path.length === 0) return this; - const first = path.shift(); - if (first === this.element.name) return this.element_getField(path); + const name = path.pop(); + if (name === this.element.name) return this.element_getField(path); const resources = CONFIG.DH.RESOURCE[this.actorType].all; - if (first in resources) { + if (name in resources) { const field = this.element._getField(path); - field.label = resources[first].label; + field.label = resources[name].label; return field; } From ae480157d1649767ac2c967d8b023a74a44703dd Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:07:51 +0200 Subject: [PATCH 10/54] [Feature] 1766 - Group Attack (#1770) * Implemented group attack logic * Updated all minions in the SRD to use the group attack functionality * . * Renamed groupAttack.nr to groupAttack.numAttackers * Moved the flag vs global setting logic to documents/scene * . --- lang/en.json | 4 +- module/applications/dialogs/damageDialog.mjs | 34 ++++++- module/config/generalConfig.mjs | 8 ++ module/data/action/baseAction.mjs | 20 +++++ module/data/chat-message/actorRoll.mjs | 1 + module/data/fields/action/damageField.mjs | 23 ++++- module/dice/damageRoll.mjs | 12 ++- module/documents/scene.mjs | 10 +++ module/enrichers/TemplateEnricher.mjs | 11 +-- ..._Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json | 84 +++++++++++++++--- .../adversary_Conscript_99TqczuQipBmaB8i.json | 84 +++++++++++++++--- ...ersary_Cult_Initiate_zx99sOGTXicP4SSD.json | 86 +++++++++++++++--- ...sary_Elemental_Spark_P7h54ZePFPHpYwvB.json | 86 +++++++++++++++--- ...y_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json | 86 +++++++++++++++--- .../adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json | 44 ++++++++-- ...ersary_Giant_Recruit_5s8wSvpyC5rxY5aD.json | 88 +++++++++++++++---- ...ary_Hallowed_Soldier_VENwg7xEFcYObjmT.json | 86 +++++++++++++++--- ..._Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json | 82 ++++++++++++++--- ...versary_Minor_Treant_G62k4oSkhkoXEs2D.json | 86 +++++++++++++++--- ..._Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json | 84 +++++++++++++++--- ...ersary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json | 86 +++++++++++++++--- .../adversary_Sellsword_bgreCaQ6ap2DVpCr.json | 84 +++++++++++++++--- ...sary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json | 86 +++++++++++++++--- ...rsary_Tangle_Bramble_XcAGOSmtCFLT1unN.json | 84 +++++++++++++++--- ...rsary_Treant_Sapling_o63nS0k3wHu6EgKP.json | 86 +++++++++++++++--- .../less/dialog/damage-selection/sheet.less | 22 ++++- styles/less/global/elements.less | 8 ++ templates/actionTypes/damage.hbs | 7 +- .../dialogs/dice-roll/damageSelection.hbs | 18 ++++ .../action-settings/effect.hbs | 2 +- .../adversary-settings/attack.hbs | 2 +- .../companion-settings/attack.hbs | 2 +- 32 files changed, 1286 insertions(+), 220 deletions(-) diff --git a/lang/en.json b/lang/en.json index 55ffc5ad..36532e4d 100755 --- a/lang/en.json +++ b/lang/en.json @@ -131,6 +131,7 @@ "attackName": "Attack Name", "criticalThreshold": "Critical Threshold", "includeBase": { "label": "Include Item Damage" }, + "groupAttack": { "label": "Group Attack" }, "multiplier": "Multiplier", "saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.", "resultBased": { @@ -3143,7 +3144,8 @@ "tokenActorsMissing": "[{names}] missing Actors", "domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used", "knowTheTide": "Know The Tide gained a token", - "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}" + "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}", + "noTokenTargeted": "No token is targeted" }, "Progress": { "migrationLabel": "Performing system migration. Please wait and do not close Foundry." diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index 97f1c538..46d3d41f 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -22,6 +22,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application }, actions: { toggleSelectedEffect: this.toggleSelectedEffect, + updateGroupAttack: this.updateGroupAttack, toggleCritical: this.toggleCritical, submitRoll: this.submitRoll }, @@ -64,15 +65,40 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length); context.selectedEffects = this.selectedEffects; + context.damageOptions = this.config.damageOptions; + context.rangeOptions = CONFIG.DH.GENERAL.groupAttackRange; + return context; } static updateRollConfiguration(_event, _, formData) { - const { ...rest } = foundry.utils.expandObject(formData.object); - foundry.utils.mergeObject(this.config.roll, rest.roll); - foundry.utils.mergeObject(this.config.modifiers, rest.modifiers); - this.config.selectedMessageMode = rest.selectedMessageMode; + const data = foundry.utils.expandObject(formData.object); + foundry.utils.mergeObject(this.config.roll, data.roll); + foundry.utils.mergeObject(this.config.modifiers, data.modifiers); + this.config.selectedMessageMode = data.selectedMessageMode; + if (data.damageOptions) { + const numAttackers = data.damageOptions.groupAttack?.numAttackers; + if (typeof numAttackers !== 'number' || numAttackers % 1 !== 0) { + data.damageOptions.groupAttack.numAttackers = null; + } + + foundry.utils.mergeObject(this.config.damageOptions, data.damageOptions); + } + + this.render(); + } + + static updateGroupAttack() { + const targets = Array.from(game.user.targets); + if (targets.length === 0) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noTokenTargeted')); + + const actorId = this.roll.data.parent.id; + const range = this.config.damageOptions.groupAttack.range; + const groupAttackTokens = game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(actorId, range); + + this.config.damageOptions.groupAttack.numAttackers = groupAttackTokens.length; this.render(); } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 918cb417..4a3d672d 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -70,6 +70,14 @@ export const range = { } }; +export const groupAttackRange = { + melee: range.melee, + veryClose: range.veryClose, + close: range.close, + far: range.far, + veryFar: range.veryFar +}; + /* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */ export const templateTypes = { CIRCLE: 'circle', diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 1f75d382..0992350b 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -280,6 +280,26 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } }; + if (this.damage) { + config.isDirect = this.damage.direct; + + const groupAttackTokens = this.damage.groupAttack + ? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens( + this.actor.id, + this.damage.groupAttack + ) + : null; + + config.damageOptions = { + groupAttack: this.damage.groupAttack + ? { + numAttackers: Math.max(groupAttackTokens.length, 1), + range: this.damage.groupAttack + } + : null + }; + } + DHBaseAction.applyKeybindings(config); return config; } diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index 89f34949..eaa1cdc2 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -48,6 +48,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { action: new fields.StringField() }), damage: new fields.ObjectField(), + damageOptions: new fields.ObjectField(), costs: new fields.ArrayField(new fields.ObjectField()), successConsumed: new fields.BooleanField({ initial: false }) }; diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 5d40a470..7839bf5a 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -18,7 +18,12 @@ export default class DamageField extends fields.SchemaField { initial: false, label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label' }), - direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }) + direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }), + groupAttack: new fields.StringField({ + choices: CONFIG.DH.GENERAL.groupAttackRange, + blank: true, + label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label' + }) }; super(damageFields, options, context); } @@ -224,6 +229,22 @@ export default class DamageField extends fields.SchemaField { game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players) ); } + + static getGroupAttackTokens(actorId, range) { + if (!canvas.scene) return []; + + const targets = Array.from(game.user.targets); + const rangeSettings = canvas.scene?.rangeSettings; + if (!rangeSettings) return []; + + const maxDistance = rangeSettings[range]; + return canvas.scene.tokens.filter(x => { + if (x.actor?.id !== actorId) return false; + if (targets.every(target => x.object.distanceTo(target) > maxDistance)) return false; + + return true; + }); + } } export class DHActionDiceData extends foundry.abstract.DataModel { diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 1d680a1b..98fd8401 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -144,6 +144,7 @@ export default class DamageRoll extends DHRoll { constructFormula(config) { this.options.isCritical = config.isCritical; for (const [index, part] of this.options.roll.entries()) { + const isHitpointPart = part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id; part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data)); part.roll.terms = Roll.parse(part.roll.formula, config.data); if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { @@ -169,7 +170,16 @@ export default class DamageRoll extends DHRoll { ); } - if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) { + const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm]; + for (const term of part.roll.terms) { + if (damageTypes.some(type => term instanceof type)) { + term.number *= config.damageOptions.groupAttack.numAttackers; + } + } + } + + if (config.isCritical && isHitpointPart) { const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0); if (total > 0) { part.roll.terms.push(...this.formatModifier(total)); diff --git a/module/documents/scene.mjs b/module/documents/scene.mjs index 1c2faa34..59b8091f 100644 --- a/module/documents/scene.mjs +++ b/module/documents/scene.mjs @@ -1,6 +1,16 @@ import DHToken from './token.mjs'; export default class DhScene extends Scene { + get rangeSettings() { + const { custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting; + const sceneMeasurements = this.flags.daggerheart?.rangeMeasurement; + const globalMeasurements = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.variantRules + ).rangeMeasurement; + return sceneMeasurements?.setting === custom.id ? sceneMeasurements : globalMeasurements; + } + /** A map of `TokenDocument` IDs embedded in this scene long with new dimensions from actor size-category changes */ #sizeSyncBatch = new Map(); diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index 1a075518..cd0e7d9c 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -118,13 +118,6 @@ const getTemplateDistance = range => { const rangeNumber = Number(range); if (!Number.isNaN(rangeNumber)) return rangeNumber; - const { custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting; - const sceneMeasurements = canvas.scene?.flags.daggerheart?.rangeMeasurement; - const globalMeasurements = game.settings.get( - CONFIG.DH.id, - CONFIG.DH.SETTINGS.gameSettings.variantRules - ).rangeMeasurement; - - const settings = sceneMeasurements?.setting === custom.id ? sceneMeasurements : globalMeasurements; - return settings[range]; + const settings = canvas.scene?.rangeSettings; + return settings ? settings[range] : 0; }; diff --git a/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json b/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json index 4c63297d..12ec35ae 100644 --- a/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json +++ b/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json @@ -131,12 +131,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -187,7 +184,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -213,7 +210,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -249,33 +247,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "vgguNWz8vG8aoLXR": { - "type": "effect", - "_id": "vgguNWz8vG8aoLXR", + "SrNyZgPvCXMpbCLG": { + "type": "attack", + "_id": "SrNyZgPvCXMpbCLG", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "4" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/magic/unholy/orb-hands-pink.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json b/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json index 5cbc1f82..38e7ceab 100644 --- a/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json +++ b/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -242,33 +240,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "cbAvPSIhwBMBTI3D": { - "type": "effect", - "_id": "cbAvPSIhwBMBTI3D", + "FCeTuf71gCzRiO5N": { + "type": "attack", + "_id": "FCeTuf71gCzRiO5N", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "6" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json b/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json index 4f04a85a..db26605d 100644 --- a/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json +++ b/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -242,33 +240,95 @@ "description": "

Spend a Fear to choose a target and spotlight all Cult @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "EH1preaTWBD4rOvx": { - "type": "effect", - "_id": "EH1preaTWBD4rOvx", + "4M2MvVzEgIQEQHBS": { + "type": "attack", + "_id": "4M2MvVzEgIQEQHBS", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "5" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": null, + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json b/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json index 2c2633ea..5b8fa7b2 100644 --- a/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json +++ b/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -242,33 +240,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "vXHZVb0Y7Hqu3uso": { - "type": "effect", - "_id": "vXHZVb0Y7Hqu3uso", + "S3dYxRclyhYINRi8": { + "type": "attack", + "_id": "S3dYxRclyhYINRi8", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "5" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/magic/unholy/orb-hands-pink.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json b/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json index 8c0d7b95..484e161a 100644 --- a/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json +++ b/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -320,33 +318,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "QHNRSEQmqOcaoXq4": { - "type": "effect", - "_id": "QHNRSEQmqOcaoXq4", + "G0DVft7h55pBnwJA": { + "type": "attack", + "_id": "G0DVft7h55pBnwJA", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "12" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/magic/unholy/orb-hands-pink.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json b/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json index 822ee035..746806d9 100644 --- a/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json +++ b/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json @@ -131,12 +131,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -187,7 +184,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -213,7 +210,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -272,8 +270,38 @@ "recovery": null }, "damage": { - "parts": {}, - "includeBase": false + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "1" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "groupAttack": "close" }, "target": { "type": "any", @@ -300,7 +328,7 @@ "difficulty": null, "damageMod": "none" }, - "name": "Attack", + "name": "Spend Fear", "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } diff --git a/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json b/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json index 376ebace..6611496f 100644 --- a/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json +++ b/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -242,35 +240,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "DjbPQowW1OdBD9Zn": { - "type": "effect", - "_id": "DjbPQowW1OdBD9Zn", + "wez1xgy9vScux9wi": { + "type": "attack", + "_id": "wez1xgy9vScux9wi", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { + "consumeOnSuccess": false, "scalable": false, "key": "fear", "value": 1, - "keyIsID": false, - "step": null, - "consumeOnSuccess": false + "itemId": null, + "step": null } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "5" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json b/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json index 6a131c86..3e1ab854 100644 --- a/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json +++ b/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -297,33 +295,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "eo7J0v1B5zPHul1M": { - "type": "effect", - "_id": "eo7J0v1B5zPHul1M", + "irZGPKPpGLA6sP2y": { + "type": "attack", + "_id": "irZGPKPpGLA6sP2y", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "10" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json b/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json index cfcdea8b..076318c6 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json @@ -131,12 +131,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -187,7 +184,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -213,7 +210,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -251,33 +249,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name] within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "aoQDb2m32NDxE6ZP": { - "type": "effect", - "_id": "aoQDb2m32NDxE6ZP", + "ferZO3BuiP9zU46m": { + "type": "attack", + "_id": "ferZO3BuiP9zU46m", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { + "consumeOnSuccess": false, "scalable": false, "key": "fear", "value": 1, + "itemId": null, "step": null } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "2" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json b/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json index b2217e66..163556e9 100644 --- a/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json +++ b/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -245,33 +243,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "xTMNAHcoErKuR6TZ": { - "type": "effect", - "_id": "xTMNAHcoErKuR6TZ", + "xFlhxnQWmVvDqQ55": { + "type": "attack", + "_id": "xFlhxnQWmVvDqQ55", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "4" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json b/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json index 5a7a605a..0bb3a44c 100644 --- a/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json +++ b/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -242,33 +240,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "tvQetauskZoHDR5y": { - "type": "effect", - "_id": "tvQetauskZoHDR5y", + "6VKv71tPUIGGIfkZ": { + "type": "attack", + "_id": "6VKv71tPUIGGIfkZ", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { + "consumeOnSuccess": false, "scalable": false, "key": "fear", "value": 1, + "itemId": null, "step": null } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "11" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json b/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json index 6755d27f..f1c7f470 100644 --- a/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json +++ b/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -245,33 +243,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "DJBNtd3hWjwsjPwq": { - "type": "effect", - "_id": "DJBNtd3hWjwsjPwq", + "8wRrAWHU0xHW4zuE": { + "type": "attack", + "_id": "8wRrAWHU0xHW4zuE", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "2" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json b/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json index ed6d7775..46bed122 100644 --- a/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json +++ b/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -245,33 +243,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "ghgFZskDiizJDjcn": { - "type": "effect", - "_id": "ghgFZskDiizJDjcn", + "K3pF2DBnR9zJ90W8": { + "type": "attack", + "_id": "K3pF2DBnR9zJ90W8", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { + "consumeOnSuccess": false, "scalable": false, "key": "fear", "value": 1, + "itemId": null, "step": null } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "3" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json b/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json index e4cbab5e..1a82abb8 100644 --- a/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json +++ b/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -245,33 +243,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "Sz55uB8xkoNytLwJ": { - "type": "effect", - "_id": "Sz55uB8xkoNytLwJ", + "6rdwJKwsSCO4R0Ty": { + "type": "attack", + "_id": "6rdwJKwsSCO4R0Ty", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "1" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json b/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json index c36502de..d635b2ca 100644 --- a/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json +++ b/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json @@ -164,12 +164,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -220,7 +217,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -246,7 +243,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -284,33 +282,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "ZC5pKIb9N82vgMWu": { - "type": "effect", - "_id": "ZC5pKIb9N82vgMWu", + "V58Ry90tvIjvfDTZ": { + "type": "attack", + "_id": "V58Ry90tvIjvfDTZ", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { + "consumeOnSuccess": false, "scalable": false, "key": "fear", "value": 1, + "itemId": null, "step": null } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "2" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/creatures/abilities/tail-strike-bone-orange.webp", "range": "" } }, diff --git a/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json b/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json index c6c11d36..c03a1b52 100644 --- a/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json +++ b/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json @@ -125,12 +125,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -181,7 +178,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -207,7 +204,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -242,33 +240,95 @@ "description": "

Spend a Fear to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.

", "resource": null, "actions": { - "euP8VA4wvfsCpwN1": { - "type": "effect", - "_id": "euP8VA4wvfsCpwN1", + "Itubbr63irPJcbXG": { + "type": "attack", + "_id": "Itubbr63irPJcbXG", "systemPath": "actions", + "baseAction": false, "description": "", "chatDisplay": true, + "originItem": { + "type": "itemCollection" + }, "actionType": "action", + "triggers": [], "cost": [ { "scalable": false, "key": "fear", "value": 1, - "step": null + "itemId": null, + "step": null, + "consumeOnSuccess": false } ], "uses": { "value": null, "max": "", - "recovery": null + "recovery": null, + "consumeOnSuccess": false + }, + "damage": { + "parts": { + "hitPoints": { + "applyTo": "hitPoints", + "resultBased": false, + "value": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": true, + "formula": "8" + } + }, + "valueAlt": { + "multiplier": "flat", + "flatMultiplier": 1, + "dice": "d6", + "bonus": null, + "custom": { + "enabled": false, + "formula": "" + } + }, + "base": false, + "type": [ + "physical" + ] + } + }, + "includeBase": false, + "direct": false, + "groupAttack": "close" }, - "effects": [], "target": { - "type": "self", + "type": "any", "amount": null }, + "effects": [], + "roll": { + "type": "attack", + "trait": null, + "difficulty": null, + "bonus": null, + "advState": "neutral", + "diceRolling": { + "multiplier": "prof", + "flatMultiplier": 1, + "dice": "d6", + "compare": null, + "treshold": null + }, + "useDefault": false + }, + "save": { + "trait": null, + "difficulty": null, + "damageMod": "none" + }, "name": "Spend Fear", - "img": "icons/magic/unholy/orb-hands-pink.webp", "range": "" } }, diff --git a/styles/less/dialog/damage-selection/sheet.less b/styles/less/dialog/damage-selection/sheet.less index 0f765748..9f8cfc8a 100644 --- a/styles/less/dialog/damage-selection/sheet.less +++ b/styles/less/dialog/damage-selection/sheet.less @@ -21,7 +21,7 @@ gap: 4px; .critical-chip { flex: 0; - + display: flex; align-items: center; border-radius: 5px; @@ -41,6 +41,26 @@ } } + .group-attack-container { + margin: 0; + + .group-attack-inner-container { + display: flex; + align-items: center; + gap: 16px; + + > * { + flex: 1; + } + + .group-attack-tools { + display: flex; + align-items: center; + gap: 4px; + } + } + } + .damage-section-controls { display: flex; align-items: center; diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 793c8164..c5bca1da 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -419,11 +419,19 @@ width: fit-content; display: flex; align-items: center; + .form-fields { height: 32px; align-content: center; } } + + &.select { + width: fit-content; + display: flex; + align-items: center; + gap: 4px; + } } .scalable-input { diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 9e7c2884..192c5be5 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -8,13 +8,16 @@ {{/if}} {{#unless (eq path 'system.attack.')}}{{/unless}} -
+
{{#if @root.hasBaseDamage}} {{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }} {{/if}} {{#unless (eq @root.source.type 'healing')}} - {{formField directField value=source.direct name=(concat path "damage.direct") localize=true classes="checkbox"}} + {{formField baseFields.direct value=source.direct name=(concat path "damage.direct") localize=true classes="checkbox"}} {{/unless}} + {{#if (and @root.isNPC (not (eq path 'system.attack.')))}} + {{formField baseFields.groupAttack value=source.groupAttack name=(concat path "damage.groupAttack") localize=true classes="select"}} + {{/if}}
{{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}} diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index b2d1a895..915061a0 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -42,6 +42,24 @@
{{/each}} + + {{#if damageOptions.groupAttack}} +
+ {{localize "DAGGERHEART.ACTIONS.Settings.groupAttack.label"}} + +
+ + +
+ + +
+
+
+ {{/if}} + {{#unless (empty @root.modifiers)}}
{{localize "DAGGERHEART.GENERAL.Modifier.plural"}} diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs index 1bdd0304..567cb81c 100644 --- a/templates/sheets-settings/action-settings/effect.hbs +++ b/templates/sheets-settings/action-settings/effect.hbs @@ -5,7 +5,7 @@ > {{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}} {{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}} - {{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage directField=fields.damage.fields.direct }}{{/if}} + {{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage baseFields=fields.damage.fields }}{{/if}} {{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}} {{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}} {{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}} diff --git a/templates/sheets-settings/adversary-settings/attack.hbs b/templates/sheets-settings/adversary-settings/attack.hbs index f829338f..41960032 100644 --- a/templates/sheets-settings/adversary-settings/attack.hbs +++ b/templates/sheets-settings/adversary-settings/attack.hbs @@ -22,5 +22,5 @@ {{formGroup systemFields.criticalThreshold value=document._source.system.criticalThreshold label="DAGGERHEART.ACTIONS.Settings.criticalThreshold" name="system.criticalThreshold" localize=true}}
- {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." directField=systemFields.attack.fields.damage.fields.direct horde=(eq document._source.system.type 'horde')}} + {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." baseFields=systemFields.attack.fields.damage.fields horde=(eq document._source.system.type 'horde')}}
\ No newline at end of file diff --git a/templates/sheets-settings/companion-settings/attack.hbs b/templates/sheets-settings/companion-settings/attack.hbs index f99f7d8c..41451ef0 100644 --- a/templates/sheets-settings/companion-settings/attack.hbs +++ b/templates/sheets-settings/companion-settings/attack.hbs @@ -18,5 +18,5 @@ {{/if}} {{/if}} - {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." directField=systemFields.attack.fields.damage.fields.direct}} + {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." baseFields=systemFields.attack.fields.damage.fields}} \ No newline at end of file From 7ca420ae0e702b2386c22047c975354b9510c842 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 10 Apr 2026 15:33:44 -0400 Subject: [PATCH 11/54] [Feature] Redesign and merge party members and resources tabs (#1784) --- lang/en.json | 15 + module/applications/sheets/actors/party.mjs | 85 ++++- .../sheets/actors/party/party-members.less | 307 ++++++++++++++++-- .../less/sheets/actors/party/resources.less | 196 ----------- styles/less/sheets/index.less | 1 - templates/sheets/actors/party/header.hbs | 1 - .../sheets/actors/party/party-members.hbs | 170 ++++++++-- templates/sheets/actors/party/resources.hbs | 104 ------ 8 files changed, 522 insertions(+), 357 deletions(-) delete mode 100644 styles/less/sheets/actors/party/resources.less delete mode 100644 templates/sheets/actors/party/resources.hbs diff --git a/lang/en.json b/lang/en.json index 9bcf02b5..ec2130b8 100755 --- a/lang/en.json +++ b/lang/en.json @@ -316,6 +316,21 @@ } }, "newAdversary": "New Adversary" + }, + "Party": { + "Subtitle": { + "character": "{community} {ancestry} | {subclass} {class}", + "companion": "Companion of {partner}" + }, + "RemoveConfirmation": { + "title": "Remove member {name}", + "text": "Are you sure you want to remove {name} from the party?" + }, + "Thresholds": { + "minor": "MIN", + "major": "MAJ", + "severe": "SEV" + } } }, "APPLICATIONS": { diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index c5e77112..7c8c2338 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -1,5 +1,5 @@ import DHBaseActorSheet from '../api/base-actor.mjs'; -import { getDocFromElement } from '../../../helpers/utils.mjs'; +import { getDocFromElement, sortBy } from '../../../helpers/utils.mjs'; import { ItemBrowser } from '../../ui/itemBrowser.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs'; @@ -18,13 +18,14 @@ export default class Party extends DHBaseActorSheet { static DEFAULT_OPTIONS = { classes: ['party'], position: { - width: 550, + width: 600, height: 900 }, window: { resizable: true }, actions: { + openDocument: Party.#openDocument, deletePartyMember: Party.#deletePartyMember, deleteItem: Party.#deleteItem, toggleHope: Party.#toggleHope, @@ -45,10 +46,6 @@ export default class Party extends DHBaseActorSheet { header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' }, - resources: { - template: 'systems/daggerheart/templates/sheets/actors/party/resources.hbs', - scrollable: [''] - }, /* NOT YET IMPLEMENTED */ // projects: { // template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs', @@ -66,7 +63,6 @@ export default class Party extends DHBaseActorSheet { primary: { tabs: [ { id: 'partyMembers' }, - { id: 'resources' }, /* NOT YET IMPLEMENTED */ // { id: 'projects' }, { id: 'inventory' }, @@ -96,6 +92,8 @@ export default class Party extends DHBaseActorSheet { case 'header': await this._prepareHeaderContext(context, options); break; + case 'partyMembers': + await this._prepareMembersContext(context, options); case 'notes': await this._prepareNotesContext(context, options); break; @@ -121,6 +119,59 @@ export default class Party extends DHBaseActorSheet { context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator); } + async _prepareMembersContext(context, _options) { + context.partyMembers = []; + const traits = ['agility', 'strength', 'finesse', 'instinct', 'presence', 'knowledge']; + for (const actor of this.document.system.partyMembers) { + const weapons = []; + if (actor.type === 'character') { + if (actor.system.usedUnarmed) { + weapons.push(actor.system.usedUnarmed); + } + const equipped = actor.items.filter(i => i.system.equipped && i.type === 'weapon'); + weapons.push(...sortBy(equipped, i => (i.system.secondary ? 1 : 0))); + } + + context.partyMembers.push({ + uuid: actor.uuid, + img: actor.img, + name: actor.name, + subtitle: (() => { + if (!['character', 'companion'].includes(actor.type)) { + return game.i18n.format(`TYPES.Actor.${actor.type}`); + } + + const { value: classItem, subclass } = actor.system.class ?? {}; + const partner = actor.system.partner; + const ancestry = actor.system.ancestry; + const community = actor.system.community; + if (partner || (classItem && subclass && ancestry && community)) { + return game.i18n.format(`DAGGERHEART.ACTORS.Party.Subtitle.${actor.type}`, { + class: classItem?.name, + subclass: subclass?.name, + partner: partner?.name, + ancestry: ancestry?.name, + community: community?.name + }); + } + })(), + type: actor.type, + resources: actor.system.resources, + armorScore: actor.system.armorScore, + damageThresholds: actor.system.damageThresholds, + evasion: actor.system.evasion, + 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 + })) + : null, + weapons + }); + } + } + /** * Prepare render context for the Biography part. * @param {ApplicationRenderContext} context @@ -149,6 +200,12 @@ export default class Party extends DHBaseActorSheet { } } + static async #openDocument(_, target) { + const uuid = target.dataset.uuid; + const document = await foundry.utils.fromUuid(uuid); + document?.sheet?.render(true); + } + /** * Toggles a hope resource value. * @type {ApplicationClickAction} @@ -190,7 +247,7 @@ export default class Party extends DHBaseActorSheet { * @type {ApplicationClickAction} */ static async #toggleArmorSlot(_, target) { - const actor = game.actors.get(target.dataset.actorId); + const actor = await foundry.utils.fromUuid(target.dataset.actorId); const { value, max } = actor.system.armorScore; const inputValue = Number.parseInt(target.dataset.value); const newValue = value >= inputValue ? inputValue - 1 : inputValue; @@ -425,25 +482,23 @@ export default class Party extends DHBaseActorSheet { } static async #deletePartyMember(event, target) { - const doc = await getDocFromElement(target.closest('.inventory-item')); - + const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid); if (!event.shiftKey) { const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { - title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: game.i18n.localize('TYPES.Actor.adversary'), + title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', { name: doc.name }) }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name }) + content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { name: doc.name }) }); if (!confirmed) return; } const currentMembers = this.document.system.partyMembers.map(x => x.uuid); - const newMemberdList = currentMembers.filter(uuid => uuid !== doc.uuid); - await this.document.update({ 'system.partyMembers': newMemberdList }); + const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid); + await this.document.update({ 'system.partyMembers': newMembersList }); } static async #deleteItem(event, target) { diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index a433ae34..155fcc36 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -1,28 +1,293 @@ @import '../../../utils/colors.less'; @import '../../../utils/fonts.less'; +@import '../../../utils/mixin.less'; -.application.sheet.daggerheart.actor.dh-style.party { - .tab.partyMembers { - max-height: 400px; - overflow: auto; +body.game:is(.performance-low, .noblur) { + .application.sheet.daggerheart.actor.dh-style.party .tab.resources .actors-list .actor-resources { + background: light-dark(@dark-blue, @dark-golden); - .actors-list { - display: flex; - flex-direction: column; - gap: 10px; - align-items: center; - width: 100%; - } - .actors-dragger { - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - width: 100%; - height: 40px; - border: 1px dashed light-dark(@dark-blue-50, @beige-50); - border-radius: 3px; - color: light-dark(@dark-blue-50, @beige-50); + .actor-name { + background: light-dark(@dark-blue, @dark-golden); } } } + +.application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers { + overflow: auto; + + .actors-list { + display: flex; + flex-direction: column; + gap: 8px; + align-items: stretch; + width: 100%; + + .actor-resources { + display: grid; + grid-template: + "img header" min-content + "img body" 1fr + / 7.5rem 1fr; + gap: 6px; + column-gap: 12px; + padding: 6px; + background-color: light-dark(@dark-blue-10, @golden-10); + + .actor-img-frame { + grid-area: img; + width: 7.5rem; + height: 7.5rem; + position: relative; + + .actor-img { + object-fit: cover; + object-position: top center; + border-radius: 6px; + width: 100%; + height: 100%; + } + + .equipped-weapons { + position: absolute; + top: -2px; + left: -3px; + display: flex; + flex-direction: column; + gap: 1px; + img { + border-radius: 50%; + width: 24px; + height: 24px; + border: 1px solid light-dark(@dark-blue, @golden); + object-fit: cover; + } + } + + .evasion { + position: absolute; + top: 1px; + right: 1px; + width: 1.75rem; + height: 1.75rem; + background: url('../assets/svg/trait-shield.svg') no-repeat; + background-size: 100%; + font-size: var(--font-size-14); + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + } + + .threshold-section { + position: absolute; + left: 0; + right: 0; + bottom: -2px; + margin: auto; + + display: flex; + gap: 4px; + background-color: light-dark(transparent, @dark-blue); + color: light-dark(@dark-blue, @golden); + padding: 4px 6px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 3px; + align-items: baseline; + width: fit-content; + + h4 { + font-weight: bold; + text-transform: uppercase; + white-space: nowrap; + + &.threshold-label { + font-size: var(--font-size-10); + color: light-dark(@dark-blue, @golden); + } + + &.threshold-value { + font-size: var(--font-size-11); + color: light-dark(@dark, @beige); + } + } + } + } + + header { + grid-area: header; + display: grid; + grid-template: + "name hope" min-content + "subtitle subtitle" min-content + / 1fr min-content; + + .actor-name { + width: 100%; + z-index: 1; + font-size: var(--font-size-20); + color: light-dark(@beige, @golden); + font-weight: bold; + } + + .delete-icon { + font-size: 0.75em; + } + + .subtitle { + grid-area: subtitle; + font-size: var(--font-size-14); + } + + .hope-section { + display: flex; + background-color: light-dark(transparent, @dark-blue); + color: light-dark(@dark-blue, @golden); + padding: 3px 6px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 3px; + align-items: center; + width: fit-content; + margin-left: auto; + + h4 { + font-size: var(--font-size-12); + font-weight: bold; + text-transform: uppercase; + color: light-dark(@dark-blue, @golden); + margin-right: 3px; + } + + .hope-value { + display: flex; + cursor: pointer; + font-size: var(--font-size-12); + margin-left: 1px; + } + } + } + + .body { + grid-area: body; + display: flex; + align-items: start; + justify-content: space-between; + } + + .resources { + display: flex; + flex-direction: column; + gap: 4px; + + .slot-section { + display: flex; + flex-direction: row; + align-items: stretch; + + .slot-label { + display: flex; + align-items: center; + color: light-dark(@beige, @dark-blue); + background: light-dark(@dark-blue, @golden); + padding: 0 4px; + width: fit-content; + font-weight: bold; + border-radius: 6px 0px 0px 6px; + font-size: var(--font-size-12); + white-space: nowrap; + + .label { + padding-right: 2px; + } + + .value { + font-variant-numeric: tabular-nums; + .current { + display: inline-block; + text-align: end; + width: 2ch; + } + .max { + display: inline-block; + text-align: start; + width: 2ch; + } + } + } + + .slot-bar { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 4px; + + background-color: light-dark(@dark-blue-10, @dark-blue); + color: light-dark(@dark-blue, @golden); + padding: 2px 5px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 0 6px 6px 0; + width: fit-content; + min-height: 22px; + + .armor-slot { + cursor: pointer; + transition: all 0.3s ease; + font-size: var(--font-size-12); + + .fa-shield-halved { + color: light-dark(@dark-blue-40, @golden-40); + } + } + + .slot { + width: 16px; + height: 10px; + border: 1px solid light-dark(@dark-blue, @golden); + background: light-dark(@dark-blue-10, @golden-10); + border-radius: 3px; + transition: all 0.3s ease; + cursor: pointer; + + &.filled { + background: light-dark(@dark-blue, @golden); + } + } + } + } + } + + .traits { + background-color: light-dark(@dark-blue-10, @dark-blue); + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + display: grid; + grid-template-columns: 1fr 1fr; + font-size: var(--font-size-12); + padding: 3px 4px; + gap: 3px 7px; + .trait { + display: flex; + justify-content: space-between; + gap: 3px; + .label { + color: light-dark(@dark-blue, @golden); + } + .value { + font-weight: 600; + } + } + } + } + } + + .actors-dragger { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + width: 100%; + height: 40px; + border: 1px dashed light-dark(@dark-blue-50, @beige-50); + border-radius: 3px; + color: light-dark(@dark-blue-50, @beige-50); + } +} diff --git a/styles/less/sheets/actors/party/resources.less b/styles/less/sheets/actors/party/resources.less deleted file mode 100644 index 4db254bf..00000000 --- a/styles/less/sheets/actors/party/resources.less +++ /dev/null @@ -1,196 +0,0 @@ -@import '../../../utils/colors.less'; -@import '../../../utils/fonts.less'; -@import '../../../utils/mixin.less'; - -body.game:is(.performance-low, .noblur) { - .application.sheet.daggerheart.actor.dh-style.party .tab.resources .actors-list .actor-resources { - background: light-dark(@dark-blue, @dark-golden); - - .actor-name { - background: light-dark(@dark-blue, @dark-golden); - } - } -} - -.application.sheet.daggerheart.actor.dh-style.party { - .tab.resources { - overflow: auto; - - .actors-list { - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 10px; - align-items: center; - width: 100%; - justify-content: center; - - .actor-resources { - display: flex; - flex-direction: column; - position: relative; - background: light-dark(@dark-blue-40, @dark-golden-40); - border-radius: 6px; - border: 1px solid light-dark(@dark-blue, @golden); - width: 230px; - height: -webkit-fill-available; - - .actor-name { - position: absolute; - top: 0px; - background: light-dark(@dark-blue-90, @dark-golden-80); - backdrop-filter: blur(6.5px); - border-radius: 6px 6px 0px 0px; - text-align: center; - width: 100%; - z-index: 1; - font-size: var(--font-size-20); - color: light-dark(@beige, @golden); - font-weight: bold; - padding: 5px 0; - } - - .actor-img { - height: 150px; - object-fit: cover; - object-position: top center; - border-radius: 6px 6px 0px 0px; - mask-image: linear-gradient(180deg, black 88%, transparent 100%); - } - - .resources { - display: flex; - flex-direction: column; - gap: 10px; - align-items: center; - margin: 10px; - - .slot-section { - display: flex; - flex-direction: column; - align-items: center; - - .slot-bar { - display: flex; - flex-wrap: wrap; - gap: 5px; - width: 239px; - - background-color: light-dark(@dark-blue-10, @dark-blue); - color: light-dark(@dark-blue, @golden); - padding: 5px; - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 6px; - width: fit-content; - - .armor-slot { - cursor: pointer; - transition: all 0.3s ease; - font-size: var(--font-size-12); - - .fa-shield-halved { - color: light-dark(@dark-blue-40, @golden-40); - } - } - - .slot { - width: 20px; - height: 10px; - border: 1px solid light-dark(@dark-blue, @golden); - background: light-dark(@dark-blue-10, @golden-10); - border-radius: 3px; - transition: all 0.3s ease; - cursor: pointer; - - &.filled { - background: light-dark(@dark-blue, @golden); - } - } - } - .slot-label { - display: flex; - align-items: center; - color: light-dark(@beige, @dark-blue); - background: light-dark(@dark-blue, @golden); - padding: 0 5px; - width: fit-content; - font-weight: bold; - border-radius: 0px 0px 5px 5px; - font-size: var(--font-size-12); - - .label { - padding-right: 5px; - } - - .value { - padding-left: 6px; - border-left: 1px solid light-dark(@beige, @dark-golden); - } - } - } - - .hope-section { - position: relative; - display: flex; - gap: 10px; - background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); - padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 3px; - align-items: center; - width: fit-content; - - h4 { - font-size: var(--font-size-12); - font-weight: bold; - text-transform: uppercase; - color: light-dark(@dark-blue, @golden); - } - - .hope-value { - display: flex; - cursor: pointer; - font-size: var(--font-size-12); - } - } - - .threshold-section { - display: flex; - align-self: center; - gap: 10px; - background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); - padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 3px; - align-items: center; - width: fit-content; - - h4 { - font-size: var(--font-size-12); - font-weight: bold; - text-transform: uppercase; - color: light-dark(@dark-blue, @golden); - - &.threshold-value { - color: light-dark(@dark, @beige); - } - } - } - } - } - } - .actors-dragger { - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - width: 100%; - height: 40px; - border: 1px dashed light-dark(@dark-blue-50, @beige-50); - border-radius: 3px; - color: light-dark(@dark-blue-50, @beige-50); - } - } -} diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index e5ffbf3e..7d595614 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -31,7 +31,6 @@ @import './actors/party/party-members.less'; @import './actors/party/sheet.less'; @import './actors/party/inventory.less'; -@import './actors/party/resources.less'; @import './items/beastform.less'; @import './items/class.less'; diff --git a/templates/sheets/actors/party/header.hbs b/templates/sheets/actors/party/header.hbs index f39f683f..3fdb137d 100644 --- a/templates/sheets/actors/party/header.hbs +++ b/templates/sheets/actors/party/header.hbs @@ -3,7 +3,6 @@

-

Party

\ No newline at end of file diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs index b3dd53e6..8a113ac8 100644 --- a/templates/sheets/actors/party/party-members.hbs +++ b/templates/sheets/actors/party/party-members.hbs @@ -18,24 +18,156 @@ Help Action --}} + + -
- {{localize tabs.partyMembers.label}} -
    - {{#each document.system.partyMembers as |actor id|}} - {{> 'daggerheart.inventory-item' - item=actor - type='character' - isActor=true - hideContextMenu=true - }} - {{/each}} -
- {{#unless document.system.partyMembers.length}} -
- {{localize "DAGGERHEART.GENERAL.dropActorsHere"}} -
- {{/unless}} -
- \ No newline at end of file +
    + {{#each partyMembers as |member id|}} +
  • +
    + + {{#if member.weapons}} +
    + {{#each member.weapons as |weapon|}} + + {{/each}} +
    + {{/if}} + {{#if member.evasion includeZero=true}} +
    {{member.evasion}}
    + {{/if}} + {{#if member.difficulty includeZero=true}} +
    {{member.difficulty}}
    + {{/if}} + {{#unless (eq member.type 'companion')}} +
    +

    {{localize "DAGGERHEART.ACTORS.Party.Thresholds.minor"}}

    +

    {{member.damageThresholds.major}}

    +

    {{localize "DAGGERHEART.ACTORS.Party.Thresholds.major"}}

    +

    {{member.damageThresholds.severe}}

    +

    {{localize "DAGGERHEART.ACTORS.Party.Thresholds.severe"}}

    +
    + {{/unless}} +
    +
    +

    + {{member.name}} + +

    +
    + {{#unless (or (eq member.type 'companion') (eq member.type 'adversary')) }} +
    +

    {{localize "DAGGERHEART.GENERAL.hope"}}

    + {{#times member.resources.hope.max}} + + {{#if (gte member.resources.hope.value (add this 1))}} + + {{else}} + + {{/if}} + + {{/times}} +
    + {{/unless}} +
    + {{#if member.subtitle}} + {{member.subtitle}} + {{/if}} +
    +
    +
    + {{#unless (eq member.type 'companion') }} +
    +
    + + + + + {{member.resources.hitPoints.value}} + / + {{member.resources.hitPoints.max}} + +
    +
    + {{#times member.resources.hitPoints.max}} + + + {{/times}} +
    +
    + {{/unless}} + +
    +
    + + + + + {{member.resources.stress.value}} + / + {{member.resources.stress.max}} + +
    +
    + {{#times member.resources.stress.max}} + + + {{/times}} +
    +
    + + {{#if member.armorScore.max}} +
    +
    + + + + + {{member.armorScore.value}} + / + {{member.armorScore.max}} + +
    + +
    + {{/if}} +
    + {{#if member.traits}} +
    + {{#each member.traits as |trait|}} + + {{trait.label}} + {{trait.value}} + + {{/each}} +
    + {{/if}} +
    +
  • + {{/each}} +
+ {{#unless document.system.partyMembers.length}} +
+ {{localize "DAGGERHEART.GENERAL.dropActorsHere"}} +
+ {{/unless}} + diff --git a/templates/sheets/actors/party/resources.hbs b/templates/sheets/actors/party/resources.hbs deleted file mode 100644 index bfbfb578..00000000 --- a/templates/sheets/actors/party/resources.hbs +++ /dev/null @@ -1,104 +0,0 @@ -
-
- - -
- -
- {{localize tabs.resources.label}} -
    - {{#each document.system.partyMembers as |actor id|}} -
  • -

    {{actor.name}}

    - -
    - {{#unless (eq actor.type 'companion') }} -
    -
    - {{#times actor.system.resources.hitPoints.max}} - - - {{/times}} -
    -
    - {{localize "DAGGERHEART.GENERAL.HitPoints.short"}} - {{actor.system.resources.hitPoints.value}} / {{actor.system.resources.hitPoints.max}} -
    -
    - {{/unless}} - -
    -
    - {{#times actor.system.resources.stress.max}} - - - {{/times}} -
    -
    - {{localize "DAGGERHEART.GENERAL.stress"}} - {{actor.system.resources.stress.value}} / {{actor.system.resources.stress.max}} -
    -
    - - {{#if actor.system.armorScore.max}} -
    -
    - {{#times actor.system.armorScore.max}} - - {{#if (gte actor.system.armorScore.value (add this 1))}} - - {{else}} - - {{/if}} - - {{/times}} -
    -
    - {{localize "DAGGERHEART.GENERAL.armorSlots"}} - {{actor.system.armorScore.value}} / {{actor.system.armorScore.max}} -
    -
    - {{/if}} - - {{#unless (or (eq actor.type 'companion') (eq actor.type 'adversary')) }} -
    -

    {{localize "DAGGERHEART.GENERAL.hope"}}

    - {{#times actor.system.resources.hope.max}} - - {{#if (gte actor.system.resources.hope.value (add this 1))}} - - {{else}} - - {{/if}} - - {{/times}} -
    - {{/unless}} - - {{#unless (eq actor.type 'companion')}} -
    -

    {{localize "DAGGERHEART.GENERAL.DamageThresholds.minor"}}

    -

    {{actor.system.damageThresholds.major}}

    -

    {{localize "DAGGERHEART.GENERAL.DamageThresholds.major"}}

    -

    {{actor.system.damageThresholds.severe}}

    -

    {{localize "DAGGERHEART.GENERAL.DamageThresholds.severe"}}

    -
    - {{/unless}} -
    -
  • - {{/each}} -
-
-
\ No newline at end of file From e7be2a7d2be8af0389377d151f243dfd83c28b97 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 11 Apr 2026 02:34:18 +0200 Subject: [PATCH 12/54] Raised Foundry version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 28d849b3..300b1042 100644 --- a/system.json +++ b/system.json @@ -4,8 +4,8 @@ "description": "An unofficial implementation of the Daggerheart system", "version": "2.0.3", "compatibility": { - "minimum": "14.359", - "verified": "14.359", + "minimum": "14.360", + "verified": "14.360", "maximum": "14" }, "authors": [ From 97636fa134c1ddd40e58ce73352253fc302694dc Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 11 Apr 2026 02:53:51 +0200 Subject: [PATCH 13/54] Fixed Countdown Actions not actually setting their DefaultOwnership. Fixed GMs not always getting Ownership of a countdown --- module/applications/ui/countdowns.mjs | 2 + module/data/countdowns.mjs | 120 ------------------- module/data/fields/action/countdownField.mjs | 4 + 3 files changed, 6 insertions(+), 120 deletions(-) diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 0f83a05f..79a59a07 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -137,6 +137,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application } static #getPlayerOwnership(user, setting, countdown) { + if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; + const playerOwnership = countdown.ownership[user.id]; return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT ? setting.defaultOwnership diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index b944bf73..7d27197d 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -5,10 +5,6 @@ export default class DhCountdowns extends foundry.abstract.DataModel { const fields = foundry.data.fields; return { - /* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */ - narrative: new fields.EmbeddedDataField(DhCountdownData), - encounter: new fields.EmbeddedDataField(DhCountdownData), - /**/ countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhCountdown)), defaultOwnership: new fields.NumberField({ required: true, @@ -19,122 +15,6 @@ export default class DhCountdowns extends foundry.abstract.DataModel { } } -/* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */ -class DhCountdownData extends foundry.abstract.DataModel { - static defineSchema() { - const fields = foundry.data.fields; - return { - countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhOldCountdown)), - ownership: new fields.SchemaField({ - default: new fields.NumberField({ - required: true, - choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS), - initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE - }), - players: new fields.TypedObjectField( - new fields.SchemaField({ - type: new fields.NumberField({ - required: true, - choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS), - initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT - }) - }) - ) - }), - window: new fields.SchemaField({}) - }; - } - - get playerOwnership() { - return Array.from(game.users).reduce((acc, user) => { - acc[user.id] = { - 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, - isGM: user.isGM - }; - - return acc; - }, {}); - } -} - -/* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */ -class DhOldCountdown extends foundry.abstract.DataModel { - static defineSchema() { - const fields = foundry.data.fields; - return { - name: new fields.StringField({ - required: true, - label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.name.label' - }), - img: new fields.FilePathField({ - categories: ['IMAGE'], - base64: false, - initial: 'icons/magic/time/hourglass-yellow-green.webp' - }), - ownership: new fields.SchemaField({ - default: new fields.NumberField({ - required: true, - choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS), - initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE - }), - players: new fields.TypedObjectField( - new fields.SchemaField({ - type: new fields.NumberField({ - required: true, - choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS), - initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT - }) - }) - ) - }), - progress: new fields.SchemaField({ - current: new fields.NumberField({ - required: true, - integer: true, - initial: 1, - label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label' - }), - max: new fields.NumberField({ - required: true, - integer: true, - initial: 1, - label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label' - }), - type: new fields.SchemaField({ - value: new fields.StringField({ - required: true, - choices: CONFIG.DH.GENERAL.countdownProgressionTypes, - initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id, - label: 'DAGGERHEART.GENERAL.type' - }), - label: new fields.StringField({ - label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.label.label' - }) - }) - }) - }; - } - - get playerOwnership() { - return Array.from(game.users).reduce((acc, user) => { - acc[user.id] = { - 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, - isGM: user.isGM - }; - - return acc; - }, {}); - } -} - export class DhCountdown extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; diff --git a/module/data/fields/action/countdownField.mjs b/module/data/fields/action/countdownField.mjs index f49e71ad..719ca749 100644 --- a/module/data/fields/action/countdownField.mjs +++ b/module/data/fields/action/countdownField.mjs @@ -57,6 +57,10 @@ export default class CountdownField extends fields.ArrayField { data.countdowns[foundry.utils.randomID()] = { ...countdown, + ownership: game.users.reduce((acc, curr) => { + if (!curr.isGM) acc[curr.id] = countdown.defaultOwnership; + return acc; + }, {}), progress: { ...countdown.progress, current: countdownStart, From a897037dc4861aa38c52895d16773ee7cf3e59c3 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 11 Apr 2026 11:14:36 +0200 Subject: [PATCH 14/54] [Feature] Group Roll Rework (#1785) * Initial * . * Improvements * . * Renamed 'Main Charater' to 'Leader' * Localization fixes * . * Fixed roll sound coming when canceling a roll. Fixed the leader PART not being disabled when the player isn't the leader --- daggerheart.mjs | 11 + lang/en.json | 23 +- module/applications/dialogs/_module.mjs | 2 +- .../dialogs/group-roll-dialog.mjs | 204 ------- .../applications/dialogs/groupRollDialog.mjs | 527 ++++++++++++++++++ module/applications/sheets/actors/party.mjs | 6 +- module/applications/ui/chatLog.mjs | 182 ------ module/config/hooksConfig.mjs | 3 +- module/data/_module.mjs | 1 + module/data/actor/party.mjs | 4 +- module/data/chat-message/_modules.mjs | 2 - module/data/chat-message/groupRoll.mjs | 39 -- module/data/groupRollData.mjs | 40 ++ module/systemRegistration/socket.mjs | 6 +- .../group-roll-dialog/initialization.less | 78 +++ .../less/dialog/group-roll-dialog/leader.less | 35 ++ .../less/dialog/group-roll-dialog/sheet.less | 266 +++++++++ styles/less/dialog/index.less | 4 + .../tag-team-dialog/initialization.less | 11 + system.json | 1 - templates/dialogs/groupRollDialog/footer.hbs | 6 + .../dialogs/groupRollDialog/groupRoll.hbs | 20 + .../groupRollDialog/groupRollMember.hbs | 85 +++ .../groupRollDialog/initialization.hbs | 32 ++ templates/dialogs/groupRollDialog/leader.hbs | 73 +++ .../dialogs/tagTeamDialog/initialization.hbs | 1 - .../sheets/actors/party/party-members.hbs | 7 +- 27 files changed, 1214 insertions(+), 455 deletions(-) delete mode 100644 module/applications/dialogs/group-roll-dialog.mjs create mode 100644 module/applications/dialogs/groupRollDialog.mjs delete mode 100644 module/data/chat-message/groupRoll.mjs create mode 100644 module/data/groupRollData.mjs create mode 100644 styles/less/dialog/group-roll-dialog/initialization.less create mode 100644 styles/less/dialog/group-roll-dialog/leader.less create mode 100644 styles/less/dialog/group-roll-dialog/sheet.less create mode 100644 templates/dialogs/groupRollDialog/footer.hbs create mode 100644 templates/dialogs/groupRollDialog/groupRoll.hbs create mode 100644 templates/dialogs/groupRollDialog/groupRollMember.hbs create mode 100644 templates/dialogs/groupRollDialog/initialization.hbs create mode 100644 templates/dialogs/groupRollDialog/leader.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index 240d8704..43aafce4 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -343,6 +343,17 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => { } }); +Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => { + if (data.openForAllPlayers && data.partyId) { + const party = game.actors.get(data.partyId); + if (!party) return; + + const dialog = new game.system.api.applications.dialogs.GroupRollDialog(party); + dialog.tabGroups.application = 'groupRoll'; + await dialog.render({ force: true }); + } +}); + const updateActorsRangeDependentEffects = async token => { const rangeMeasurement = game.settings.get( CONFIG.DH.id, diff --git a/lang/en.json b/lang/en.json index f94a7325..65323790 100755 --- a/lang/en.json +++ b/lang/en.json @@ -733,6 +733,17 @@ "selectRoll": "Select which roll value to be used for the Tag Team" } }, + "GroupRollSelect": { + "title": "Group Roll", + "leader": "Leader", + "leaderRoll": "Leader Roll", + "openDialogForAll": "Open Dialog For All", + "startGroupRoll": "Start Group Roll", + "cancelGroupRoll": "Cancel", + "finishGroupRoll": "Finish Group Roll", + "cancelConfirmTitle": "Cancel Group Roll", + "cancelConfirmText": "Are you sure you want to cancel the Group Roll? This will close it for all other players too." + }, "TokenConfig": { "actorSizeUsed": "Actor size is set, determining the dimensions" } @@ -2982,18 +2993,6 @@ "immunityTo": "Immunity: {immunities}" }, "featureTitle": "Class Feature", - "groupRoll": { - "title": "Group Roll", - "leader": "Leader", - "partyTeam": "Party Team", - "team": "Team", - "selectLeader": "Select a Leader", - "selectMember": "Select a Member", - "rerollTitle": "Reroll Group Roll", - "rerollContent": "Are you sure you want to reroll your {trait} roll?", - "rerollTooltip": "Reroll", - "wholePartySelected": "The whole party is selected" - }, "healingRoll": { "title": "Heal - {damage}", "heal": "Heal", diff --git a/module/applications/dialogs/_module.mjs b/module/applications/dialogs/_module.mjs index a479100a..c866f1cd 100644 --- a/module/applications/dialogs/_module.mjs +++ b/module/applications/dialogs/_module.mjs @@ -13,7 +13,7 @@ export { default as OwnershipSelection } from './ownershipSelection.mjs'; export { default as RerollDamageDialog } from './rerollDamageDialog.mjs'; export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs'; export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs'; -export { default as GroupRollDialog } from './group-roll-dialog.mjs'; export { default as TagTeamDialog } from './tagTeamDialog.mjs'; +export { default as GroupRollDialog } from './groupRollDialog.mjs'; export { default as RiskItAllDialog } from './riskItAllDialog.mjs'; export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs'; diff --git a/module/applications/dialogs/group-roll-dialog.mjs b/module/applications/dialogs/group-roll-dialog.mjs deleted file mode 100644 index 8a3c43d6..00000000 --- a/module/applications/dialogs/group-roll-dialog.mjs +++ /dev/null @@ -1,204 +0,0 @@ -import autocomplete from 'autocompleter'; -import { abilities } from '../../config/actorConfig.mjs'; - -const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; - -export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(actors) { - super(); - this.actors = actors; - this.actorLeader = {}; - this.actorsMembers = []; - } - - get title() { - return 'Group Roll'; - } - - static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll'], - position: { width: 'auto', height: 'auto' }, - window: { - title: 'DAGGERHEART.UI.Chat.groupRoll.title' - }, - actions: { - roll: GroupRollDialog.#roll, - removeLeader: GroupRollDialog.#removeLeader, - removeMember: GroupRollDialog.#removeMember - }, - form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false } - }; - - static PARTS = { - application: { - id: 'group-roll', - template: 'systems/daggerheart/templates/dialogs/group-roll/group-roll.hbs' - } - }; - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - const leaderChoices = this.actors.filter(x => this.actorsMembers.every(member => member.actor?.id !== x.id)); - const memberChoices = this.actors.filter( - x => this.actorLeader?.actor?.id !== x.id && this.actorsMembers.every(member => member.actor?.id !== x.id) - ); - - htmlElement.querySelectorAll('.leader-change-input').forEach(element => { - autocomplete({ - input: element, - fetch: function (text, update) { - if (!text) { - update(leaderChoices); - } else { - text = text.toLowerCase(); - var suggestions = leaderChoices.filter(n => n.name.toLowerCase().includes(text)); - update(suggestions); - } - }, - render: function (actor, search) { - const actorName = game.i18n.localize(actor.name); - const matchIndex = actorName.toLowerCase().indexOf(search); - - const beforeText = actorName.slice(0, matchIndex); - const matchText = actorName.slice(matchIndex, matchIndex + search.length); - const after = actorName.slice(matchIndex + search.length, actorName.length); - const img = document.createElement('img'); - img.src = actor.img; - - const element = document.createElement('li'); - element.appendChild(img); - - const label = document.createElement('span'); - label.innerHTML = - `${beforeText}${matchText ? `${matchText}` : ''}${after}`.replaceAll( - ' ', - ' ' - ); - element.appendChild(label); - - return element; - }, - renderGroup: function (label) { - const itemElement = document.createElement('div'); - itemElement.textContent = game.i18n.localize(label); - return itemElement; - }, - onSelect: actor => { - element.value = actor.uuid; - this.actorLeader = { actor: actor, trait: 'agility', difficulty: 0 }; - this.render(); - }, - click: e => e.fetch(), - customize: function (_input, _inputRect, container) { - container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ; - }, - minLength: 0 - }); - }); - - htmlElement.querySelectorAll('.team-push-input').forEach(element => { - autocomplete({ - input: element, - fetch: function (text, update) { - if (!text) { - update(memberChoices); - } else { - text = text.toLowerCase(); - var suggestions = memberChoices.filter(n => n.name.toLowerCase().includes(text)); - update(suggestions); - } - }, - render: function (actor, search) { - const actorName = game.i18n.localize(actor.name); - const matchIndex = actorName.toLowerCase().indexOf(search); - - const beforeText = actorName.slice(0, matchIndex); - const matchText = actorName.slice(matchIndex, matchIndex + search.length); - const after = actorName.slice(matchIndex + search.length, actorName.length); - const img = document.createElement('img'); - img.src = actor.img; - - const element = document.createElement('li'); - element.appendChild(img); - - const label = document.createElement('span'); - label.innerHTML = - `${beforeText}${matchText ? `${matchText}` : ''}${after}`.replaceAll( - ' ', - ' ' - ); - element.appendChild(label); - - return element; - }, - renderGroup: function (label) { - const itemElement = document.createElement('div'); - itemElement.textContent = game.i18n.localize(label); - return itemElement; - }, - onSelect: actor => { - element.value = actor.uuid; - this.actorsMembers.push({ actor: actor, trait: 'agility', difficulty: 0 }); - this.render({ force: true }); - }, - click: e => e.fetch(), - customize: function (_input, _inputRect, container) { - container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ; - }, - minLength: 0 - }); - }); - } - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.leader = this.actorLeader; - context.members = this.actorsMembers; - context.traitList = abilities; - - context.allSelected = this.actorsMembers.length + (this.actorLeader?.actor ? 1 : 0) === this.actors.length; - context.rollDisabled = context.members.length === 0 || !this.actorLeader?.actor; - - return context; - } - - static updateData(event, _, formData) { - const { actorLeader, actorsMembers } = foundry.utils.expandObject(formData.object); - this.actorLeader = foundry.utils.mergeObject(this.actorLeader, actorLeader); - this.actorsMembers = foundry.utils.mergeObject(this.actorsMembers, actorsMembers); - this.render(true); - } - - static async #removeLeader(_, button) { - this.actorLeader = null; - this.render(); - } - - static async #removeMember(_, button) { - this.actorsMembers = this.actorsMembers.filter(m => m.actor.uuid !== button.dataset.memberUuid); - this.render(); - } - - static async #roll() { - const cls = getDocumentClass('ChatMessage'); - const systemData = { - leader: this.actorLeader, - members: this.actorsMembers - }; - const msg = { - type: 'groupRoll', - user: game.user.id, - speaker: cls.getSpeaker(), - title: game.i18n.localize('DAGGERHEART.UI.Chat.groupRoll.title'), - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/groupRoll.hbs', - { system: systemData } - ) - }; - - cls.create(msg); - this.close(); - } -} diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs new file mode 100644 index 00000000..2a7be791 --- /dev/null +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -0,0 +1,527 @@ +import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; +import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; +import Party from '../sheets/actors/party.mjs'; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(party) { + super(); + + this.party = party; + this.partyMembers = party.system.partyMembers + .filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) + .map(member => ({ + ...member.toObject(), + uuid: member.uuid, + id: member.id, + selected: true, + owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) + })); + + this.leader = null; + this.openForAllPlayers = true; + + this.tabGroups.application = Object.keys(party.system.groupRoll.participants).length + ? 'groupRoll' + : 'initialization'; + + Hooks.on(socketEvent.Refresh, this.groupRollRefresh.bind()); + } + + get title() { + return game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title'); + } + + static DEFAULT_OPTIONS = { + tag: 'form', + id: 'GroupRollDialog', + classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'], + position: { width: 550, height: 'auto' }, + actions: { + toggleSelectMember: this.#toggleSelectMember, + startGroupRoll: this.#startGroupRoll, + makeRoll: this.#makeRoll, + removeRoll: this.#removeRoll, + rerollDice: this.#rerollDice, + makeLeaderRoll: this.#makeLeaderRoll, + removeLeaderRoll: this.#removeLeaderRoll, + rerollLeaderDice: this.#rerollLeaderDice, + markSuccessfull: this.#markSuccessfull, + cancelRoll: this.#onCancelRoll, + finishRoll: this.#finishRoll + }, + form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false } + }; + + static PARTS = { + initialization: { + id: 'initialization', + template: 'systems/daggerheart/templates/dialogs/groupRollDialog/initialization.hbs' + }, + leader: { + id: 'leader', + template: 'systems/daggerheart/templates/dialogs/groupRollDialog/leader.hbs' + }, + groupRoll: { + id: 'groupRoll', + template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRoll.hbs' + }, + footer: { + id: 'footer', + template: 'systems/daggerheart/templates/dialogs/groupRollDialog/footer.hbs' + } + }; + + /** @inheritdoc */ + static TABS = { + application: { + tabs: [{ id: 'initialization' }, { id: 'groupRoll' }] + } + }; + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + htmlElement + .querySelector('.main-character-field') + ?.addEventListener('input', this.updateLeaderField.bind(this)); + } + + _configureRenderParts(options) { + const { initialization, leader, groupRoll, footer } = super._configureRenderParts(options); + const augmentedParts = { initialization }; + for (const memberKey of Object.keys(this.party.system.groupRoll.aidingCharacters)) { + augmentedParts[memberKey] = { + id: memberKey, + template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRollMember.hbs' + }; + } + + augmentedParts.leader = leader; + augmentedParts.groupRoll = groupRoll; + augmentedParts.footer = footer; + + return augmentedParts; + } + + /**@inheritdoc */ + async _onRender(context, options) { + await super._onRender(context, options); + + if (this.element.querySelector('.team-container')) return; + + if (this.tabGroups.application !== this.constructor.PARTS.initialization.id) { + const initializationPart = this.element.querySelector('.initialization-container'); + initializationPart.insertAdjacentHTML('afterend', '
'); + initializationPart.insertAdjacentHTML( + 'afterend', + `
${game.i18n.localize('Aiding Characters')}
` + ); + + const teamContainer = this.element.querySelector('.team-container'); + for (const memberContainer of this.element.querySelectorAll('.team-member-container')) + teamContainer.appendChild(memberContainer); + } + } + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + + context.isGM = game.user.isGM; + context.isEditable = this.getIsEditable(); + context.fields = this.party.system.schema.fields.groupRoll.fields; + context.data = this.party.system.groupRoll; + context.traitOptions = CONFIG.DH.ACTOR.abilities; + context.members = {}; + context.allHaveRolled = Object.keys(context.data.participants).every(key => { + const data = context.data.participants[key]; + return Boolean(data.rollData); + }); + + return context; + } + + async _preparePartContext(partId, context, options) { + const partContext = await super._preparePartContext(partId, context, options); + partContext.partId = partId; + + switch (partId) { + case 'initialization': + partContext.groupRollFields = this.party.system.schema.fields.groupRoll.fields; + partContext.memberSelection = this.partyMembers; + + const selectedMembers = partContext.memberSelection.filter(x => x.selected); + + partContext.selectedLeader = this.leader; + partContext.selectedLeaderOptions = selectedMembers + .filter(actor => actor.owned) + .map(x => ({ value: x.id, label: x.name })); + partContext.selectedLeaderDisabled = !selectedMembers.length; + + partContext.canStartGroupRoll = selectedMembers.length > 1 && this.leader?.memberId; + partContext.openForAllPlayers = this.openForAllPlayers; + break; + case 'leader': + partContext.leader = this.getRollCharacterData(this.party.system.groupRoll.leader); + break; + case 'groupRoll': + const leader = this.party.system.groupRoll.leader; + partContext.hasRolled = + leader?.rollData || + Object.values(this.party.system.groupRoll?.aidingCharacters ?? {}).some( + x => x.successfull !== null + ); + const { modifierTotal, modifiers } = Object.values(this.party.system.groupRoll.aidingCharacters).reduce( + (acc, curr) => { + const modifier = curr.successfull === true ? 1 : curr.successfull === false ? -1 : null; + if (modifier) { + acc.modifierTotal += modifier; + acc.modifiers.push(modifier); + } + + return acc; + }, + { modifierTotal: 0, modifiers: [] } + ); + const leaderTotal = leader?.rollData ? leader.roll.total : null; + partContext.groupRoll = { + totalLabel: leader?.rollData + ? game.i18n.format('DAGGERHEART.GENERAL.withThing', { + thing: leader.roll.totalLabel + }) + : null, + totalDualityClass: leader?.roll?.isCritical ? 'critical' : leader?.roll?.withHope ? 'hope' : 'fear', + total: leaderTotal + modifierTotal, + leaderTotal: leaderTotal, + modifiers + }; + break; + case 'footer': + partContext.canFinishRoll = + Boolean(this.party.system.groupRoll.leader?.rollData) && + Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successfull !== null); + break; + } + + if (Object.keys(this.party.system.groupRoll.aidingCharacters).includes(partId)) { + const characterData = this.party.system.groupRoll.aidingCharacters[partId]; + partContext.members[partId] = this.getRollCharacterData(characterData, partId); + } + + return partContext; + } + + getRollCharacterData(data, partId) { + if (!data) return {}; + + const actor = game.actors.get(data.id); + + return { + ...data, + roll: data.roll, + isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER), + key: partId, + readyToRoll: Boolean(data.rollChoice), + hasRolled: Boolean(data.rollData) + }; + } + + static async updateData(event, _, formData) { + const partyData = foundry.utils.expandObject(formData.object); + + this.updatePartyData(partyData, this.getUpdatingParts(event.target)); + } + + async updatePartyData(update, updatingParts, options = { render: true }) { + if (!game.users.activeGM) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired')); + + const gmUpdate = async update => { + await this.party.update(update); + this.render({ parts: updatingParts }); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts } + }); + }; + + await emitAsGM( + GMUpdateEvent.UpdateDocument, + gmUpdate, + update, + this.party.uuid, + options.render ? { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts } : undefined + ); + } + + getUpdatingParts(target) { + const { initialization, leader, groupRoll, footer } = this.constructor.PARTS; + const isInitialization = this.tabGroups.application === initialization.id; + const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey; + const updatingLeader = target.closest('.main-character-outer-container'); + + return [ + ...(isInitialization ? [initialization.id] : []), + ...(updatingMember ? [updatingMember] : []), + ...(updatingLeader ? [leader.id] : []), + ...(!isInitialization ? [groupRoll.id, footer.id] : []) + ]; + } + + getIsEditable() { + return this.party.system.partyMembers.some(actor => { + const selected = Boolean(this.party.system.groupRoll.participants[actor.id]); + return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); + }); + } + + groupRollRefresh = ({ refreshType, action, parts }) => { + if (refreshType !== RefreshType.GroupRoll) return; + + switch (action) { + case 'startGroupRoll': + this.tabGroups.application = 'groupRoll'; + break; + case 'refresh': + this.render({ parts }); + break; + case 'close': + this.close(); + break; + } + }; + + async close(options = {}) { + /* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */ + if (options.closeKey) return; + + Hooks.off(socketEvent.Refresh, this.groupRollRefresh); + return super.close(options); + } + + //#region Initialization + static #toggleSelectMember(_, button) { + const member = this.partyMembers.find(x => x.id === button.dataset.id); + member.selected = !member.selected; + this.render(); + } + + updateLeaderField(event) { + if (!this.leader) this.leader = {}; + this.leader.memberId = event.target.value; + this.render(); + } + + static async #startGroupRoll() { + const leader = this.partyMembers.find(x => x.id === this.leader.memberId); + const aidingCharacters = this.partyMembers.reduce((acc, curr) => { + if (curr.selected && curr.id !== this.leader.memberId) + acc[curr.id] = { id: curr.id, name: curr.name, img: curr.img }; + + return acc; + }, {}); + + await this.party.update({ + 'system.groupRoll': _replace( + new game.system.api.data.GroupRollData({ + ...this.party.system.groupRoll.toObject(), + leader: { id: leader.id, name: leader.name, img: leader.img }, + aidingCharacters + }) + ) + }); + + const hookData = { openForAllPlayers: this.openForAllPlayers, partyId: this.party.id }; + Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, hookData); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.GroupRollStart, + data: hookData + }); + + this.render(); + } + //#endregion + + async makeRoll(button, characterData, path) { + const actor = game.actors.find(x => x.id === characterData.id); + if (!actor) return; + + const result = await actor.rollTrait(characterData.rollChoice, { + skips: { + createMessage: true, + resources: true, + triggers: true + } + }); + + if (!result) return; + if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); + + const rollData = result.messageRoll.toJSON(); + delete rollData.options.messageRoll; + this.updatePartyData( + { + [path]: rollData + }, + this.getUpdatingParts(button) + ); + } + + static async #makeRoll(_event, button) { + const { member } = button.dataset; + const character = this.party.system.groupRoll.aidingCharacters[member]; + this.makeRoll(button, character, `system.groupRoll.aidingCharacters.${member}.rollData`); + } + + static async #makeLeaderRoll(_event, button) { + const character = this.party.system.groupRoll.leader; + this.makeRoll(button, character, 'system.groupRoll.leader.rollData'); + } + + async removeRoll(button, path) { + this.updatePartyData( + { + [path]: { + rollData: null, + rollChoice: null, + selected: false, + successfull: null + } + }, + this.getUpdatingParts(button) + ); + } + + static async #removeRoll(_event, button) { + this.removeRoll(button, `system.groupRoll.aidingCharacters.${button.dataset.member}`); + } + + static async #removeLeaderRoll(_event, button) { + this.removeRoll(button, 'system.groupRoll.leader'); + } + + async rerollDice(button, data, path) { + const { diceType } = button.dataset; + + const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 1 : 2; + const newRoll = game.system.api.dice.DualityRoll.fromData(data.rollData); + const dice = newRoll.dice[dieIndex]; + await dice.reroll(`/r1=${dice.total}`, { + liveRoll: { + roll: newRoll, + isReaction: true + } + }); + const rollData = newRoll.toJSON(); + this.updatePartyData( + { + [path]: rollData + }, + this.getUpdatingParts(button) + ); + } + + static async #rerollDice(_, button) { + const { member } = button.dataset; + this.rerollDice( + button, + this.party.system.groupRoll.aidingCharacters[member], + `system.groupRoll.aidingCharacters.${member}.rollData` + ); + } + + static async #rerollLeaderDice(_, button) { + this.rerollDice(button, this.party.system.groupRoll.leader, `system.groupRoll.leader.rollData`); + } + + static #markSuccessfull(_event, button) { + const previousValue = this.party.system.groupRoll.aidingCharacters[button.dataset.member].successfull; + const newValue = Boolean(button.dataset.successfull === 'true'); + this.updatePartyData( + { + [`system.groupRoll.aidingCharacters.${button.dataset.member}.successfull`]: + previousValue === newValue ? null : newValue + }, + this.getUpdatingParts(button) + ); + } + + static async #onCancelRoll(_event, _button, options = { confirm: true }) { + this.cancelRoll(options); + } + + async cancelRoll(options = { confirm: true }) { + if (options.confirm) { + const confirmed = await foundry.applications.api.DialogV2.confirm({ + window: { + title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelConfirmTitle') + }, + content: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelConfirmText') + }); + + if (!confirmed) return; + } + + await this.updatePartyData( + { + 'system.groupRoll': { + leader: null, + aidingCharacters: _replace({}) + } + }, + [], + { render: false } + ); + + this.close(); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.GroupRoll, action: 'close' } + }); + } + + static async #finishRoll() { + const totalRoll = this.party.system.groupRoll.leader.roll; + for (const character of Object.values(this.party.system.groupRoll.aidingCharacters)) { + totalRoll.terms.push(new foundry.dice.terms.OperatorTerm({ operator: character.successfull ? '+' : '-' })); + totalRoll.terms.push(new foundry.dice.terms.NumericTerm({ number: 1 })); + } + + await totalRoll._evaluate(); + + const systemData = totalRoll.options; + const actor = game.actors.get(this.party.system.groupRoll.leader.id); + + const cls = getDocumentClass('ChatMessage'), + msgData = { + type: 'dualityRoll', + user: game.user.id, + title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title'), + speaker: cls.getSpeaker({ actor }), + system: systemData, + rolls: [JSON.stringify(totalRoll)], + sound: null, + flags: { core: { RollTable: true } } + }; + + await cls.create(msgData); + + const resourceMap = new ResourceUpdateMap(actor); + if (totalRoll.isCritical) { + resourceMap.addResources([ + { key: 'stress', value: -1, total: 1 }, + { key: 'hope', value: 1, total: 1 } + ]); + } else if (totalRoll.withHope) { + resourceMap.addResources([{ key: 'hope', value: 1, total: 1 }]); + } else { + resourceMap.addResources([{ key: 'fear', value: 1, total: 1 }]); + } + + resourceMap.updateResources(); + + /* Fin */ + this.cancelRoll({ confirm: false }); + } +} diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 7c8c2338..d4545f63 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -4,7 +4,6 @@ import { ItemBrowser } from '../../ui/itemBrowser.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs'; import { socketEvent } from '../../../systemRegistration/socket.mjs'; -import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs'; import DhpActor from '../../../documents/actor.mjs'; export default class Party extends DHBaseActorSheet { @@ -117,6 +116,7 @@ export default class Party extends DHBaseActorSheet { relativeTo: this.document }); context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator); + context.groupRollActive = Boolean(this.document.system.groupRoll.leader); } async _prepareMembersContext(context, _options) { @@ -318,9 +318,7 @@ export default class Party extends DHBaseActorSheet { } static async #groupRoll(_params) { - new GroupRollDialog( - this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) - ).render({ force: true }); + new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true }); } /* -------------------------------------------- */ diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 8cbacb09..59939963 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -1,8 +1,6 @@ -import { abilities } from '../../config/actorConfig.mjs'; import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs'; import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs'; import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs'; -import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs'; export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog { constructor(options) { @@ -150,18 +148,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo html.querySelectorAll('.reroll-button').forEach(element => element.addEventListener('click', event => this.rerollEvent(event, message)) ); - html.querySelectorAll('.group-roll-button').forEach(element => - element.addEventListener('click', event => this.groupRollButton(event, message)) - ); - html.querySelectorAll('.group-roll-reroll').forEach(element => - element.addEventListener('click', event => this.groupRollReroll(event, message)) - ); - html.querySelectorAll('.group-roll-success').forEach(element => - element.addEventListener('click', event => this.groupRollSuccessEvent(event, message)) - ); - html.querySelectorAll('.group-roll-header-expand-section').forEach(element => - element.addEventListener('click', this.groupRollExpandSection) - ); html.querySelectorAll('.risk-it-all-button').forEach(element => element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data)) ); @@ -305,174 +291,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } } - async groupRollButton(event, message) { - const path = event.currentTarget.dataset.path; - const isLeader = path === 'leader'; - const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path); - const actor = game.actors.get(actorData._id); - - if (!actor) { - return ui.notifications.error( - game.i18n.format('DAGGERHEART.UI.Notifications.documentIsMissing', { - documentType: game.i18n.localize('TYPES.Actor.character') - }) - ); - } - - if (!actor.testUserPermission(game.user, 'OWNER')) { - return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership')); - } - - const traitLabel = game.i18n.localize(abilities[trait].label); - const config = { - event: event, - title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`, - headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: traitLabel - }), - roll: { - trait: trait, - advantage: 0, - modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }] - }, - hasRoll: true, - skips: { - createMessage: true, - resources: !isLeader, - updateCountdowns: !isLeader - } - }; - const result = await actor.diceRoll({ - ...config, - headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`, - title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: traitLabel - }) - }); - - if (!result) return; - - const newMessageData = foundry.utils.deepClone(message.system); - foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll); - const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) }; - - const updatedContent = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/groupRoll.hbs', - { ...renderData, user: game.user } - ); - const mess = game.messages.get(message._id); - - await emitAsGM( - GMUpdateEvent.UpdateDocument, - mess.update.bind(mess), - { - ...renderData, - content: updatedContent - }, - mess.uuid - ); - } - - async groupRollReroll(event, message) { - const path = event.currentTarget.dataset.path; - const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path); - const actor = game.actors.get(actorData._id); - - if (!actor.testUserPermission(game.user, 'OWNER')) { - return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership')); - } - - const traitLabel = game.i18n.localize(abilities[trait].label); - - const config = { - event: event, - title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`, - headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: traitLabel - }), - roll: { - trait: trait, - advantage: 0, - modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }] - }, - hasRoll: true, - skips: { - createMessage: true, - updateCountdowns: true - } - }; - const result = await actor.diceRoll({ - ...config, - headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`, - title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: traitLabel - }) - }); - - const newMessageData = foundry.utils.deepClone(message.system); - foundry.utils.setProperty(newMessageData, `${path}.result`, { ...result.roll, rerolled: true }); - const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) }; - - const updatedContent = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/groupRoll.hbs', - { ...renderData, user: game.user } - ); - const mess = game.messages.get(message._id); - await emitAsGM( - GMUpdateEvent.UpdateDocument, - mess.update.bind(mess), - { - ...renderData, - content: updatedContent - }, - mess.uuid - ); - } - - async groupRollSuccessEvent(event, message) { - if (!game.user.isGM) { - return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmOnly')); - } - - const { path, success } = event.currentTarget.dataset; - const { actor: actorData } = foundry.utils.getProperty(message.system, path); - const actor = game.actors.get(actorData._id); - - if (!actor.testUserPermission(game.user, 'OWNER')) { - return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership')); - } - - const newMessageData = foundry.utils.deepClone(message.system); - foundry.utils.setProperty(newMessageData, `${path}.manualSuccess`, Boolean(success)); - const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) }; - - const updatedContent = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/groupRoll.hbs', - { ...renderData, user: game.user } - ); - const mess = game.messages.get(message._id); - await emitAsGM( - GMUpdateEvent.UpdateDocument, - mess.update.bind(mess), - { - ...renderData, - content: updatedContent - }, - mess.uuid - ); - } - - async groupRollExpandSection(event) { - event.target - .closest('.group-roll-header-expand-section') - .querySelectorAll('i') - .forEach(element => { - element.classList.toggle('fa-angle-up'); - element.classList.toggle('fa-angle-down'); - }); - event.target.closest('.group-roll-section').querySelector('.group-roll-content').classList.toggle('closed'); - } - async riskItAllClearStressAndHitPoints(event, data) { const resourceValue = event.target.dataset.resourceValue; const actor = game.actors.get(event.target.dataset.actorId); diff --git a/module/config/hooksConfig.mjs b/module/config/hooksConfig.mjs index 8d04be6d..c0930d90 100644 --- a/module/config/hooksConfig.mjs +++ b/module/config/hooksConfig.mjs @@ -1,5 +1,6 @@ export const hooksConfig = { effectDisplayToggle: 'DHEffectDisplayToggle', lockedTooltipDismissed: 'DHLockedTooltipDismissed', - tagTeamStart: 'DHTagTeamRollStart' + tagTeamStart: 'DHTagTeamRollStart', + groupRollStart: 'DHGroupRollStart' }; diff --git a/module/data/_module.mjs b/module/data/_module.mjs index 0e7e295e..cd691ee1 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -4,6 +4,7 @@ export { default as DhRollTable } from './rollTable.mjs'; export { default as RegisteredTriggers } from './registeredTriggers.mjs'; export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs'; export { default as TagTeamData } from './tagTeamData.mjs'; +export { default as GroupRollData } from './groupRollData.mjs'; export { default as SpotlightTracker } from './spotlightTracker.mjs'; export * as countdowns from './countdowns.mjs'; diff --git a/module/data/actor/party.mjs b/module/data/actor/party.mjs index 2c797803..ec1beb99 100644 --- a/module/data/actor/party.mjs +++ b/module/data/actor/party.mjs @@ -1,6 +1,7 @@ import BaseDataActor from './base.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import TagTeamData from '../tagTeamData.mjs'; +import GroupRollData from '../groupRollData.mjs'; export default class DhParty extends BaseDataActor { /**@inheritdoc */ @@ -16,7 +17,8 @@ export default class DhParty extends BaseDataActor { bags: new fields.NumberField({ initial: 0, integer: true }), chests: new fields.NumberField({ initial: 0, integer: true }) }), - tagTeam: new fields.EmbeddedDataField(TagTeamData) + tagTeam: new fields.EmbeddedDataField(TagTeamData), + groupRoll: new fields.EmbeddedDataField(GroupRollData) }; } diff --git a/module/data/chat-message/_modules.mjs b/module/data/chat-message/_modules.mjs index c671de31..450d1ba2 100644 --- a/module/data/chat-message/_modules.mjs +++ b/module/data/chat-message/_modules.mjs @@ -1,6 +1,5 @@ import DHAbilityUse from './abilityUse.mjs'; import DHActorRoll from './actorRoll.mjs'; -import DHGroupRoll from './groupRoll.mjs'; import DHSystemMessage from './systemMessage.mjs'; export const config = { @@ -9,6 +8,5 @@ export const config = { damageRoll: DHActorRoll, dualityRoll: DHActorRoll, fateRoll: DHActorRoll, - groupRoll: DHGroupRoll, systemMessage: DHSystemMessage }; diff --git a/module/data/chat-message/groupRoll.mjs b/module/data/chat-message/groupRoll.mjs deleted file mode 100644 index a5308323..00000000 --- a/module/data/chat-message/groupRoll.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { abilities } from '../../config/actorConfig.mjs'; - -export default class DHGroupRoll extends foundry.abstract.TypeDataModel { - static defineSchema() { - const fields = foundry.data.fields; - - return { - leader: new fields.EmbeddedDataField(GroupRollMemberField), - members: new fields.ArrayField(new fields.EmbeddedDataField(GroupRollMemberField)) - }; - } - - get totalModifier() { - return this.members.reduce((acc, m) => { - if (m.manualSuccess === null) return acc; - - return acc + (m.manualSuccess ? 1 : -1); - }, 0); - } -} - -class GroupRollMemberField extends foundry.abstract.DataModel { - static defineSchema() { - const fields = foundry.data.fields; - - return { - actor: new fields.ObjectField(), - trait: new fields.StringField({ choices: abilities }), - difficulty: new fields.StringField(), - result: new fields.ObjectField({ nullable: true, initial: null }), - manualSuccess: new fields.BooleanField({ nullable: true, initial: null }) - }; - } - - /* Can be expanded if we handle automation of success/failure */ - get success() { - return manualSuccess; - } -} diff --git a/module/data/groupRollData.mjs b/module/data/groupRollData.mjs new file mode 100644 index 00000000..78a06b13 --- /dev/null +++ b/module/data/groupRollData.mjs @@ -0,0 +1,40 @@ +export default class GroupRollData extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + leader: new fields.EmbeddedDataField(CharacterData, { nullable: true, initial: null }), + aidingCharacters: new fields.TypedObjectField(new fields.EmbeddedDataField(CharacterData)) + }; + } + + get participants() { + return { + ...(this.leader ? { [this.leader.id]: this.leader } : {}), + ...this.aidingCharacters + }; + } +} + +export class CharacterData extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + id: new fields.StringField({ required: true }), + name: new fields.StringField({ required: true }), + img: new fields.StringField({ required: true }), + rollChoice: new fields.StringField({ + choices: CONFIG.DH.ACTOR.abilities, + initial: CONFIG.DH.ACTOR.abilities.agility.id + }), + rollData: new fields.JSONField({ nullable: true, initial: null }), + selected: new fields.BooleanField({ initial: false }), + successfull: new fields.BooleanField({ nullable: true, initial: null }) + }; + } + + get roll() { + return this.rollData ? CONFIG.Dice.daggerheart.DualityRoll.fromData(this.rollData) : null; + } +} diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index fb152959..8fed346d 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -18,6 +18,8 @@ export function handleSocketEvent({ action = null, data = {} } = {}) { case socketEvent.TagTeamStart: Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data); break; + case socketEvent.GroupRollStart: + Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, data); } } @@ -26,7 +28,8 @@ export const socketEvent = { Refresh: 'DhRefresh', DhpFearUpdate: 'DhFearUpdate', DowntimeTrigger: 'DowntimeTrigger', - TagTeamStart: 'DhTagTeamStart' + TagTeamStart: 'DhTagTeamStart', + GroupRollStart: 'DhGroupRollStart' }; export const GMUpdateEvent = { @@ -41,6 +44,7 @@ export const GMUpdateEvent = { export const RefreshType = { Countdown: 'DhCoundownRefresh', TagTeamRoll: 'DhTagTeamRollRefresh', + GroupRoll: 'DhGroupRollRefresh', EffectsDisplay: 'DhEffectsDisplayRefresh', Scene: 'DhSceneRefresh', CompendiumBrowser: 'DhCompendiumBrowserRefresh' diff --git a/styles/less/dialog/group-roll-dialog/initialization.less b/styles/less/dialog/group-roll-dialog/initialization.less new file mode 100644 index 00000000..96990339 --- /dev/null +++ b/styles/less/dialog/group-roll-dialog/initialization.less @@ -0,0 +1,78 @@ +.theme-light .daggerheart.dialog.dh-style.views.group-roll-dialog { + .initialization-container .members-container .member-container { + .member-name { + background-image: url('../assets/parchments/dh-parchment-light.png'); + } + } +} + +.daggerheart.dialog.dh-style.views.group-roll-dialog { + .initialization-container { + h2 { + text-align: center; + } + + .members-container { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 8px; + + .member-container { + position: relative; + display: flex; + justify-content: center; + + &.inactive { + opacity: 0.4; + } + + .member-name { + position: absolute; + padding: 0 2px; + border: 1px solid; + border-radius: 6px; + margin-top: 4px; + color: light-dark(@dark, @beige); + background-image: url('../assets/parchments/dh-parchment-dark.png'); + } + + img { + border-radius: 6px; + border: 1px solid light-dark(@dark-blue, @golden); + } + } + } + + .main-roll { + margin-top: 8px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + + &.inactive { + opacity: 0.4; + } + } + + footer { + margin-top: 8px; + display: flex; + gap: 8px; + + button { + flex: 1; + } + + .finish-tools { + flex: none; + display: flex; + align-items: center; + gap: 4px; + + &.inactive { + opacity: 0.4; + } + } + } + } +} diff --git a/styles/less/dialog/group-roll-dialog/leader.less b/styles/less/dialog/group-roll-dialog/leader.less new file mode 100644 index 00000000..b3fa3a3b --- /dev/null +++ b/styles/less/dialog/group-roll-dialog/leader.less @@ -0,0 +1,35 @@ +.daggerheart.dialog.dh-style.views.group-roll-dialog { + .main-character-outer-container { + &.inactive { + opacity: 0.3; + pointer-events: none; + } + + .main-character-container { + .character-info { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: 64px; + + img { + height: 64px; + border-radius: 6px; + border: 1px solid light-dark(@dark-blue, @golden); + } + + .character-data { + padding-left: 0.75rem; + flex: 1; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + text-align: left; + font-size: var(--font-size-18); + } + } + } + } +} 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..823f6cbf --- /dev/null +++ b/styles/less/dialog/group-roll-dialog/sheet.less @@ -0,0 +1,266 @@ +.daggerheart.dialog.dh-style.views.group-roll-dialog { + .team-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-bottom: 16px; + + .team-member-container { + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 8px; + flex: 1; + + &.inactive { + opacity: 0.3; + pointer-events: none; + } + + .data-container { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + } + + .member-info { + display: flex; + align-items: center; + width: 100%; + height: 64px; + + img { + height: 64px; + border-radius: 6px; + border: 1px solid light-dark(@dark-blue, @golden); + } + + .member-data { + padding-left: 0.75rem; + flex: 1; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + text-align: left; + font-size: var(--font-size-18); + } + } + } + } + + .roll-container { + display: flex; + flex-direction: column; + } + + .roll-title { + font-size: var(--font-size-20); + font-weight: bold; + color: light-dark(@dark-blue, @golden); + text-align: center; + display: flex; + align-items: center; + gap: 8px; + + &.hope, + &.fear, + &.critical { + color: var(--text-color); + } + + &.hope { + --text-color: @golden; + } + + &.fear { + --text-color: @chat-blue; + } + + &.critical { + --text-color: @chat-purple; + } + + &::before, + &::after { + color: var(--text-color); + content: ''; + flex: 1; + height: 2px; + } + + &::before { + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, light-dark(@dark-blue, @golden) 100%); + } + + &::after { + background: linear-gradient(90deg, light-dark(@dark-blue, @golden) 0%, rgba(0, 0, 0, 0) 100%); + } + } + + .roll-tools { + display: flex; + gap: 4px; + align-items: center; + + img { + height: 16px; + } + + a { + display: flex; + font-size: 16px; + + &:hover { + text-shadow: none; + filter: drop-shadow(0 0 8px var(--golden)); + } + } + } + + .roll-data { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + + &.hope { + --text-color: @golden; + --bg-color: @golden-40; + } + + &.fear { + --text-color: @chat-blue; + --bg-color: @chat-blue-40; + } + + &.critical { + --text-color: @chat-purple; + --bg-color: @chat-purple-40; + } + + .duality-label { + color: var(--text-color); + font-size: var(--font-size-20); + font-weight: bold; + text-align: center; + + .unused-damage { + text-decoration: line-through; + } + } + + .roll-dice-container { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 8px; + + .roll-dice { + position: relative; + display: flex; + align-items: center; + justify-content: center; + + .dice-label { + position: absolute; + color: white; + font-size: 1rem; + paint-order: stroke fill; + -webkit-text-stroke: 2px black; + } + + img { + height: 32px; + } + } + + .roll-operator { + font-size: var(--font-size-24); + } + + .roll-value { + font-size: 18px; + } + } + + .roll-total { + background: var(--bg-color); + color: var(--text-color); + border-radius: 4px; + padding: 3px; + } + } + + .roll-success-container { + display: flex; + align-items: center; + justify-content: space-around; + + .roll-success-tools { + display: flex; + align-items: center; + gap: 4px; + color: light-dark(@dark-blue, @golden); + + i { + font-size: 24px; + } + } + + .roll-success-modifier { + display: flex; + align-items: center; + justify-content: right; + gap: 2px; + font-size: var(--font-size-20); + padding: 0px 4px; + + &.success { + background: @green-10; + color: @green; + } + + &.failure { + background: @red-10; + color: @red; + } + } + } + + .section-title { + font-size: var(--font-size-18); + font-weight: bold; + } + + .group-roll-results { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + font-size: var(--font-size-20); + + .group-roll-container { + display: flex; + align-items: center; + gap: 2px; + } + } + + .finish-container { + margin-top: 16px; + gap: 16px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + + .finish-button { + grid-column: span 2; + } + } + + .hint { + text-align: center; + } +} diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index 73738eaa..947142ff 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -36,6 +36,10 @@ @import './tag-team-dialog/initialization.less'; @import './tag-team-dialog/sheet.less'; +@import './group-roll-dialog/initialization.less'; +@import './group-roll-dialog/leader.less'; +@import './group-roll-dialog/sheet.less'; + @import './image-select/sheet.less'; @import './item-transfer/sheet.less'; diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index 30676f82..0d16aa3b 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -20,6 +20,17 @@ .member-name { position: absolute; + padding: 0 2px; + border: 1px solid; + border-radius: 6px; + margin-top: 4px; + color: light-dark(@dark, @beige); + background-image: url('../assets/parchments/dh-parchment-dark.png'); + } + + img { + border-radius: 6px; + border: 1px solid light-dark(@dark-blue, @golden); } } } diff --git a/system.json b/system.json index 300b1042..fed7d02d 100644 --- a/system.json +++ b/system.json @@ -290,7 +290,6 @@ "damageRoll": {}, "abilityUse": {}, "tagTeam": {}, - "groupRoll": {}, "systemMessage": {} } }, diff --git a/templates/dialogs/groupRollDialog/footer.hbs b/templates/dialogs/groupRollDialog/footer.hbs new file mode 100644 index 00000000..cb041247 --- /dev/null +++ b/templates/dialogs/groupRollDialog/footer.hbs @@ -0,0 +1,6 @@ +
+
+ + +
+
\ No newline at end of file diff --git a/templates/dialogs/groupRollDialog/groupRoll.hbs b/templates/dialogs/groupRollDialog/groupRoll.hbs new file mode 100644 index 00000000..edf1c980 --- /dev/null +++ b/templates/dialogs/groupRollDialog/groupRoll.hbs @@ -0,0 +1,20 @@ +
+
+ {{localize "DAGGERHEART.GENERAL.result.single"}} + +
+ {{#if hasRolled}}{{groupRoll.total}} {{groupRoll.totalLabel}}{{/if}} +
+ {{#if groupRoll.leaderTotal includeZero=true}}{{groupRoll.leaderTotal}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leaderRoll"}}{{/if}} + {{#each groupRoll.modifiers as |modifier|}} + {{#if (gte modifier 0)}}+{{else}}-{{/if}} + {{positive modifier}} + {{/each}} + {{#unless groupRoll.modifiers.length}} + + + {{localize "DAGGERHEART.GENERAL.Modifier.plural"}} + {{/unless}} +
+
+
+
\ No newline at end of file diff --git a/templates/dialogs/groupRollDialog/groupRollMember.hbs b/templates/dialogs/groupRollDialog/groupRollMember.hbs new file mode 100644 index 00000000..acf8e8f1 --- /dev/null +++ b/templates/dialogs/groupRollDialog/groupRollMember.hbs @@ -0,0 +1,85 @@ +{{#with (lookup members partId)}} +
+
+
+ +
+ {{name}} +
+
+
+ {{!-- --}} + +
+
+
+
+
+ {{#if readyToRoll}} +
+ + {{localize "DAGGERHEART.GENERAL.roll"}} +
+ + + + + {{#if hasRolled}} + + + + {{/if}} +
+
+ + {{#if roll}} +
+
{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}
+
+ + {{roll.dHope.total}} + + + + + + {{roll.dFear.total}} + + + {{#if roll.advantage.type}} + {{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}} + + {{roll.advantage.value}} + + + {{/if}} + {{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}} + {{positive roll.modifierTotal}} +
+
+ {{else}} + {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}} + {{/if}} +
+ {{/if}} + {{#if hasRolled}} +
+ {{#if ../isGM}} + + {{/if}} +
+ {{localize "DAGGERHEART.GENERAL.Modifier.single"}}{{#if successfull}} + 1{{else if (isNullish successfull)}} + ?{{else}} - 1{{/if}} +
+
+ {{/if}} +
+
+{{/with}} \ No newline at end of file diff --git a/templates/dialogs/groupRollDialog/initialization.hbs b/templates/dialogs/groupRollDialog/initialization.hbs new file mode 100644 index 00000000..a520b8bd --- /dev/null +++ b/templates/dialogs/groupRollDialog/initialization.hbs @@ -0,0 +1,32 @@ +
+
+ {{#each memberSelection as |member|}} + + {{member.name}} + + + {{/each}} +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ {{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.openDialogForAll"}} + +
+
+
\ No newline at end of file diff --git a/templates/dialogs/groupRollDialog/leader.hbs b/templates/dialogs/groupRollDialog/leader.hbs new file mode 100644 index 00000000..3d5db3f7 --- /dev/null +++ b/templates/dialogs/groupRollDialog/leader.hbs @@ -0,0 +1,73 @@ +
+ {{#with leader}} +
+
{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leader"}}
+
+
+
+ +
+ {{name}} +
+
+
+ +
+
+
+
+
+
+ + {{#if readyToRoll}} +
+ + {{localize "DAGGERHEART.GENERAL.roll"}} +
+ + + + + {{#if hasRolled}} + + + + {{/if}} +
+
+ + {{#if roll}} +
+
{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}
+
+ + {{roll.dHope.total}} + + + + + + {{roll.dFear.total}} + + + {{#if roll.advantage.type}} + {{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}} + + {{roll.advantage.value}} + + + {{/if}} + {{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}} + {{positive roll.modifierTotal}} +
+
+ {{else}} + {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}} + {{/if}} +
+ {{/if}} +
+
+ {{/with}} +
\ No newline at end of file diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs index d25e8f6c..7ccdf566 100644 --- a/templates/dialogs/tagTeamDialog/initialization.hbs +++ b/templates/dialogs/tagTeamDialog/initialization.hbs @@ -1,5 +1,4 @@
- {{partId}}

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

{{#each memberSelection as |member|}} diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs index 8a113ac8..bc0c6672 100644 --- a/templates/sheets/actors/party/party-members.hbs +++ b/templates/sheets/actors/party/party-members.hbs @@ -9,15 +9,10 @@ Tag Team Roll - - {{!-- NOT YET IMPLEMENTED --}} - {{!-- --}} + {{#if isLastTab}} {{else}} - + {{/if}}
\ No newline at end of file diff --git a/templates/dialogs/characterReset.hbs b/templates/dialogs/characterReset.hbs index 298826e5..59f88437 100644 --- a/templates/dialogs/characterReset.hbs +++ b/templates/dialogs/characterReset.hbs @@ -28,6 +28,6 @@ - + \ No newline at end of file diff --git a/templates/dialogs/deathMove.hbs b/templates/dialogs/deathMove.hbs index 341659df..97900022 100644 --- a/templates/dialogs/deathMove.hbs +++ b/templates/dialogs/deathMove.hbs @@ -17,7 +17,7 @@
+
\ No newline at end of file diff --git a/templates/dialogs/groupRollDialog/footer.hbs b/templates/dialogs/groupRollDialog/footer.hbs index cb041247..e401966b 100644 --- a/templates/dialogs/groupRollDialog/footer.hbs +++ b/templates/dialogs/groupRollDialog/footer.hbs @@ -1,6 +1,6 @@
- +
\ No newline at end of file diff --git a/templates/dialogs/image-select/footer.hbs b/templates/dialogs/image-select/footer.hbs index cd7d3d1a..58a60cc4 100644 --- a/templates/dialogs/image-select/footer.hbs +++ b/templates/dialogs/image-select/footer.hbs @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/templates/dialogs/multiclassChoice.hbs b/templates/dialogs/multiclassChoice.hbs index 3c89ff1a..55365939 100644 --- a/templates/dialogs/multiclassChoice.hbs +++ b/templates/dialogs/multiclassChoice.hbs @@ -16,7 +16,7 @@
- +
diff --git a/templates/levelup/tabs/footer.hbs b/templates/levelup/tabs/footer.hbs index 2ee7a316..d487e657 100644 --- a/templates/levelup/tabs/footer.hbs +++ b/templates/levelup/tabs/footer.hbs @@ -20,7 +20,7 @@ {{/if}} {{#unless levelupAuto}} {{/unless}} diff --git a/templates/settings/automation-settings/footer.hbs b/templates/settings/automation-settings/footer.hbs index 54939c17..7e9d1991 100644 --- a/templates/settings/automation-settings/footer.hbs +++ b/templates/settings/automation-settings/footer.hbs @@ -1,10 +1,10 @@
\ No newline at end of file diff --git a/templates/settings/downtime-config/footer.hbs b/templates/settings/downtime-config/footer.hbs index 5e5f31dd..199aea15 100644 --- a/templates/settings/downtime-config/footer.hbs +++ b/templates/settings/downtime-config/footer.hbs @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/templates/settings/homebrew-settings/footer.hbs b/templates/settings/homebrew-settings/footer.hbs index 954572de..ba1b5ada 100644 --- a/templates/settings/homebrew-settings/footer.hbs +++ b/templates/settings/homebrew-settings/footer.hbs @@ -1,10 +1,10 @@
\ No newline at end of file diff --git a/templates/settings/metagaming-settings/footer.hbs b/templates/settings/metagaming-settings/footer.hbs index 54939c17..7e9d1991 100644 --- a/templates/settings/metagaming-settings/footer.hbs +++ b/templates/settings/metagaming-settings/footer.hbs @@ -1,10 +1,10 @@
\ No newline at end of file diff --git a/templates/settings/variant-rules.hbs b/templates/settings/variant-rules.hbs index df7accb3..31316dc6 100644 --- a/templates/settings/variant-rules.hbs +++ b/templates/settings/variant-rules.hbs @@ -32,11 +32,11 @@
diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index a05fed35..711d0c9f 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -5,7 +5,7 @@
- + diff --git a/templates/sheets/actors/character/loadout.hbs b/templates/sheets/actors/character/loadout.hbs index 0319d56f..5e4c9f54 100644 --- a/templates/sheets/actors/character/loadout.hbs +++ b/templates/sheets/actors/character/loadout.hbs @@ -5,7 +5,7 @@
- +
diff --git a/templates/sheets/actors/party/inventory.hbs b/templates/sheets/actors/party/inventory.hbs index 92371b8d..186e2e99 100644 --- a/templates/sheets/actors/party/inventory.hbs +++ b/templates/sheets/actors/party/inventory.hbs @@ -5,7 +5,7 @@
- +
diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs index bc0c6672..aa41aeaa 100644 --- a/templates/sheets/actors/party/party-members.hbs +++ b/templates/sheets/actors/party/party-members.hbs @@ -7,11 +7,11 @@
{{/if}} - {{#if (and hasSave currentTargets.length)}}
Reaction Roll All Targets
{{/if}} + {{#if (and hasSave currentTargets.length)}}
{{localize "DAGGERHEART.UI.Chat.saveRoll.reactionRollAllTargets"}}
{{/if}} {{#each currentTargets}}
diff --git a/templates/ui/itemBrowser/itemBrowser.hbs b/templates/ui/itemBrowser/itemBrowser.hbs index 137693fc..d4946c1f 100644 --- a/templates/ui/itemBrowser/itemBrowser.hbs +++ b/templates/ui/itemBrowser/itemBrowser.hbs @@ -1,14 +1,14 @@
{{#if menu.path.length }}
@@ -17,7 +17,7 @@
- +
diff --git a/templates/ui/tooltip/action.hbs b/templates/ui/tooltip/action.hbs index 29d44dde..959188f3 100644 --- a/templates/ui/tooltip/action.hbs +++ b/templates/ui/tooltip/action.hbs @@ -16,7 +16,7 @@ {{#if (gt item.cost.length 0)}} {{#each item.cost as | cost |}}
- {{localize "Type"}} {{#with (lookup @root.config.GENERAL.abilityCosts cost.type) as | type |}}{{localize type.label}}{{/with}} + {{localize "DAGGERHEART.GENERAL.type"}} {{#with (lookup @root.config.GENERAL.abilityCosts cost.type) as | type |}}{{localize type.label}}{{/with}}
{{localize "DAGGERHEART.GENERAL.value"}} {{cost.value}} diff --git a/templates/ui/tooltip/adversary.hbs b/templates/ui/tooltip/adversary.hbs index bba7e696..f96fe2a0 100644 --- a/templates/ui/tooltip/adversary.hbs +++ b/templates/ui/tooltip/adversary.hbs @@ -11,7 +11,7 @@ {{/with}}
- + {{#with (lookup adversaryTypes item.system.type) as | type |}}
{{localize type.label}}
{{/with}} diff --git a/templates/ui/tooltip/battlepoints.hbs b/templates/ui/tooltip/battlepoints.hbs index d793fe8c..9672698a 100644 --- a/templates/ui/tooltip/battlepoints.hbs +++ b/templates/ui/tooltip/battlepoints.hbs @@ -1,6 +1,6 @@
-

{{localize "Adversaries"}} ({{currentBP}}/{{maxBP}})

+

{{localize "DAGGERHEART.GENERAL.Adversary.plural"}} ({{currentBP}}/{{maxBP}})

{{#each categories as |category key|}} @@ -17,7 +17,7 @@
-

{{localize "Modifiers"}}

+

{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}

{{#each toggles as |toggle|}}
From f22b67367be3122a7c36ceccfbe0d59920168393 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 11 Apr 2026 23:57:33 +0200 Subject: [PATCH 25/54] Updated system.json to point to V14 --- system.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system.json b/system.json index 450c33b2..bedac7db 100644 --- a/system.json +++ b/system.json @@ -8,6 +8,9 @@ "verified": "14.360", "maximum": "14" }, + "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.1.1/system.zip", "authors": [ { "name": "WBHarry" @@ -295,8 +298,5 @@ }, "background": "systems/daggerheart/assets/logos/FoundrybornBackgroundLogo.png", "primaryTokenAttribute": "resources.hitPoints", - "secondaryTokenAttribute": "resources.stress", - "url": "https://github.com/Foundryborne/daggerheart", - "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/main/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.1.1/system.zip" + "secondaryTokenAttribute": "resources.stress" } From 94f1fbdd9b9ebb6705a70e7694699aec1cb61bbf Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 12 Apr 2026 00:21:16 +0200 Subject: [PATCH 26/54] Updated system.json --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index bedac7db..614b9cc0 100644 --- a/system.json +++ b/system.json @@ -9,7 +9,7 @@ "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart", - "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/V14/system.json", + "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.1.1/system.zip", "authors": [ { From 3ec013ff5072700572b1cd32a87ec39f0c4528da Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 12 Apr 2026 00:25:43 +0200 Subject: [PATCH 27/54] Reworked summon action and clowncar functionality to work with levels (#1791) --- module/applications/hud/tokenHUD.mjs | 16 ++- module/canvas/tokens.mjs | 17 +-- module/data/fields/action/summonField.mjs | 38 +++--- module/documents/tokenManager.mjs | 138 ++++++++-------------- 4 files changed, 79 insertions(+), 130 deletions(-) diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs index 77caaaff..943f3506 100644 --- a/module/applications/hud/tokenHUD.mjs +++ b/module/applications/hud/tokenHUD.mjs @@ -122,15 +122,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { async toggleClowncar(actors) { const animationDuration = 500; - const activeTokens = actors.flatMap(member => member.getActiveTokens()); + const scene = game.scenes.get(game.user.viewedScene); + /* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */ + const activeTokens = actors.flatMap(member => member.getDependentTokens({ scenes: scene }).filter(x => x._id)); const { x: actorX, y: actorY } = this.document; if (activeTokens.length > 0) { for (let token of activeTokens) { - await token.document.update( - { x: actorX, y: actorY, alpha: 0 }, - { animation: { duration: animationDuration } } - ); - setTimeout(() => token.document.delete(), animationDuration); + await token.update({ x: actorX, y: actorY, alpha: 0 }, { animation: { duration: animationDuration } }); + setTimeout(() => token.delete(), animationDuration); } } else { const activeScene = game.scenes.find(x => x.id === game.user.viewedScene); @@ -140,11 +139,16 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { tokenData.push(data.toObject()); } + const viewedLevel = game.scenes.get(game.user.viewedScene).levels.get(game.user.viewedLevel); + const elevation = this.actor.token?.elevation ?? viewedLevel.elevation.bottom; + const newTokens = await activeScene.createEmbeddedDocuments( 'Token', tokenData.map(tokenData => ({ ...tokenData, alpha: 0, + level: viewedLevel, + elevation: elevation, x: actorX, y: actorY })) diff --git a/module/canvas/tokens.mjs b/module/canvas/tokens.mjs index 9813cd48..9ca140e0 100644 --- a/module/canvas/tokens.mjs +++ b/module/canvas/tokens.mjs @@ -1,16 +1 @@ -export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer { - async _createPreview(createData, options) { - if (options.actor) { - const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; - if (options.actor?.system.metadata.usesSize) { - const tokenSize = tokenSizes[options.actor.system.size]; - if (tokenSize && options.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) { - createData.width = tokenSize; - createData.height = tokenSize; - } - } - } - - return super._createPreview(createData, options); - } -} +export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {} diff --git a/module/data/fields/action/summonField.mjs b/module/data/fields/action/summonField.mjs index dce6414c..36ea1010 100644 --- a/module/data/fields/action/summonField.mjs +++ b/module/data/fields/action/summonField.mjs @@ -44,12 +44,18 @@ export default class DHSummonField extends fields.ArrayField { count = roll.total; } - const actor = DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); + const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); /* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */ - summon.rolledCount = count; summon.actor = actor.toObject(); - summonData.push({ actor, count: count }); + const countNumber = Number.parseInt(count); + for (let i = 0; i < countNumber; i++) { + const remaining = countNumber - i; + summonData.push({ + actor, + tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}` + }); + } } if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true))); @@ -58,32 +64,22 @@ 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 */ - static getWorldActor(baseActor) { + /* 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); - return worldActorCopy ?? baseActor; + if (worldActorCopy) return worldActorCopy; + + return await game.system.api.documents.DhpActor.create(baseActor.toObject()); } return baseActor; } - static async handleSummon(summonData, actionActor, summonIndex = 0) { - const summon = summonData[summonIndex]; - const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, { - name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}` - }); + static async handleSummon(summonData, actionActor) { + await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation }); - if (!result) return actionActor.sheet?.maximize(); - summon.actor = result.actor; - - summon.count--; - if (summon.count <= 0) { - summonIndex++; - if (summonIndex === summonData.length) return actionActor.sheet?.maximize(); - } - - DHSummonField.handleSummon(summonData, actionActor, summonIndex); + return actionActor.sheet?.maximize(); } } diff --git a/module/documents/tokenManager.mjs b/module/documents/tokenManager.mjs index f766a677..3ccff4e2 100644 --- a/module/documents/tokenManager.mjs +++ b/module/documents/tokenManager.mjs @@ -1,104 +1,68 @@ /** - * A singleton class that handles preview tokens. + * A singleton class that handles creating tokens. */ export default class DhTokenManager { - #activePreview; - #actor; - #resolve; - /** - * Create a template preview, deactivating any existing ones. - * @param {object} data + * Create a token previer + * @param {Actor} actor + * @param {object} tokenData */ async createPreview(actor, tokenData) { - this.#actor = actor; - const token = await canvas.tokens._createPreview( - { - ...actor.prototypeToken, - displayName: 50, - ...tokenData - }, - { renderSheet: false, actor } + const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; + if (actor?.system.metadata.usesSize) { + const tokenSize = tokenSizes[actor.system.size]; + if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) { + tokenData.width = tokenSize; + tokenData.height = tokenSize; + } + } + + return await canvas.tokens.placeTokens( + [ + { + ...actor.prototypeToken.toObject(), + actorId: actor.id, + displayName: 50, + ...tokenData + } + ], + { create: false } ); - - this.#activePreview = { - document: token.document, - object: token, - origin: { x: token.document.x, y: token.document.y } - }; - - this.#activePreview.events = { - contextmenu: this.#cancelTemplate.bind(this), - mousedown: this.#confirmTemplate.bind(this), - mousemove: this.#onDragMouseMove.bind(this) - }; - - canvas.stage.on('mousemove', this.#activePreview.events.mousemove); - canvas.stage.on('mousedown', this.#activePreview.events.mousedown); - canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu); - } - - /* Currently intended for using as a preview of where to create a token. (note the flag) */ - async createPreviewAsync(actor, tokenData = {}) { - return new Promise(resolve => { - this.#resolve = resolve; - this.createPreview(actor, { ...tokenData, flags: { daggerheart: { createPlacement: true } } }); - }); } /** - * Handles the movement of the token preview on mousedrag. - * @param {mousemove Event} event + * Creates new tokens on the canvas by placing previews. + * @param {object} tokenData + * @param {object} options */ - #onDragMouseMove(event) { - event.stopPropagation(); - const { moveTime, object } = this.#activePreview; - const update = {}; + async createTokensWithPreview(tokensData, { elevation } = {}) { + const scene = game.scenes.get(game.user.viewedScene); + if (!scene) return; - const now = Date.now(); - if (now - (moveTime || 0) <= 16) return; - this.#activePreview.moveTime = now; + const level = scene.levels.get(game.user.viewedLevel); + if (!level) return; - let cursor = event.getLocalPosition(canvas.templates); + const createElevation = elevation ?? level.elevation.bottom; + for (const tokenData of tokensData) { + const previewTokens = await this.createPreview(tokenData.actor, { + name: tokenData.tokenPreviewName, + level: game.user.viewedLevel, + elevation: createElevation, + flags: { daggerheart: { createPlacement: true } } + }); + if (!previewTokens?.length) return null; - Object.assign(update, canvas.grid.getTopLeftPoint(cursor)); - - object.document.updateSource(update); - object.renderFlags.set({ refresh: true }); - } - - /** - * Cancels the preview token on right-click. - * @param {contextmenu Event} event - */ - #cancelTemplate(_event, resolved) { - const { mousemove, mousedown, contextmenu } = this.#activePreview.events; - this.#activePreview.object.destroy(); - - canvas.stage.off('mousemove', mousemove); - canvas.stage.off('mousedown', mousedown); - canvas.app.view.removeEventListener('contextmenu', contextmenu); - if (this.#resolve && !resolved) this.#resolve(false); - } - - /** - * Creates a real Actor and token at the preview location and cancels the preview. - * @param {click Event} event - */ - async #confirmTemplate(event) { - event.stopPropagation(); - this.#cancelTemplate(event, true); - - const actor = this.#actor.inCompendium - ? await game.system.api.documents.DhpActor.create(this.#actor.toObject()) - : this.#actor; - const tokenData = await actor.getTokenDocument(); - const result = await canvas.scene.createEmbeddedDocuments('Token', [ - { ...tokenData.toObject(), x: this.#activePreview.document.x, y: this.#activePreview.document.y } - ]); - - this.#activePreview = undefined; - if (this.#resolve && result.length) this.#resolve(result[0]); + await canvas.scene.createEmbeddedDocuments( + 'Token', + previewTokens.map(x => ({ + ...x.toObject(), + name: tokenData.actor.prototypeToken.name, + displayName: tokenData.actor.prototypeToken.displayName, + flags: tokenData.actor.prototypeToken.flags + })), + { controlObject: true, parent: canvas.scene } + ); + } } } From e2c97a7b61023d1a192db91b6ac4c756bb5e12fe Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 12 Apr 2026 00:32:59 +0200 Subject: [PATCH 28/54] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 614b9cc0..babdde26 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.1.1", + "version": "2.1.2", "compatibility": { "minimum": "14.359", "verified": "14.360", @@ -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.1.1/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.1.2/system.zip", "authors": [ { "name": "WBHarry" From a839ca006667de2db5a769ec511305886e935f4a Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 12 Apr 2026 00:36:24 +0200 Subject: [PATCH 29/54] Corrected deploy.yml for new branch --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e245c7fa..553a1a17 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: env: version: ${{steps.get_version.outputs.version-without-v}} url: https://github.com/${{github.repository}} - manifest: https://raw.githubusercontent.com/${{github.repository}}/main/system.json + manifest: https://raw.githubusercontent.com/${{github.repository}}/v14/system.json download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip # Create a zip file with all files required by the module to add to the release From 66c90d69e3aad53a2bb960780261e02ef43723d8 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 12 Apr 2026 11:10:02 +0200 Subject: [PATCH 30/54] Added saefety to updateActorsRangeDepenedentEffects --- daggerheart.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daggerheart.mjs b/daggerheart.mjs index 43aafce4..598c1a78 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -360,7 +360,7 @@ const updateActorsRangeDependentEffects = async token => { CONFIG.DH.SETTINGS.gameSettings.variantRules ).rangeMeasurement; - for (let effect of token.actor?.allApplicableEffects() ?? []) { + for (let effect of token.actor?.allApplicableEffects?.() ?? []) { if (!effect.system.rangeDependence?.enabled) continue; const { target, range, type } = effect.system.rangeDependence; From e003db3ec137fcc08f7e6ac4cfa0e87e2e183676 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 12 Apr 2026 11:22:00 +0200 Subject: [PATCH 31/54] Corrected updateActorsRangeDependentEffects when token is null --- daggerheart.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daggerheart.mjs b/daggerheart.mjs index 598c1a78..064b1670 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -355,12 +355,14 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => { }); const updateActorsRangeDependentEffects = async token => { + if (!token) return; + const rangeMeasurement = game.settings.get( CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules ).rangeMeasurement; - for (let effect of token.actor?.allApplicableEffects?.() ?? []) { + for (let effect of token.actor?.allApplicableEffects() ?? []) { if (!effect.system.rangeDependence?.enabled) continue; const { target, range, type } = effect.system.rangeDependence; From 56a6613a73d3edce24a8a52ed217be0788cc3606 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 12 Apr 2026 11:38:15 +0200 Subject: [PATCH 32/54] Fixed more missing translations (#1792) --- lang/en.json | 20 ++++++++++++----- module/applications/ui/itemBrowser.mjs | 2 +- module/config/itemBrowserConfig.mjs | 22 +++++++++++-------- .../footer.hbs | 2 +- templates/sheets/items/weapon/header.hbs | 4 ++-- templates/ui/tooltip/battlepoints.hbs | 6 ++--- templates/ui/tooltip/weapon.hbs | 2 +- 7 files changed, 36 insertions(+), 22 deletions(-) diff --git a/lang/en.json b/lang/en.json index a2c3dc79..45a3b414 100755 --- a/lang/en.json +++ b/lang/en.json @@ -74,9 +74,7 @@ "name": "Summon", "tooltip": "Create tokens in the scene.", "error": "You do not have permission to summon tokens or there is no active scene.", - "invalidDrop": "You can only drop Actor entities to summon.", - "chatMessageTitle": "Test2", - "chatMessageHeaderTitle": "Summoning" + "invalidDrop": "You can only drop Actor entities to summon." }, "transform": { "name": "Transform", @@ -2016,6 +2014,10 @@ "hint": "Multiply any damage dealt to you by this number" } }, + "Battlepoints": { + "full": "Battlepoints", + "short": "BP" + }, "Bonuses": { "rest": { "downtimeAction": "Downtime Action", @@ -2457,6 +2459,7 @@ "rollDamage": "Roll Damage", "rollWith": "{roll} Roll", "save": "Save", + "saveSettings": "Save Settings", "scalable": "Scalable", "scars": "Scars", "situationalBonus": "Situational Bonus", @@ -2611,8 +2614,14 @@ }, "Weapon": { "weaponType": "Weapon Type", - "primaryWeapon": "Primary Weapon", - "secondaryWeapon": "Secondary Weapon" + "primaryWeapon": { + "full": "Primary Weapon", + "short": "Primary" + }, + "secondaryWeapon": { + "full": "Secondary Weapon", + "short": "Secondary" + } } }, "MACROS": { @@ -3067,6 +3076,7 @@ }, "ItemBrowser": { "title": "Daggerheart Compendium Browser", + "windowTitle": "Compendium Browser", "hint": "Select a Folder in sidebar to start browsing through the compendium", "browserSettings": "Browser Settings", "columnName": "Name", diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 9ca328a0..22de38ab 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -37,7 +37,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { tag: 'div', window: { frame: true, - title: 'Compendium Browser', + title: 'DAGGERHEART.UI.ItemBrowser.windowTitle', icon: 'fa-solid fa-book-atlas', positioned: true, resizable: true diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 0a4154a8..f20e56d0 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -74,12 +74,13 @@ export const typeConfig = { columns: [ { key: 'type', - label: 'DAGGERHEART.GENERAL.type' + label: 'DAGGERHEART.GENERAL.type', + format: type => type ? `TYPES.Item.${type}` : '-' }, { key: 'system.secondary', label: 'DAGGERHEART.UI.ItemBrowser.subtype', - format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-') + format: isSecondary => (isSecondary ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' : '-') }, { key: 'system.tier', @@ -99,8 +100,8 @@ export const typeConfig = { key: 'system.secondary', label: 'DAGGERHEART.UI.ItemBrowser.subtype', choices: [ - { value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' }, - { value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' } + { value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' }, + { value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' } ] }, { @@ -258,11 +259,13 @@ export const typeConfig = { columns: [ { key: 'system.type', - label: 'DAGGERHEART.GENERAL.type' + label: 'DAGGERHEART.GENERAL.type', + format: type => type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-' }, { key: 'system.domain', - label: 'DAGGERHEART.GENERAL.Domain.single' + label: 'DAGGERHEART.GENERAL.Domain.single', + format: domain => domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-' }, { key: 'system.level', @@ -374,7 +377,7 @@ export const typeConfig = { columns: [ { key: 'system.linkedClass', - label: 'Class', + label: 'TYPES.Item.class', format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing' }, { @@ -386,7 +389,7 @@ export const typeConfig = { filters: [ { key: 'system.linkedClass.uuid', - label: 'Class', + label: 'TYPES.Item.class', choices: items => { const list = items .filter(item => item.system.linkedClass) @@ -410,7 +413,8 @@ export const typeConfig = { }, { key: 'system.mainTrait', - label: 'DAGGERHEART.GENERAL.Trait.single' + label: 'DAGGERHEART.GENERAL.Trait.single', + format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-') } ], filters: [ diff --git a/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs b/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs index 9dc61cbe..d9bb378e 100644 --- a/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs +++ b/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs @@ -1,3 +1,3 @@
- +
\ No newline at end of file diff --git a/templates/sheets/items/weapon/header.hbs b/templates/sheets/items/weapon/header.hbs index 9bbd9511..fabbb07f 100644 --- a/templates/sheets/items/weapon/header.hbs +++ b/templates/sheets/items/weapon/header.hbs @@ -5,9 +5,9 @@

{{#if source.system.secondary}} -

{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}}

+

{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full"}}

{{else}} -

{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon"}}

+

{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon.full"}}

{{/if}}

{{localize (concat 'DAGGERHEART.CONFIG.Traits.' source.system.attack.roll.trait '.short')}} diff --git a/templates/ui/tooltip/battlepoints.hbs b/templates/ui/tooltip/battlepoints.hbs index 9672698a..f2f42f53 100644 --- a/templates/ui/tooltip/battlepoints.hbs +++ b/templates/ui/tooltip/battlepoints.hbs @@ -7,9 +7,9 @@ {{#each category as |grouping index|}}
{{#if grouping.nr}} - + {{else}} - + {{/if}}
{{/each}} @@ -26,7 +26,7 @@ {{else}} {{/if}} - +

{{/each}}
diff --git a/templates/ui/tooltip/weapon.hbs b/templates/ui/tooltip/weapon.hbs index a672c883..4adb9c46 100644 --- a/templates/ui/tooltip/weapon.hbs +++ b/templates/ui/tooltip/weapon.hbs @@ -3,7 +3,7 @@

{{item.name}}

- {{#if item.system.secondary}}{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}}{{else}}{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon"}}{{/if}} + {{#if item.system.secondary}}{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full"}}{{else}}{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon.full"}}{{/if}}
{{#with (lookup config.GENERAL.burden item.system.burden) as | burden |}} From f9000115102284082263a3a49573bfc7e309d4a9 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 12 Apr 2026 13:58:43 +0200 Subject: [PATCH 33/54] Fixed trait counting in CharacterCreation. Fixed description layout and labels in CharacterCreation. Fixed drag/drop images from Compendium Browser --- lang/en.json | 1 + .../characterCreation/characterCreation.mjs | 9 +++++++-- module/applications/ui/itemBrowser.mjs | 2 ++ .../selections-container.less | 14 +++++++++++++ .../characterCreation/tabs/experience.hbs | 20 +++++++++++++------ 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lang/en.json b/lang/en.json index a2c3dc79..66df26b3 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2431,6 +2431,7 @@ "next": "Next", "none": "None", "noTarget": "No current target", + "optionalThing": "Optional {thing}", "partner": "Partner", "player": { "single": "Player", diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index e6c0f299..936bb79d 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -11,7 +11,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl this.character = character; this.setup = { - traits: this.character.system.traits, + traits: Object.keys(this.character.system.traits).reduce((acc, key) => { + acc[key] = { value: null }; + return acc; + }, {}), ancestryName: { primary: '', secondary: '' @@ -377,8 +380,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl ]; return Object.values(this.setup.traits).reduce((acc, x) => { const index = traitCompareArray.indexOf(x.value); + if (index === -1) return acc; + traitCompareArray.splice(index, 1); - acc += index !== -1; + acc += 1; return acc; }, 0); } diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 9ca328a0..4c64c39e 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -583,7 +583,9 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { const { itemUuid } = event.target.closest('[data-item-uuid]').dataset, item = await foundry.utils.fromUuid(itemUuid), dragData = item.toDragData(); + event.dataTransfer.setData('text/plain', JSON.stringify(dragData)); + event.dataTransfer.setDragImage(event.target.querySelector('img'), 0, 0); } _canDragStart() { diff --git a/styles/less/dialog/character-creation/selections-container.less b/styles/less/dialog/character-creation/selections-container.less index f9569fca..a68cd36c 100644 --- a/styles/less/dialog/character-creation/selections-container.less +++ b/styles/less/dialog/character-creation/selections-container.less @@ -175,6 +175,11 @@ opacity: 0.2; } + &.no-horizontal-padding { + padding-left: 0; + padding-right: 0; + } + legend { margin-left: auto; margin-right: auto; @@ -278,6 +283,15 @@ flex-direction: column; gap: 5px; + &.separated { + border-bottom: 2px solid; + padding-bottom: 8px; + } + + .form-group { + padding: 0 0.75rem; + } + .experience-inner-container { position: relative; display: flex; diff --git a/templates/characterCreation/tabs/experience.hbs b/templates/characterCreation/tabs/experience.hbs index 3eb92834..66363084 100644 --- a/templates/characterCreation/tabs/experience.hbs +++ b/templates/characterCreation/tabs/experience.hbs @@ -4,17 +4,25 @@ data-group='{{tabs.experience.group}}' >
-
+
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.initialExperiences"}} {{experience.nrSelected}}/{{experience.nrTotal}}
{{#each experience.values as |experience id|}} -
-
- - {{numberFormat this.value sign=true}} +
+
+ +
+ + {{numberFormat this.value sign=true}} +
- +
+ +
+ +
+
{{/each}}
From fb07938e54aefaa9528e7d5d4b27e5e955e4f90e Mon Sep 17 00:00:00 2001 From: Nikhil Nagarajan Date: Sun, 12 Apr 2026 13:10:51 -0400 Subject: [PATCH 34/54] container fix (#1795) --- .../less/dialog/character-creation/selections-container.less | 5 +++++ styles/less/sheets-settings/character-settings/sheet.less | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/styles/less/dialog/character-creation/selections-container.less b/styles/less/dialog/character-creation/selections-container.less index a68cd36c..2bbac484 100644 --- a/styles/less/dialog/character-creation/selections-container.less +++ b/styles/less/dialog/character-creation/selections-container.less @@ -255,6 +255,11 @@ height: 65px; background: url(../assets/svg/trait-shield.svg) no-repeat; background-size: 100%; + padding-top: 3px; + display: flex; + flex-direction: column; + align-items: center; + row-gap: 3px; div { filter: drop-shadow(0 0 3px black); diff --git a/styles/less/sheets-settings/character-settings/sheet.less b/styles/less/sheets-settings/character-settings/sheet.less index f7f16df4..eab29436 100644 --- a/styles/less/sheets-settings/character-settings/sheet.less +++ b/styles/less/sheets-settings/character-settings/sheet.less @@ -27,10 +27,11 @@ height: 65px; background: url(../assets/svg/trait-shield.svg) no-repeat; background-size: 100%; - padding-top: 4px; + padding-top: 3px; display: flex; flex-direction: column; align-items: center; + row-gap: 3px; span { font-size: var(--font-size-10); From 8d8dea81fe100220433252928254250673a09cf8 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:41:28 +0200 Subject: [PATCH 35/54] [Fix] Compendium Advantage Sources (#1796) * Duration Description field didn't show initially for type temporary * Corrected SRD --- .../feature_Retract_UFR67BUOhNGLFyg9.json | 54 +++++++++++------- ...ure_Low_Light_Living_aMla3xQuCHEwORGD.json | 55 +++++++++---------- ...omainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json | 44 +++++++++------ templates/sheets/activeEffect/settings.hbs | 8 +-- 4 files changed, 92 insertions(+), 69 deletions(-) diff --git a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json index b17cd7da..eb9696b2 100644 --- a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json +++ b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json @@ -68,31 +68,33 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.resistance.physical.resistance", + "type": "override", + "value": 1, + "priority": null, + "phase": "initial" + }, + { + "key": "system.disadvantageSources", + "type": "add", + "value": "Action rolls", + "priority": null, + "phase": "initial" + } + ], + "duration": { + "type": "" } }, - "changes": [ - { - "key": "system.resistance.physical.resistance", - "mode": 5, - "value": "1", - "priority": null - }, - { - "key": "system.disadvantageSources", - "mode": 2, - "value": "Retract", - "priority": null - } - ], "disabled": true, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

While in your shell, you have resistance to physical damage, you have disadvantage on action rolls, and you can’t move.

", "tint": "#ffffff", @@ -102,6 +104,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!UFR67BUOhNGLFyg9.3V4FPoyjJUnFP9WS" } ], diff --git a/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json b/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json index f1ed3ace..c95d9132 100644 --- a/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json +++ b/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json @@ -29,39 +29,28 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.advantageSources", + "type": "add", + "value": "Rolls to hide, investigate, or perceive details in low light", + "priority": null, + "phase": "initial" + } + ], + "duration": { + "type": "" } }, - "changes": [ - { - "key": "system.advantageSources", - "mode": 2, - "value": "In an area with low light or heavy shadow: hide, investigate, or perceive", - "priority": null - }, - { - "key": "system.advantageSources", - "mode": 2, - "value": "", - "priority": null - }, - { - "key": "", - "mode": 2, - "value": "", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "", + "description": "

When you’re in an area with low light or heavy shadow, you have advantage on rolls to hide, investigate, or perceive details within that area.

", "origin": null, "tint": "#ffffff", "transfer": true, @@ -71,6 +60,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!aMla3xQuCHEwORGD.pCp32u7UwqxCI4WW" } ], diff --git a/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json b/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json index c9ae6071..1fde286d 100644 --- a/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json +++ b/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json @@ -132,27 +132,29 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.advantageSources", + "type": "add", + "value": "On Attacks", + "priority": null, + "phase": "initial" + } + ], + "duration": { + "type": "temporary", + "description": "

Until you or an ally rolls a failure with Fear.

" } }, - "changes": [ - { - "key": "system.advantageSources", - "mode": 2, - "value": "1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "", + "description": "

You gain advantage on attack rolls until you or an ally rolls a failure with Fear.

", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -160,6 +162,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!Ef1JsUG50LIoKx2F.s7ma4TNgAvt0ZgEW" } ], diff --git a/templates/sheets/activeEffect/settings.hbs b/templates/sheets/activeEffect/settings.hbs index 9307ff65..09b78856 100644 --- a/templates/sheets/activeEffect/settings.hbs +++ b/templates/sheets/activeEffect/settings.hbs @@ -26,11 +26,11 @@ {{formGroup systemFields.duration.fields.type value=source.system.duration.type localize=true }} -
-
- {{formInput systemFields.duration.fields.description value=source.system.duration.description localize=true }} -
+
+
+ {{formInput systemFields.duration.fields.description value=source.system.duration.description localize=true }}
+
{{formGroup fields.start.fields.time value=source.start.time localize=true }} From a62d28cd96fdfdcdc74e3c8f9e6528edbf9840f9 Mon Sep 17 00:00:00 2001 From: CPTN_Cosmo Date: Tue, 14 Apr 2026 18:51:28 +0200 Subject: [PATCH 36/54] updated contributing guidelines (#1800) --- CONTRIBUTING.md | 79 ++++--------------------------------------------- 1 file changed, 5 insertions(+), 74 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9099005..261c26bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,78 +1,9 @@ -# Contributing to Foundryborne +# Contributing to Daggerheart -Welcome! This is a community-driven project to bring [Daggerheart](https://www.daggerheart.com/) to [FoundryVTT](https://foundryvtt.com/) as a full system. We're excited to have you here and appreciate your interest in contributing. +Thank you for your interest in contributing to the Foundryborne project! ---- +To ensure that all contributions align with our project goals and architectural standards, we ask that you **do not submit outside contributions without first receiving feedback from the development team.** -## 🤝 How to Contribute +If you have an idea or a fix you'd like to contribute, please start a discussion or open an issue first. We'd love to hear from you and collaborate on the best way to move forward! -We welcome contributions of all kinds: - -- Bug reports -- Feature suggestions -- Code contributions -- UI/UX mockups -- Documentation improvements -- Questions and discussions - -Please be respectful and collaborative — we’re all here to build something great together. - -### Community Translations - -Please note that we are not accepting community translations in the main project. Instead, community translations should be published as a module. - ---- - -## 🧭 General Guidelines - -- **Use GitHub Issues** to report bugs or propose features -- **Start a Discussion** for larger ideas or questions -- **Open a Pull Request** once you've confirmed your work aligns with project direction -- **Keep things modular and maintainable** — if you're not sure how to structure something, ask! -- **Orient your code on existing examples**, and feel free to suggest a standard if it makes things clearer - ---- - -## 🗂️ Project Structure - -Please try to follow the general logic of the existing code when submitting PRs. - -We encourage contributors to leave comments or open Discussions when proposing structural or organizational changes. - ---- - -## 🧾 Issue & PR Best Practices - -**For Issues:** - -- Use clear, descriptive titles -- Provide a concise explanation of the problem or idea -- Include reproduction steps or example scenarios if it's a bug -- Add screenshots or logs if helpful - -**For Pull Requests:** - -- Use a clear title summarizing the change -- Provide a brief description of what your code does and why -- Link to any related Issues -- Keep PRs focused — smaller is better - ---- - -## 🔖 Labels and Boards - -We use GitHub labels to help organize contributions. If your issue or PR relates to a specific category, feel free to tag it. There is also a GitHub Project Board to help track active work and priorities. - ---- - -## 📣 Communication - -Discussions are currently happening on GitHub — in Issues, PRs, and [GitHub Discussions](https://github.com/Foundryborne/daggerheart/discussions). You're welcome to use any of these, though we may consolidate to one in the future. - ---- - -## 🤗 Thank You! - -Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring _Daggerheart_ to life in FoundryVTT through **Foundryborne**! - -🐸🛠️ +Thank you for your understanding and support. From 1176328f625c66a86fe5e6e497d1f01062d4ecef Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 14 Apr 2026 20:55:10 +0200 Subject: [PATCH 37/54] Updated deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e245c7fa..553a1a17 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: env: version: ${{steps.get_version.outputs.version-without-v}} url: https://github.com/${{github.repository}} - manifest: https://raw.githubusercontent.com/${{github.repository}}/main/system.json + manifest: https://raw.githubusercontent.com/${{github.repository}}/v14/system.json download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip # Create a zip file with all files required by the module to add to the release From a77d2088a02a4ead25582b05ce74decee863defc Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 15 Apr 2026 12:42:30 -0400 Subject: [PATCH 38/54] Increase reuse of gold and inventory styling (#1804) --- module/data/actor/character.mjs | 9 +-- module/data/actor/party.mjs | 8 +-- module/data/fields/actorField.mjs | 13 ++++- module/systemRegistration/handlebars.mjs | 1 + .../sheets/actors/actor-sheet-shared.less | 53 ++++++++++++++++++ .../sheets/actors/character/inventory.less | 52 ----------------- .../less/sheets/actors/party/inventory.less | 56 ------------------- .../sheets/actors/character/inventory.hbs | 13 +---- templates/sheets/actors/party/inventory.hbs | 13 +---- templates/sheets/global/partials/gold.hbs | 12 ++++ 10 files changed, 84 insertions(+), 146 deletions(-) create mode 100644 templates/sheets/global/partials/gold.hbs diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 61338344..0e1e2259 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -3,7 +3,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import DhLevelData from '../levelData.mjs'; import { commonActorRules } from './base.mjs'; import DhCreature from './creature.mjs'; -import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; +import { attributeField, stressDamageReductionRule, bonusField, GoldField } from '../fields/actorField.mjs'; import { ActionField } from '../fields/actionField.mjs'; import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs'; import { getArmorSources } from '../../helpers/utils.mjs'; @@ -62,12 +62,7 @@ export default class DhCharacter extends DhCreature { core: new fields.BooleanField({ initial: false }) }) ), - gold: new fields.SchemaField({ - 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 }) - }), + gold: new GoldField(), 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/actor/party.mjs b/module/data/actor/party.mjs index ec1beb99..c9b99dcd 100644 --- a/module/data/actor/party.mjs +++ b/module/data/actor/party.mjs @@ -2,6 +2,7 @@ import BaseDataActor from './base.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import TagTeamData from '../tagTeamData.mjs'; import GroupRollData from '../groupRollData.mjs'; +import { GoldField } from '../fields/actorField.mjs'; export default class DhParty extends BaseDataActor { /**@inheritdoc */ @@ -11,12 +12,7 @@ export default class DhParty extends BaseDataActor { ...super.defineSchema(), partyMembers: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { prune: true }), notes: new fields.HTMLField(), - gold: new fields.SchemaField({ - 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 }) - }), + gold: new GoldField(), tagTeam: new fields.EmbeddedDataField(TagTeamData), groupRoll: new fields.EmbeddedDataField(GroupRollData) }; diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs index ae6f060c..d6b58675 100644 --- a/module/data/fields/actorField.mjs +++ b/module/data/fields/actorField.mjs @@ -103,4 +103,15 @@ class ResourcesField extends fields.TypedObjectField { } } -export { attributeField, ResourcesField, stressDamageReductionRule, bonusField }; +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 }) + }); + } +} + +export { attributeField, ResourcesField, GoldField, stressDamageReductionRule, bonusField }; diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 63e591c6..ebd65fa2 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -10,6 +10,7 @@ export const preloadHandlebarsTemplates = async function () { 'templates/generic/tab-navigation.hbs', 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs', 'systems/daggerheart/templates/sheets/global/partials/action-item.hbs', + 'systems/daggerheart/templates/sheets/global/partials/gold.hbs', 'systems/daggerheart/templates/sheets/global/partials/domain-card-item.hbs', 'systems/daggerheart/templates/sheets/global/partials/item-resource.hbs', 'systems/daggerheart/templates/sheets/global/partials/resource-section/resource-section.hbs', diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 23db088a..bd82ef83 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -37,6 +37,59 @@ color: light-dark(@chat-blue-bg, @beige-50); } + .tab.inventory { + .search-section { + display: flex; + gap: 10px; + align-items: center; + } + .search-bar { + position: relative; + color: light-dark(@dark-blue-50, @beige-50); + width: 100%; + padding-top: 5px; + + input { + border-radius: 50px; + background: light-dark(@dark-blue-10, @golden-10); + border: none; + outline: 2px solid transparent; + transition: all 0.3s ease; + padding: 0 20px; + + &:hover { + outline: 2px solid light-dark(@dark, @golden); + } + + &::-webkit-search-cancel-button { + -webkit-appearance: none; + display: none; + } + } + + .icon { + align-content: center; + height: 32px; + position: absolute; + right: 20px; + font-size: 16px; + z-index: 1; + color: light-dark(@dark-blue-50, @beige-50); + } + } + + .gold-section { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 10px; + padding: 10px 10px 0; + + .input { + color: light-dark(@dark, @beige); + } + } + } + &.limited { &.character, &.adversary, diff --git a/styles/less/sheets/actors/character/inventory.less b/styles/less/sheets/actors/character/inventory.less index b555aa3d..12f63753 100644 --- a/styles/less/sheets/actors/character/inventory.less +++ b/styles/less/sheets/actors/character/inventory.less @@ -3,47 +3,6 @@ .application.sheet.daggerheart.actor.dh-style.character { .tab.inventory { - .search-section { - display: flex; - gap: 10px; - align-items: center; - - .search-bar { - position: relative; - color: light-dark(@dark-blue-50, @beige-50); - width: 100%; - padding-top: 5px; - - input { - border-radius: 50px; - background: light-dark(@dark-blue-10, @golden-10); - border: none; - outline: 2px solid transparent; - transition: all 0.3s ease; - padding: 0 20px; - - &:hover { - outline: 2px solid light-dark(@dark, @golden); - } - - &::-webkit-search-cancel-button { - -webkit-appearance: none; - display: none; - } - } - - .icon { - align-content: center; - height: 32px; - position: absolute; - right: 20px; - font-size: var(--font-size-16); - z-index: 1; - color: light-dark(@dark-blue-50, @beige-50); - } - } - } - .items-section { display: flex; flex-direction: column; @@ -55,16 +14,5 @@ scrollbar-width: thin; scrollbar-color: light-dark(@dark-blue, @golden) transparent; } - - .currency-section { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; - gap: 10px; - padding: 10px 10px 0; - - .input { - color: light-dark(@dark, @beige); - } - } } } diff --git a/styles/less/sheets/actors/party/inventory.less b/styles/less/sheets/actors/party/inventory.less index 2dcc97d8..ac59e1de 100644 --- a/styles/less/sheets/actors/party/inventory.less +++ b/styles/less/sheets/actors/party/inventory.less @@ -3,51 +3,6 @@ .application.sheet.daggerheart.actor.dh-style.party { .tab.inventory { - .search-section { - display: flex; - gap: 10px; - align-items: center; - - .search-bar { - position: relative; - color: light-dark(@dark-blue-50, @beige-50); - width: 100%; - padding-top: 5px; - - input { - border-radius: 50px; - background: light-dark(@dark-blue-10, @golden-10); - border: none; - outline: 2px solid transparent; - transition: all 0.3s ease; - padding: 0 20px; - - &:hover { - outline: 2px solid light-dark(@dark, @golden); - } - - &:placeholder { - color: light-dark(@dark-blue-50, @beige-50); - } - - &::-webkit-search-cancel-button { - -webkit-appearance: none; - display: none; - } - } - - .icon { - align-content: center; - height: 32px; - position: absolute; - right: 20px; - font-size: 16px; - z-index: 1; - color: light-dark(@dark-blue-50, @beige-50); - } - } - } - .items-section { display: flex; flex-direction: column; @@ -59,16 +14,5 @@ scrollbar-width: thin; scrollbar-color: light-dark(@dark-blue, @golden) transparent; } - - .currency-section { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; - gap: 10px; - padding: 10px 10px 0; - - .input { - color: light-dark(@dark, @beige); - } - } } } diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index 711d0c9f..aad1cf7e 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -13,18 +13,7 @@
{{#if this.inventory.hasCurrency}} -
- {{#each this.inventory.currencies as |currency key|}} - {{#if currency.enabled}} -
- - {{localize currency.label}} - - -
- {{/if}} - {{/each}} -
+ {{> "systems/daggerheart/templates/sheets/global/partials/gold.hbs" currencies=inventory.currencies}} {{/if}}
diff --git a/templates/sheets/actors/party/inventory.hbs b/templates/sheets/actors/party/inventory.hbs index 186e2e99..8dd10154 100644 --- a/templates/sheets/actors/party/inventory.hbs +++ b/templates/sheets/actors/party/inventory.hbs @@ -16,18 +16,7 @@
{{#if inventory.hasCurrency}} -
- {{#each this.inventory.currencies as |currency key|}} - {{#if currency.enabled}} -
- - {{localize currency.label}} - - -
- {{/if}} - {{/each}} -
+ {{> "systems/daggerheart/templates/sheets/global/partials/gold.hbs" currencies=inventory.currencies}} {{/if}}
diff --git a/templates/sheets/global/partials/gold.hbs b/templates/sheets/global/partials/gold.hbs new file mode 100644 index 00000000..7aba2815 --- /dev/null +++ b/templates/sheets/global/partials/gold.hbs @@ -0,0 +1,12 @@ +
+ {{#each currencies as |currency key|}} + {{#if currency.enabled}} +
+ + {{localize currency.label}} + + +
+ {{/if}} + {{/each}} +
\ No newline at end of file From 8808e4646d0512e6f5d6f11100d9ec19df02835f Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 15 Apr 2026 18:47:20 +0200 Subject: [PATCH 39/54] Corrected use of Foundry's Reset translation --- module/applications/settings/appearanceSettings.mjs | 2 +- templates/dialogs/characterReset.hbs | 2 +- templates/settings/automation-settings/footer.hbs | 2 +- templates/settings/homebrew-settings/footer.hbs | 2 +- templates/settings/metagaming-settings/footer.hbs | 2 +- templates/settings/variant-rules.hbs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/module/applications/settings/appearanceSettings.mjs b/module/applications/settings/appearanceSettings.mjs index 64e5a076..9de9e752 100644 --- a/module/applications/settings/appearanceSettings.mjs +++ b/module/applications/settings/appearanceSettings.mjs @@ -122,7 +122,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', - label: game.i18n.localize('ACTIONS.Reset') + label: game.i18n.localize('SETTINGS.UI.ACTIONS.Reset') }, { type: 'submit', icon: 'fa-solid fa-floppy-disk', label: game.i18n.localize('EDITOR.Save') } ]; diff --git a/templates/dialogs/characterReset.hbs b/templates/dialogs/characterReset.hbs index 59f88437..0bd54f7c 100644 --- a/templates/dialogs/characterReset.hbs +++ b/templates/dialogs/characterReset.hbs @@ -28,6 +28,6 @@
- +
\ No newline at end of file diff --git a/templates/settings/automation-settings/footer.hbs b/templates/settings/automation-settings/footer.hbs index 7e9d1991..14ff5bb5 100644 --- a/templates/settings/automation-settings/footer.hbs +++ b/templates/settings/automation-settings/footer.hbs @@ -1,7 +1,7 @@
{{#if abilities}} From 16c07d23bbb50e7417b6d90c094e7f0ed397d195 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 16 Apr 2026 09:57:16 +0200 Subject: [PATCH 44/54] Changed diceFaces->dieFaces for consistency --- module/applications/dialogs/d20RollDialog.mjs | 2 +- templates/dialogs/dice-roll/rollSelection.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index d8317d70..067aa473 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -123,7 +123,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio context.advantage = this.config.roll?.advantage; context.disadvantage = this.config.roll?.disadvantage; context.diceOptions = CONFIG.DH.GENERAL.diceTypes; - context.diceFaces = CONFIG.DH.GENERAL.dieFaces.reduce((acc, face) => { + context.dieFaces = CONFIG.DH.GENERAL.dieFaces.reduce((acc, face) => { acc[face] = `d${face}`; return acc; }, {}); diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index c30b0786..2c1a21b6 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -158,7 +158,7 @@ {{/times}}
{{#if abilities}} From d9b322406d530a1ece39b25c7126ed9777ff0dfd Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 16 Apr 2026 04:23:55 -0400 Subject: [PATCH 45/54] Fix party sheet rerenders from member updates interfering with currency and note input (#1809) --- module/documents/actor.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 8ae2e062..4e20db0e 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -113,7 +113,7 @@ export default class DhpActor extends Actor { _onUpdate(changes, options, userId) { super._onUpdate(changes, options, userId); for (const party of this.parties) { - party.render(); + party.render({ parts: ['partyMembers'] }); } } @@ -132,7 +132,7 @@ export default class DhpActor extends Actor { _onDelete(options, userId) { super._onDelete(options, userId); for (const party of this.parties) { - party.render(); + party.render({ parts: ['partyMembers'] }); } } From 2fde61a1d5aac62d812b7e1e6862c565e952c515 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 16 Apr 2026 05:12:36 -0400 Subject: [PATCH 46/54] [Feature] Make all item types quantifiable in the party (#1808) * Show notification when invalid item types are added to actors * Make all item types quantifiable in the party actor * Remove from comment * Use isInventoryItem to set quantity * Fix formatting --- module/applications/dialogs/itemTransfer.mjs | 4 +- module/applications/sheets/api/base-actor.mjs | 98 ++++++++++++------- module/data/actor/character.mjs | 3 +- module/data/actor/party.mjs | 3 +- module/data/item/base.mjs | 4 +- module/data/item/consumable.mjs | 1 - module/data/item/loot.mjs | 1 - templates/dialogs/item-transfer.hbs | 2 +- .../sheets/actors/character/inventory.hbs | 2 + templates/sheets/actors/party/inventory.hbs | 4 + .../partials/inventory-fieldset-items-V2.hbs | 1 + .../global/partials/inventory-item-V2.hbs | 8 +- 12 files changed, 85 insertions(+), 46 deletions(-) diff --git a/module/applications/dialogs/itemTransfer.mjs b/module/applications/dialogs/itemTransfer.mjs index ad3cf103..42e3a727 100644 --- a/module/applications/dialogs/itemTransfer.mjs +++ b/module/applications/dialogs/itemTransfer.mjs @@ -38,13 +38,15 @@ export default class ItemTransferDialog extends HandlebarsApplicationMixin(Appli originActor ??= item?.actor; 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; return { originActor, targetActor, itemImage: item?.img, currencyIcon: currencySetting?.icon, - max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0, + max, + initial: targetActor.system.metadata.quantifiable?.includes(item.type) ? max : 1, title: item?.name ?? currencySetting?.label }; } diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index f009267e..e23a4426 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -298,47 +298,79 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { ); } - if (item.system.metadata.isQuantifiable) { - const actorItem = originActor.items.get(data.originId); - const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({ + // Perform the actual transfer, showing a dialog when doing it + const availableQuantity = Math.max(1, item.system.quantity); + const actorItem = originActor.items.get(data.originId) ?? item; + if (availableQuantity > 1) { + const quantityTransferred = await game.system.api.applications.dialogs.ItemTransferDialog.configure({ item, targetActor: this.document }); - - if (quantityTransfered) { - const existingItem = this.document.items.find(x => itemIsIdentical(x, item)); - if (existingItem) { - await existingItem.update({ - 'system.quantity': existingItem.system.quantity + quantityTransfered - }); - } else { - const createData = item.toObject(); - await this.document.createEmbeddedDocuments('Item', [ - { - ...createData, - system: { - ...createData.system, - quantity: quantityTransfered - } - } - ]); - } - - if (quantityTransfered === actorItem.system.quantity) { - await originActor.deleteEmbeddedDocuments('Item', [data.originId]); - } else { - await actorItem.update({ - 'system.quantity': actorItem.system.quantity - quantityTransfered - }); - } - } + return this.#transferItem(actorItem, quantityTransferred); } else { - await this.document.createEmbeddedDocuments('Item', [item.toObject()]); - await originActor.deleteEmbeddedDocuments('Item', [data.originId]); + return this.#transferItem(actorItem, availableQuantity); } } } + /** + * Helper to perform the actual transfer of an item to this actor, including stack/unstack logic based on target quantifiability. + * Make sure item is the actor item before calling this method or there will be issues + */ + async #transferItem(item, quantity) { + const originActor = item.actor; + const targetActor = this.document; + const allowStacking = targetActor.system.metadata.quantifiable?.includes(item.type); + + const batch = []; + + // First add/update the item to the target actor + const existing = allowStacking ? targetActor.items.find(x => itemIsIdentical(x, item)) : null; + if (existing) { + batch.push({ + action: 'update', + documentName: 'Item', + parent: targetActor, + updates: [{ '_id': existing.id, 'system.quantity': existing.system.quantity + quantity }] + }); + } else { + const itemsToCreate = []; + if (allowStacking) { + itemsToCreate.push(foundry.utils.mergeObject(item.toObject(true), { system: { quantity } })); + } else { + const createData = new Array(Math.max(1, quantity)) + .fill(0) + .map(() => foundry.utils.mergeObject(item.toObject(), { system: { quantity: 1 } })); + itemsToCreate.push(...createData); + } + batch.push({ + action: 'create', + documentName: 'Item', + parent: targetActor, + data: itemsToCreate + }); + } + + // Remove the item from the original actor (by either deleting it, or updating its quantity) + if (quantity >= item.system.quantity) { + batch.push({ + action: 'delete', + documentName: 'Item', + parent: originActor, + ids: [item.id] + }); + } else { + batch.push({ + action: 'update', + documentName: 'Item', + parent: originActor, + updates: [{ '_id': item.id, 'system.quantity': item.system.quantity - quantity }] + }); + } + + return foundry.documents.modifyBatch(batch); + } + /** * On dragStart on the item. * @param {DragEvent} event - The drag event diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index e6bcd294..bf3d7bb8 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -19,7 +19,8 @@ export default class DhCharacter extends DhCreature { type: 'character', settingSheet: DHCharacterSettings, isNPC: false, - hasInventory: true + hasInventory: true, + quantifiable: ["loot", "consumable"] }); } diff --git a/module/data/actor/party.mjs b/module/data/actor/party.mjs index 46635237..6ccf8852 100644 --- a/module/data/actor/party.mjs +++ b/module/data/actor/party.mjs @@ -8,7 +8,8 @@ export default class DhParty extends BaseDataActor { /** @inheritdoc */ static get metadata() { return foundry.utils.mergeObject(super.metadata, { - hasInventory: true + hasInventory: true, + quantifiable: ["weapon", "armor", "loot", "consumable"] }); } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 21a11149..72718c5e 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -4,7 +4,6 @@ * @property {string} label - A localizable label used on application. * @property {string} type - The system type that this data model represents. * @property {boolean} hasDescription - Indicates whether items of this type have description field - * @property {boolean} isQuantifiable - Indicates whether items of this type have quantity field * @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item */ @@ -24,7 +23,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { type: 'base', hasDescription: false, hasResource: false, - isQuantifiable: false, isInventoryItem: false, hasActions: false, hasAttribution: true @@ -83,7 +81,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { ); } - if (this.metadata.isQuantifiable) + if (this.metadata.isInventoryItem) schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true }); if (this.metadata.hasActions) schema.actions = new ActionsField(); diff --git a/module/data/item/consumable.mjs b/module/data/item/consumable.mjs index ab527967..e83a1a53 100644 --- a/module/data/item/consumable.mjs +++ b/module/data/item/consumable.mjs @@ -7,7 +7,6 @@ export default class DHConsumable extends BaseDataItem { label: 'TYPES.Item.consumable', type: 'consumable', hasDescription: true, - isQuantifiable: true, isInventoryItem: true, hasActions: true }); diff --git a/module/data/item/loot.mjs b/module/data/item/loot.mjs index cdb0855e..d4092934 100644 --- a/module/data/item/loot.mjs +++ b/module/data/item/loot.mjs @@ -7,7 +7,6 @@ export default class DHLoot extends BaseDataItem { label: 'TYPES.Item.loot', type: 'loot', hasDescription: true, - isQuantifiable: true, isInventoryItem: true, hasActions: true }); diff --git a/templates/dialogs/item-transfer.hbs b/templates/dialogs/item-transfer.hbs index 0e7df3dc..63972ed8 100644 --- a/templates/dialogs/item-transfer.hbs +++ b/templates/dialogs/item-transfer.hbs @@ -14,7 +14,7 @@
- +
diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index aad1cf7e..30812e17 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -39,6 +39,7 @@ collection=@root.inventory.consumables isGlassy=true canCreate=true + isQuantifiable=true }} {{> 'daggerheart.inventory-items' title='TYPES.Item.loot' @@ -47,6 +48,7 @@ isGlassy=true canCreate=true showActions=true + isQuantifiable=true }}
\ No newline at end of file diff --git a/templates/sheets/actors/party/inventory.hbs b/templates/sheets/actors/party/inventory.hbs index 8dd10154..74492c73 100644 --- a/templates/sheets/actors/party/inventory.hbs +++ b/templates/sheets/actors/party/inventory.hbs @@ -29,6 +29,7 @@ canCreate=true hideResources=true hideContextMenu=true + isQuantifiable=true }} {{> 'daggerheart.inventory-items' title='TYPES.Item.armor' @@ -39,6 +40,7 @@ canCreate=true hideResources=true hideContextMenu=true + isQuantifiable=true }} {{> 'daggerheart.inventory-items' title='TYPES.Item.consumable' @@ -48,6 +50,7 @@ isGlassy=true canCreate=true hideContextMenu=true + isQuantifiable=true }} {{> 'daggerheart.inventory-items' title='TYPES.Item.loot' @@ -57,6 +60,7 @@ isGlassy=true canCreate=true hideContextMenu=true + isQuantifiable=true }}
\ No newline at end of file diff --git a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs index 31c8f7f5..5c6eae32 100644 --- a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs +++ b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs @@ -66,6 +66,7 @@ Parameters: showLabels=../showLabels isAction=../isAction hideResources=../hideResources + isQuantifiable=../isQuantifiable showActions=../showActions }} diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index 2129b969..25f1715c 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -63,10 +63,10 @@ Parameters: {{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}} {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} {{/if}} - {{#if (and (not hideResources) (gte item.system.quantity 0))}} -
- -
+ {{#if (or isQuantifiable (or (eq item.system.quantity 0) (gt item.system.quantity 1)))}} +
+ +
{{/if}} {{!-- Controls --}} From 4b92001f97e9dbaf933054ab9df27db65b9b5870 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 17 Apr 2026 13:29:37 -0400 Subject: [PATCH 47/54] Fix issues with party sheet resources in light mode (#1810) --- styles/less/sheets/actors/party/party-members.less | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index 155fcc36..2e2f4cf8 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -35,8 +35,8 @@ body.game:is(.performance-low, .noblur) { .actor-img-frame { grid-area: img; - width: 7.5rem; - height: 7.5rem; + width: 7.375rem; + height: 7.375rem; position: relative; .actor-img { @@ -71,6 +71,7 @@ body.game:is(.performance-low, .noblur) { height: 1.75rem; background: url('../assets/svg/trait-shield.svg') no-repeat; background-size: 100%; + color: var(--color-light-1); font-size: var(--font-size-14); font-weight: 700; display: flex; @@ -87,7 +88,7 @@ body.game:is(.performance-low, .noblur) { display: flex; gap: 4px; - background-color: light-dark(transparent, @dark-blue); + background-color: light-dark(var(--color-light-1), @dark-blue); color: light-dark(@dark-blue, @golden); padding: 4px 6px; border: 1px solid light-dark(@dark-blue, @golden); @@ -125,7 +126,7 @@ body.game:is(.performance-low, .noblur) { width: 100%; z-index: 1; font-size: var(--font-size-20); - color: light-dark(@beige, @golden); + color: light-dark(@dark-blue, @golden); font-weight: bold; } From 4944722139a1f0c6ca33058374b0671f133e2a70 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 18 Apr 2026 17:31:56 -0400 Subject: [PATCH 48/54] Avoid error when backing out of action (#1813) --- module/data/action/attackAction.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index a2d47309..5e93d70b 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -51,7 +51,7 @@ export default class DHAttackAction extends DHDamageAction { async use(event, options) { const result = await super.use(event, options); - if (result.message?.system.action.roll?.type === 'attack') { + if (result?.message?.system.action.roll?.type === 'attack') { const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id); } From 1fea8438ba3e9b1644e89756252ffb9d2c2fe432 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 19 Apr 2026 11:29:47 +0200 Subject: [PATCH 49/54] Fixed DowntimeMove actions not opening --- templates/settings/downtime-config/actions.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/settings/downtime-config/actions.hbs b/templates/settings/downtime-config/actions.hbs index d197f983..feb05302 100644 --- a/templates/settings/downtime-config/actions.hbs +++ b/templates/settings/downtime-config/actions.hbs @@ -8,7 +8,7 @@
{{#each move.actions as |action|}} - {{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" id=action.id }} + {{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" id=action.id type="action" }} {{/each}}
From 03110377e17ab134937e849ea3834fa488e49afd Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 20 Apr 2026 00:04:03 +0200 Subject: [PATCH 50/54] Fixed so that resource reset on downtime can handle math expressions --- module/applications/dialogs/downtime.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 3475dee7..989e4625 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -259,8 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV const resetValue = increasing ? 0 : feature.system.resource.max - ? Roll.replaceFormulaData(feature.system.resource.max, this.actor) + ? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor)).evaluateSync().total : 0; + await feature.update({ 'system.resource.value': resetValue }); } From fa04c9920f75efaeb13f94123434ad797c7f1141 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 20 Apr 2026 02:11:17 -0400 Subject: [PATCH 51/54] Fix translation string (#1817) --- templates/sheets/items/weapon/settings.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/sheets/items/weapon/settings.hbs b/templates/sheets/items/weapon/settings.hbs index e67e8dd7..ef47b323 100644 --- a/templates/sheets/items/weapon/settings.hbs +++ b/templates/sheets/items/weapon/settings.hbs @@ -7,7 +7,7 @@ {{localize tabs.settings.label}} {{localize "DAGGERHEART.GENERAL.Tiers.singular"}} {{formInput systemFields.tier value=source.system.tier}} - {{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}} + {{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full"}} {{formInput systemFields.secondary value=source.system.secondary}} {{localize "DAGGERHEART.GENERAL.Trait.single"}} {{formInput systemFields.attack.fields.roll.fields.trait value=document.system.attack.roll.trait name="system.attack.roll.trait" label="DAGGERHEART.GENERAL.Trait.single" localize=true}} From c683bc4352bfb5d07914b87a9bbe27b9eeb0748c Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 20 Apr 2026 15:20:35 +0200 Subject: [PATCH 52/54] Fixed IncludeBaseDamage to be an override --- lang/en.json | 6 ++- module/data/action/attackAction.mjs | 2 +- templates/actionTypes/damage.hbs | 70 +++++++++++++++-------------- templates/actionTypes/roll.hbs | 4 +- 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/lang/en.json b/lang/en.json index 7b8b45c7..48a59307 100755 --- a/lang/en.json +++ b/lang/en.json @@ -113,7 +113,9 @@ "deleteTriggerTitle": "Delete Trigger", "deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?", "advantageState": "Advantage State", - "damageOnSave": "Damage on Save" + "damageOnSave": "Damage on Save", + "useDefaultItemValues": "Use default Item values", + "itemDamageIsUsed": "Item Damage Is Used" }, "RollField": { "diceRolling": { @@ -128,7 +130,7 @@ "attackModifier": "Attack Modifier", "attackName": "Attack Name", "criticalThreshold": "Critical Threshold", - "includeBase": { "label": "Include Item Damage" }, + "includeBase": { "label": "Use Item Damage" }, "groupAttack": { "label": "Group Attack" }, "multiplier": "Multiplier", "saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.", diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index 5e93d70b..f54ea282 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -13,7 +13,7 @@ export default class DHAttackAction extends DHDamageAction { if (!!this.item?.system?.attack) { if (this.damage.includeBase) { const baseDamage = this.getParentDamage(); - this.damage.parts.unshift(new DHDamageData(baseDamage)); + this.damage.parts.hitPoints = new DHDamageData(baseDamage); } if (this.roll.useDefault) { this.roll.trait = this.item.system.attack.roll.trait; diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 192c5be5..454d0413 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -21,7 +21,7 @@
{{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}} - {{#each source.parts as |dmg|}} + {{#each source.parts as |dmg key|}}
@@ -31,40 +31,44 @@ {{/unless}} - {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} - {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}} - {{/if}} - {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}} -
-
- {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}} - {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} -
-
- {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}} - {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path}} -
-
- {{else}} - {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} - {{/if}} - - {{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}} - {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." dmg.applyTo ".type") localize=true}} - {{/if}} - - {{#if ../horde}} -
- {{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}} + {{#unless (and @root.source.damage.includeBase (eq key 'hitPoints'))}} + {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} + {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}} + {{/if}} + {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
- - {{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} - {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.dice") classes="inline-child" localize=true}} - {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.bonus") localize=true classes="inline-child"}} +
+ {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} +
+
+ {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}} + {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path}} +
-
- {{/if}} - + {{else}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} + {{/if}} + + {{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}} + {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." dmg.applyTo ".type") localize=true}} + {{/if}} + + {{#if ../horde}} +
+ {{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}} +
+ + {{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} + {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.dice") classes="inline-child" localize=true}} + {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.bonus") localize=true classes="inline-child"}} +
+
+ {{/if}} + + {{else}} + {{localize "DAGGERHEART.ACTIONS.Config.itemDamageIsUsed"}} + {{/unless}}
{{/each}} diff --git a/templates/actionTypes/roll.hbs b/templates/actionTypes/roll.hbs index 9784fc08..41f88ba2 100644 --- a/templates/actionTypes/roll.hbs +++ b/templates/actionTypes/roll.hbs @@ -1,7 +1,7 @@
- Roll - {{#if @root.hasBaseDamage}}{{formInput fields.useDefault name="roll.useDefault" value=source.useDefault dataset=(object tooltip="Use default Item values" tooltipDirection="UP")}}{{/if}} + {{localize "DAGGERHEART.GENERAL.roll"}} + {{#if @root.hasBaseDamage}}{{formInput fields.useDefault name="roll.useDefault" value=source.useDefault dataset=(object tooltip=(localize "DAGGERHEART.ACTIONS.Config.useDefaultItemValues") tooltipDirection="UP")}}{{/if}} {{formField fields.type label="DAGGERHEART.GENERAL.type" name="roll.type" value=source.type localize=true choices=@root.getRollTypeOptions localize=true}} From f850cbda76ac741e9ba812fdf4dc58a6bf29b378 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 20 Apr 2026 09:30:43 -0400 Subject: [PATCH 53/54] [Feature] Updates to inventory icons, context menus, action use, and observer visibility (#1814) * Update inventory controls and permissions filtering * Also disable prosemirror editors * Address context menu deprecation warnings * Fix context menu detection for actions * Refine logic for use action when hovering over item icon --- lang/en.json | 1 + .../applications/sheets/actors/character.mjs | 50 +++++--- .../sheets/api/application-mixin.mjs | 36 +++--- .../sidebar/tabs/actorDirectory.mjs | 8 +- module/applications/ui/chatLog.mjs | 17 +-- module/applications/ui/combatTracker.mjs | 8 +- module/data/action/baseAction.mjs | 11 ++ module/data/item/base.mjs | 2 + module/data/item/weapon.mjs | 4 +- module/documents/item.mjs | 7 + .../less/sheets/actors/character/sheet.less | 15 --- .../less/sheets/actors/character/sidebar.less | 2 +- templates/sheets/actors/adversary/effects.hbs | 4 +- .../sheets/actors/adversary/features.hbs | 4 +- templates/sheets/actors/character/effects.hbs | 4 +- .../sheets/actors/character/features.hbs | 6 +- templates/sheets/actors/character/header.hbs | 48 +++---- .../sheets/actors/character/inventory.hbs | 10 +- templates/sheets/actors/character/loadout.hbs | 4 +- templates/sheets/actors/character/sidebar.hbs | 8 +- templates/sheets/actors/companion/effects.hbs | 4 +- .../sheets/actors/environment/features.hbs | 4 +- templates/sheets/actors/party/inventory.hbs | 8 +- .../partials/inventory-fieldset-items-V2.hbs | 3 +- .../global/partials/inventory-item-V2.hbs | 121 +++++++++--------- .../partials/inventory-item-compact.hbs | 42 +++--- templates/sheets/global/tabs/tab-actions.hbs | 2 +- templates/sheets/global/tabs/tab-effects.hbs | 4 +- 28 files changed, 222 insertions(+), 215 deletions(-) diff --git a/lang/en.json b/lang/en.json index 48a59307..a824d379 100755 --- a/lang/en.json +++ b/lang/en.json @@ -3235,6 +3235,7 @@ "Tooltip": { "disableEffect": "Disable Effect", "enableEffect": "Enable Effect", + "edit": "Edit", "openItemWorld": "Open Item World", "openActorWorld": "Open Actor World", "sendToChat": "Send to Chat", diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index f2686fdd..3b029771 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -12,8 +12,6 @@ export default class CharacterSheet extends DHBaseActorSheet { static DEFAULT_OPTIONS = { classes: ['character'], position: { width: 850, height: 800 }, - /* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */ - editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER, actions: { toggleVault: CharacterSheet.#toggleVault, rollAttribute: CharacterSheet.#rollAttribute, @@ -68,7 +66,7 @@ export default class CharacterSheet extends DHBaseActorSheet { } }, { - handler: CharacterSheet.#getEquipamentContextOptions, + handler: CharacterSheet.#getEquipmentContextOptions, selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]', options: { parentClassHooks: false, @@ -170,6 +168,16 @@ export default class CharacterSheet extends DHBaseActorSheet { return applicationOptions; } + /** @inheritdoc */ + _toggleDisabled(disabled) { + // Overriden to only disable text inputs by default. + // Everything else is done by checking @root.editable in the sheet + const form = this.form; + for (const input of form.querySelectorAll("input:not([type=search]), .editor.prosemirror")) { + input.disabled = disabled; + } + } + /** @inheritDoc */ async _onRender(context, options) { await super._onRender(context, options); @@ -315,11 +323,11 @@ export default class CharacterSheet extends DHBaseActorSheet { /**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */ const options = [ { - name: 'toLoadout', + label: 'toLoadout', icon: 'fa-solid fa-arrow-up', - condition: target => { + visible: target => { const doc = getDocFromElementSync(target); - return doc && doc.system.inVault; + return doc?.isOwner && doc.system.inVault; }, callback: async target => { const doc = await getDocFromElement(target); @@ -329,11 +337,11 @@ export default class CharacterSheet extends DHBaseActorSheet { } }, { - name: 'recall', + label: 'recall', icon: 'fa-solid fa-bolt-lightning', - condition: target => { + visible: target => { const doc = getDocFromElementSync(target); - return doc && doc.system.inVault; + return doc?.isOwner && doc.system.inVault; }, callback: async (target, event) => { const doc = await getDocFromElement(target); @@ -368,17 +376,17 @@ export default class CharacterSheet extends DHBaseActorSheet { } }, { - name: 'toVault', + label: 'toVault', icon: 'fa-solid fa-arrow-down', - condition: target => { + visible: target => { const doc = getDocFromElementSync(target); - return doc && !doc.system.inVault; + return doc?.isOwner && !doc.system.inVault; }, callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true }) } ].map(option => ({ ...option, - name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`, + label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`, icon: `` })); @@ -391,29 +399,29 @@ export default class CharacterSheet extends DHBaseActorSheet { * @this {CharacterSheet} * @protected */ - static #getEquipamentContextOptions() { + static #getEquipmentContextOptions() { const options = [ { - name: 'equip', + label: 'equip', icon: 'fa-solid fa-hands', - condition: target => { + visible: target => { const doc = getDocFromElementSync(target); - return doc && !doc.system.equipped; + return doc.isOwner && doc && !doc.system.equipped; }, callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target) }, { - name: 'unequip', + label: 'unequip', icon: 'fa-solid fa-hands', - condition: target => { + visible: target => { const doc = getDocFromElementSync(target); - return doc && doc.system.equipped; + return doc.isOwner && doc && doc.system.equipped; }, callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target) } ].map(option => ({ ...option, - name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`, + label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`, icon: `` })); diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index e93ce774..e0110ae3 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -418,18 +418,18 @@ export default function DHApplicationMixin(Base) { /**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */ const options = [ { - name: 'disableEffect', + label: 'disableEffect', icon: 'fa-solid fa-lightbulb', - condition: element => { + visible: element => { const target = element.closest('[data-item-uuid]'); return !target.dataset.disabled && target.dataset.itemType !== 'beastform'; }, callback: async target => (await getDocFromElement(target)).update({ disabled: true }) }, { - name: 'enableEffect', + label: 'enableEffect', icon: 'fa-regular fa-lightbulb', - condition: element => { + visible: element => { const target = element.closest('[data-item-uuid]'); return target.dataset.disabled && target.dataset.itemType !== 'beastform'; }, @@ -437,7 +437,7 @@ export default function DHApplicationMixin(Base) { } ].map(option => ({ ...option, - name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`, + label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`, icon: `` })); @@ -468,14 +468,14 @@ export default function DHApplicationMixin(Base) { _getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) { const options = [ { - name: 'CONTROLS.CommonEdit', + label: 'CONTROLS.CommonEdit', icon: 'fa-solid fa-pen-to-square', - condition: target => { + visible: target => { const { dataset } = target.closest('[data-item-uuid]'); const doc = getDocFromElementSync(target); return ( (!dataset.noCompendiumEdit && !doc) || - (doc && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection)) + (doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection)) ); }, callback: async target => (await getDocFromElement(target)).sheet.render({ force: true }) @@ -484,11 +484,12 @@ export default function DHApplicationMixin(Base) { if (usable) { options.unshift({ - name: 'DAGGERHEART.GENERAL.damage', + label: 'DAGGERHEART.GENERAL.damage', icon: 'fa-solid fa-explosion', - condition: target => { + visible: target => { const doc = getDocFromElementSync(target); return ( + doc?.isOwner && !foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) || !foundry.utils.isEmpty(doc?.damage?.parts) ); @@ -507,11 +508,11 @@ export default function DHApplicationMixin(Base) { }); options.unshift({ - name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem', + label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem', icon: 'fa-solid fa-burst', - condition: target => { + visible: target => { const doc = getDocFromElementSync(target); - return doc && !(doc.type === 'domainCard' && doc.system.inVault); + return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault); }, callback: async (target, event) => (await getDocFromElement(target)).use(event) }); @@ -519,18 +520,19 @@ export default function DHApplicationMixin(Base) { if (toChat) options.push({ - name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat', + label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat', icon: 'fa-solid fa-message', callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid) }); if (deletable) options.push({ - name: 'CONTROLS.CommonDelete', + label: 'CONTROLS.CommonDelete', icon: 'fa-solid fa-trash', - condition: element => { + visible: element => { const target = element.closest('[data-item-uuid]'); - return target.dataset.itemType !== 'beastform'; + const doc = getDocFromElementSync(target); + return doc?.isOwner && target.dataset.itemType !== 'beastform'; }, callback: async (target, event) => { const doc = await getDocFromElement(target); diff --git a/module/applications/sidebar/tabs/actorDirectory.mjs b/module/applications/sidebar/tabs/actorDirectory.mjs index 1306de61..89da1426 100644 --- a/module/applications/sidebar/tabs/actorDirectory.mjs +++ b/module/applications/sidebar/tabs/actorDirectory.mjs @@ -48,9 +48,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs. const options = super._getEntryContextOptions(); options.push( { - name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier', + label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier', icon: ``, - condition: li => { + visible: li => { const actor = game.actors.get(li.dataset.entryId); return actor?.type === 'adversary' && actor.system.type !== 'social'; }, @@ -92,9 +92,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs. } }, { - name: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty', + label: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty', icon: ``, - condition: li => { + visible: li => { const actor = game.actors.get(li.dataset.entryId); return actor && actor.type === 'party' && !actor.system.active; }, diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 59939963..34b25591 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -103,23 +103,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo _getEntryContextOptions() { return [ ...super._getEntryContextOptions(), - // { - // name: 'Reroll', - // icon: '', - // condition: li => { - // const message = game.messages.get(li.dataset.messageId); - - // return (game.user.isGM || message.isAuthor) && message.rolls.length > 0; - // }, - // callback: li => { - // const message = game.messages.get(li.dataset.messageId); - // new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true }); - // } - // }, { - name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'), + label: 'DAGGERHEART.UI.ChatLog.rerollDamage', icon: '', - condition: li => { + visible: li => { const message = game.messages.get(li.dataset.messageId); const hasRolledDamage = message.system.hasDamage ? Object.keys(message.system.damage).length > 0 diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index 1043e128..fb19a17e 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -84,15 +84,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C _getCombatContextOptions() { return [ { - name: 'COMBAT.ClearMovementHistories', + label: 'COMBAT.ClearMovementHistories', icon: '', - condition: () => game.user.isGM && this.viewed?.combatants.size > 0, + visible: () => game.user.isGM && this.viewed?.combatants.size > 0, callback: () => this.viewed.clearMovementHistories() }, { - name: 'COMBAT.Delete', + label: 'COMBAT.Delete', icon: '', - condition: () => game.user.isGM && !!this.viewed, + visible: () => game.user.isGM && !!this.viewed, callback: () => this.viewed.endCombat() } ]; diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 0992350b..f4835f34 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -110,6 +110,11 @@ 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 */ + get isOwner() { + return this.item?.isOwner ?? true; + } + /** * Return Item the action is attached too. */ @@ -143,6 +148,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel : null; } + /** Returns true if the action is usable */ + get usable() { + const actor = this.actor; + return this.isOwner && actor?.type === 'character'; + } + static getRollType(parent) { return 'trait'; } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 72718c5e..cc198dc4 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -108,6 +108,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { } get actionsList() { + // No actions on non-characters + if (this.actor && this.actor.type !== 'character') return []; return this.actions; } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 9335037c..75e6dc8e 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -99,7 +99,9 @@ export default class DHWeapon extends AttachableItem { /* -------------------------------------------- */ get actionsList() { - return [this.attack, ...this.actions]; + // No actions on non-characters + if (this.actor && this.actor.type !== 'character') return []; + return [this.attack, ...super.actionsList]; } get customActions() { diff --git a/module/documents/item.mjs b/module/documents/item.mjs index a8b41b05..8ece56fa 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -76,6 +76,13 @@ export default class DHItem extends foundry.documents.Item { return this.system.metadata.isInventoryItem ?? false; } + /** Returns true if the item can be used */ + get usable() { + const actor = this.actor; + const actionsList = this.system.actionsList; + return this.isOwner && actor?.type === 'character' && (actionsList?.size || actionsList?.length); + } + /** @inheritdoc */ static async createDialog(data = {}, createOptions = {}, options = {}) { const { folders, types, template, context = {}, ...dialogOptions } = options; diff --git a/styles/less/sheets/actors/character/sheet.less b/styles/less/sheets/actors/character/sheet.less index ee6580fd..68792c99 100644 --- a/styles/less/sheets/actors/character/sheet.less +++ b/styles/less/sheets/actors/character/sheet.less @@ -11,21 +11,6 @@ padding-bottom: 0; overflow-x: auto; - &.viewMode { - button:not(.btn-toggle-view), - input:not(.search), - .controls, - .character-sidebar-sheet, - .img-portait, - .name-row, - .hope-section, - .downtime-section, - .character-traits, - .card-list { - pointer-events: none; - } - } - .character-sidebar-sheet { grid-row: 1 / span 2; grid-column: 1; diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index e7027163..b159a8e8 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -316,9 +316,9 @@ border-radius: 3px; background: light-dark(@dark-blue, @golden); clip-path: none; - cursor: pointer; display: flex; align-items: center; + justify-content: center; gap: 4px; border: 1px solid transparent; transition: all 0.3s ease; diff --git a/templates/sheets/actors/adversary/effects.hbs b/templates/sheets/actors/adversary/effects.hbs index cefb6e57..087e8b30 100644 --- a/templates/sheets/actors/adversary/effects.hbs +++ b/templates/sheets/actors/adversary/effects.hbs @@ -6,7 +6,7 @@ type='effect' isGlassy=true collection=effects.actives - canCreate=true + canCreate=@root.editable hideResources=true }} @@ -15,7 +15,7 @@ type='effect' isGlassy=true collection=effects.inactives - canCreate=true + canCreate=@root.editable hideResources=true }} diff --git a/templates/sheets/actors/adversary/features.hbs b/templates/sheets/actors/adversary/features.hbs index a24342fc..d320b0d8 100644 --- a/templates/sheets/actors/adversary/features.hbs +++ b/templates/sheets/actors/adversary/features.hbs @@ -6,8 +6,8 @@ type='feature' collection=@root.features hideContextMenu=true - canCreate=true - showActions=true + canCreate=@root.editable + showActions=@root.editable }} \ No newline at end of file diff --git a/templates/sheets/actors/character/effects.hbs b/templates/sheets/actors/character/effects.hbs index 3355d575..a2e5a420 100644 --- a/templates/sheets/actors/character/effects.hbs +++ b/templates/sheets/actors/character/effects.hbs @@ -7,7 +7,7 @@ type='effect' isGlassy=true collection=effects.actives - canCreate=true + canCreate=@root.editable hideResources=true }} @@ -16,7 +16,7 @@ type='effect' isGlassy=true collection=effects.inactives - canCreate=true + canCreate=@root.editable hideResources=true disabled=true }} diff --git a/templates/sheets/actors/character/features.hbs b/templates/sheets/actors/character/features.hbs index 3e942468..70544683 100644 --- a/templates/sheets/actors/character/features.hbs +++ b/templates/sheets/actors/character/features.hbs @@ -8,8 +8,8 @@ type='feature' actorType='character' collection=category.values - canCreate=true - showActions=true + canCreate=@root.editable + showActions=@root.editable }} {{else if category.values}} {{> 'daggerheart.inventory-items' @@ -18,7 +18,7 @@ actorType='character' collection=category.values canCreate=false - showActions=true + showActions=@root.editable }} {{/if}} diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs index 4ceba54d..a75b2c2f 100644 --- a/templates/sheets/actors/character/header.hbs +++ b/templates/sheets/actors/character/header.hbs @@ -4,22 +4,24 @@

{{source.name}}

- {{#if document.system.needsCharacterSetup}} - - {{else if document.system.levelData.canLevelUp}} - + {{#if @root.editable}} + {{#if document.system.needsCharacterSetup}} + + {{else if document.system.levelData.canLevelUp}} + + {{/if}} {{/if}} {{#unless document.system.needsCharacterSetup}} {{localize 'DAGGERHEART.GENERAL.level'}} @@ -110,12 +112,14 @@ {{/if}} - - + {{#if @root.editable}} + + + {{/if}}

diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index 30812e17..c3ddb0ad 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -22,7 +22,7 @@ type='weapon' collection=@root.inventory.weapons isGlassy=true - canCreate=true + canCreate=@root.editable hideResources=true }} {{> 'daggerheart.inventory-items' @@ -30,7 +30,7 @@ type='armor' collection=@root.inventory.armor isGlassy=true - canCreate=true + canCreate=@root.editable hideResources=true }} {{> 'daggerheart.inventory-items' @@ -38,7 +38,7 @@ type='consumable' collection=@root.inventory.consumables isGlassy=true - canCreate=true + canCreate=@root.editable isQuantifiable=true }} {{> 'daggerheart.inventory-items' @@ -46,8 +46,8 @@ type='loot' collection=@root.inventory.loot isGlassy=true - canCreate=true - showActions=true + canCreate=@root.editable + showActions=@root.editable isQuantifiable=true }} diff --git a/templates/sheets/actors/character/loadout.hbs b/templates/sheets/actors/character/loadout.hbs index 5e4c9f54..9ba3fb04 100644 --- a/templates/sheets/actors/character/loadout.hbs +++ b/templates/sheets/actors/character/loadout.hbs @@ -27,7 +27,7 @@ isGlassy=true cardView=cardView collection=document.system.domainCards.loadout - canCreate=true + canCreate=@root.editable }} {{> 'daggerheart.inventory-items' title='DAGGERHEART.GENERAL.Tabs.vault' @@ -35,7 +35,7 @@ isGlassy=true cardView=cardView collection=document.system.domainCards.vault - canCreate=true + canCreate=@root.editable inVault=true }} diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index d3be4983..0142ac1d 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -45,11 +45,11 @@ {{/times}} - + {{localize "DAGGERHEART.GENERAL.armorSlots"}}
{{document.system.armorScore.value}} / {{document.system.armorScore.max}} - + {{#if @root.editable}}{{/if}}
@@ -64,9 +64,9 @@ value='{{document.system.armorScore.value}}' max='{{document.system.armorScore.max}}' > - +

{{localize "DAGGERHEART.GENERAL.armorSlots"}}

- + {{#if @root.editable}}{{/if}}
{{/if}} diff --git a/templates/sheets/actors/companion/effects.hbs b/templates/sheets/actors/companion/effects.hbs index cefb6e57..087e8b30 100644 --- a/templates/sheets/actors/companion/effects.hbs +++ b/templates/sheets/actors/companion/effects.hbs @@ -6,7 +6,7 @@ type='effect' isGlassy=true collection=effects.actives - canCreate=true + canCreate=@root.editable hideResources=true }} @@ -15,7 +15,7 @@ type='effect' isGlassy=true collection=effects.inactives - canCreate=true + canCreate=@root.editable hideResources=true }} diff --git a/templates/sheets/actors/environment/features.hbs b/templates/sheets/actors/environment/features.hbs index 3ad36023..3fd512da 100644 --- a/templates/sheets/actors/environment/features.hbs +++ b/templates/sheets/actors/environment/features.hbs @@ -9,8 +9,8 @@ type='feature' collection=@root.features hideContextMenu=true - canCreate=true - showActions=true + canCreate=@root.editable + showActions=@root.editable }} \ No newline at end of file diff --git a/templates/sheets/actors/party/inventory.hbs b/templates/sheets/actors/party/inventory.hbs index 74492c73..cf056608 100644 --- a/templates/sheets/actors/party/inventory.hbs +++ b/templates/sheets/actors/party/inventory.hbs @@ -26,7 +26,7 @@ actorType='party' collection=@root.inventory.weapons isGlassy=true - canCreate=true + canCreate=@root.editable hideResources=true hideContextMenu=true isQuantifiable=true @@ -37,7 +37,7 @@ actorType='party' collection=@root.inventory.armor isGlassy=true - canCreate=true + canCreate=@root.editable hideResources=true hideContextMenu=true isQuantifiable=true @@ -48,7 +48,7 @@ actorType='party' collection=@root.inventory.consumables isGlassy=true - canCreate=true + canCreate=@root.editable hideContextMenu=true isQuantifiable=true }} @@ -58,7 +58,7 @@ actorType='party' collection=@root.inventory.loot isGlassy=true - canCreate=true + canCreate=@root.editable hideContextMenu=true isQuantifiable=true }} diff --git a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs index 5c6eae32..3f58b80b 100644 --- a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs +++ b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs @@ -52,12 +52,11 @@ Parameters: {{else}}