From 6b6f4e61d600162aa423500f0577bf73ce174a35 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 15 Jul 2025 03:45:16 +0200 Subject: [PATCH 01/22] Added back evasion property setting (#339) --- module/data/actor/character.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 3ee7c980..e8322671 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -323,6 +323,8 @@ export default class DhCharacter extends BaseDataActor { } prepareBaseData() { + this.evasion = this.class.value?.system?.evasion ?? 0; + const currentLevel = this.levelData.level.current; const currentTier = currentLevel === 1 From ef45cd71f8f1c053eb8e158c5f3cc5cc364b1161 Mon Sep 17 00:00:00 2001 From: Psitacus <59754077+Psitacus@users.noreply.github.com> Date: Tue, 15 Jul 2025 04:03:12 -0600 Subject: [PATCH 02/22] Iss337 - Allow armor slots to be edited from the character sheet (#345) * add armor slot input * working progress bar * fix label styling * fix more styling * fix styling issue on firefix * Restored armorSlots when empty and added localization --------- Co-authored-by: psitacus Co-authored-by: WBHarry --- lang/en.json | 1 + .../applications/sheets/actors/character.mjs | 15 ++ .../less/sheets/actors/character/sidebar.less | 135 +++++++++++++++++- templates/sheets/actors/character/sidebar.hbs | 34 +++-- 4 files changed, 172 insertions(+), 13 deletions(-) diff --git a/lang/en.json b/lang/en.json index 96f5a9dd..a2d4bfd8 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1138,6 +1138,7 @@ "plural": "Traits" }, "activeEffects": "Active Effects", + "armorSlots": "Armor Slots", "attack": "Attack", "basics": "Basics", "bonus": "Bonus", diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index eae7a223..99545d4e 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -103,6 +103,11 @@ export default class CharacterSheet extends DHBaseActorSheet { htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => { element.addEventListener('change', this.updateItemQuantity.bind(this)); }); + + // Add listener for armor marks input + htmlElement.querySelectorAll('.armor-marks-input').forEach(element => { + element.addEventListener('change', this.updateArmorMarks.bind(this)); + }); } /** @inheritDoc */ @@ -515,6 +520,16 @@ export default class CharacterSheet extends DHBaseActorSheet { this.render(); } + async updateArmorMarks(event) { + const armor = this.document.system.armor; + if (!armor) return; + + const maxMarks = this.document.system.armorScore; + const value = Math.min(Math.max(Number(event.currentTarget.value), 0), maxMarks); + await armor.update({ 'system.marks.value': value }); + this.render(); + } + /* -------------------------------------------- */ /* Application Clicks Actions */ /* -------------------------------------------- */ diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index f46a9628..e8662f86 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -174,9 +174,114 @@ gap: 5px; justify-content: center; + .status-bar.armor-slots { + display: flex; + justify-content: center; + position: relative; + width: 95px; + height: 30px; + + .status-label { + padding: 2px 10px; + position: relative; + top: 30px; + height: 22px; + width: 95px; + border-radius: 3px; + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + color: light-dark(@beige, @dark-blue); + font-size: 12px; + } + } + .status-value { + position: absolute; + display: flex; + padding: 0 6px; + font-size: 1.2rem; + align-items: center; + width: 80px; + height: 30px; + justify-content: center; + text-align: center; + z-index: 2; + color: light-dark(@dark-blue, @beige); + border: 1px solid light-dark(@dark-blue, @golden); + border-bottom: none; + border-radius: 6px 6px 0 0; + + input[type='number'] { + background: transparent; + font-size: 1.2rem; + width: 30px; + height: 20px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: light-dark(@dark-blue, @beige); + + &.bar-input { + padding: 0; + color: light-dark(@dark-blue, @beige); + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; + + &:hover, + &:focus { + background: @semi-transparent-dark-blue; + backdrop-filter: blur(9.5px); + } + } + } + + .bar-label { + width: 30px; + } + } + + .progress-bar { + position: absolute; + appearance: none; + width: 80px; + height: 30px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + z-index: 1; + background: light-dark(transparent, @dark-blue); + border-bottom: none; + border-radius: 6px 6px 0 0; + + &::-webkit-progress-bar { + border: none; + background: light-dark(transparent, @dark-blue); + } + &::-webkit-progress-value { + background: @gradient-stress; + } + &.stress-color::-webkit-progress-value { + background: @gradient-stress; + } + &::-moz-progress-bar { + background: @gradient-stress; + } + &.stress-color::-moz-progress-bar { + background: @gradient-stress; + } + } + } + .status-number { justify-items: center; + &.armor-slots { + width: 95px; + } + .status-value { position: relative; display: flex; @@ -192,9 +297,33 @@ background: light-dark(transparent, @dark-blue); z-index: 2; - &.armor-slots { - width: 80px; - height: 30px; + input[type='number'] { + background: transparent; + font-size: 1.2rem; + width: 30px; + height: 20px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: light-dark(@dark-blue, @beige); + + &.bar-input { + padding: 0; + color: light-dark(@dark-blue, @beige); + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; + + &:hover, + &:focus { + background: @semi-transparent-dark-blue; + backdrop-filter: blur(9.5px); + } + } + } + + .bar-label { + width: 30px; } } diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index 7b5659d6..51816443 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -48,18 +48,32 @@ -
-
- {{#if document.system.armor.system.marks}} -

{{document.system.armor.system.marks.value}}/{{document.system.armorScore}}

- {{else}} + {{#if document.system.armor.system.marks}} +
+
+

+

/

+

{{document.system.armorScore}}

+
+ +
+

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

+
+
+ {{else}} +
+

-

- {{/if}} +
+
+

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

+
-
-

Armor Slots

-
-
+ {{/if}}
From 422f28c93c518027739afc897b108ddde41f78f8 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 15 Jul 2025 14:07:07 +0200 Subject: [PATCH 03/22] Removed all instances of justify-items and replaced with flex solution (#343) --- styles/less/sheets/actors/adversary/sidebar.less | 4 +++- styles/less/sheets/actors/character/sidebar.less | 4 +++- styles/less/sheets/actors/companion/header.less | 1 - styles/less/sheets/actors/environment/header.less | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index c1bd1856..f8a34874 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -215,7 +215,9 @@ justify-content: center; .status-number { - justify-items: center; + display: flex; + align-items: center; + flex-direction: column; .status-value { position: relative; diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index e8662f86..2c88d14f 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -276,7 +276,9 @@ } .status-number { - justify-items: center; + display: flex; + align-items: center; + flex-direction: column; &.armor-slots { width: 95px; diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index 832a6050..b343146f 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -92,7 +92,6 @@ position: relative; width: 100px; height: 40px; - justify-items: center; .status-label { position: relative; diff --git a/styles/less/sheets/actors/environment/header.less b/styles/less/sheets/actors/environment/header.less index e3ca0eec..1276b276 100644 --- a/styles/less/sheets/actors/environment/header.less +++ b/styles/less/sheets/actors/environment/header.less @@ -60,7 +60,9 @@ } .status-number { - justify-items: center; + display: flex; + align-items: center; + flex-direction: column; .status-value { position: relative; From 37c1d7ad880894cd30e002d56b1df064415836e3 Mon Sep 17 00:00:00 2001 From: Dapoulp <74197441+Dapoulp@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:01:34 +0200 Subject: [PATCH 04/22] Feature/163 actor subdatas (#346) * Actor Roll bonuses * Removed console log and comment --------- Co-authored-by: WBHarry --- lang/en.json | 8 ++-- module/applications/ui/chatLog.mjs | 2 - module/config/generalConfig.mjs | 12 +++--- module/data/action/attackAction.mjs | 8 +++- module/data/action/baseAction.mjs | 11 ++++- module/data/action/damageAction.mjs | 5 +++ module/data/action/healingAction.mjs | 4 ++ module/data/actor/adversary.mjs | 25 ++++++----- module/data/actor/character.mjs | 56 ++++++++++--------------- module/data/actor/companion.mjs | 19 +++++---- module/data/fields/actorField.mjs | 28 +++++++++++++ module/data/item/weapon.mjs | 2 +- module/dice/d20Roll.mjs | 34 ++++++++------- module/dice/damageRoll.mjs | 18 ++++++++ module/dice/dhRoll.mjs | 47 ++++++++++++++++++--- module/dice/dualityRoll.mjs | 19 ++++++--- module/documents/actor.mjs | 2 +- module/helpers/handlebarsHelper.mjs | 5 ++- module/helpers/utils.mjs | 2 +- styles/less/ui/chat/theme-colorful.less | 2 + templates/dialogs/actionSelect.hbs | 4 +- 21 files changed, 210 insertions(+), 103 deletions(-) create mode 100644 module/data/fields/actorField.mjs diff --git a/lang/en.json b/lang/en.json index a2d4bfd8..bfaac070 100755 --- a/lang/en.json +++ b/lang/en.json @@ -686,11 +686,11 @@ } }, "RollTypes": { - "ability": { - "name": "Ability" + "trait": { + "name": "Trait" }, - "weapon": { - "name": "Weapon" + "attack": { + "name": "Attack" }, "spellcast": { "name": "SpellCast" diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index f295d2a6..aa604dda 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -14,8 +14,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } addChatListeners = async (app, html, data) => { - super.addChatListeners(app, html, data); - html.querySelectorAll('.duality-action-damage').forEach(element => element.addEventListener('click', event => this.onRollDamage(event, data.message)) ); diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 4216f631..3522b41a 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -388,17 +388,17 @@ export const countdownTypes = { } }; export const rollTypes = { - weapon: { - id: 'weapon', - label: 'DAGGERHEART.CONFIG.RollTypes.weapon.name' + attack: { + id: 'attack', + label: 'DAGGERHEART.CONFIG.RollTypes.attack.name' }, spellcast: { id: 'spellcast', label: 'DAGGERHEART.CONFIG.RollTypes.spellcast.name' }, - ability: { - id: 'ability', - label: 'DAGGERHEART.CONFIG.RollTypes.ability.name' + trait: { + id: 'trait', + label: 'DAGGERHEART.CONFIG.RollTypes.trait.name' }, diceSet: { id: 'diceSet', diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index d953416a..137879b8 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -5,7 +5,7 @@ export default class DHAttackAction extends DHDamageAction { static extraSchemas = [...super.extraSchemas, ...['roll', 'save']]; static getRollType(parent) { - return parent.type === 'weapon' ? 'weapon' : 'spellcast'; + return parent.type === 'weapon' ? 'attack' : 'spellcast'; } get chatTemplate() { @@ -21,7 +21,7 @@ export default class DHAttackAction extends DHDamageAction { } if (this.roll.useDefault) { this.roll.trait = this.item.system.attack.roll.trait; - this.roll.type = 'weapon'; + this.roll.type = 'attack'; } } } @@ -37,4 +37,8 @@ export default class DHAttackAction extends DHDamageAction { base: true }; } + + // get modifiers() { + // return []; + // } } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 6fdadfe2..4140e024 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -150,7 +150,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { } static getRollType(parent) { - return 'ability'; + return 'trait'; } static getSourceConfig(parent) { @@ -308,7 +308,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { prepareRoll() { const roll = { - modifiers: [], + modifiers: this.modifiers, trait: this.roll?.trait, label: 'Attack', type: this.actionType, @@ -362,6 +362,13 @@ export default class DHBaseAction extends foundry.abstract.DataModel { get hasRoll() { return !!this.roll?.type || !!this.roll?.bonus; } + + get modifiers() { + if(!this.actor) return []; + const modifiers = []; + /** Placeholder for specific bonuses **/ + return modifiers; + } /* ROLL */ /* SAVE */ diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 7f7faeee..492c4184 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -28,6 +28,7 @@ export default class DHDamageAction extends DHBaseAction { hasSave: this.hasSave, isCritical: data.system?.roll?.isCritical ?? false, source: data.system?.source, + data: this.getRollData(), damageTypes, event }; @@ -39,4 +40,8 @@ export default class DHDamageAction extends DHBaseAction { roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); } + + // get modifiers() { + // return []; + // } } diff --git a/module/data/action/healingAction.mjs b/module/data/action/healingAction.mjs index 26e53817..4c1366a1 100644 --- a/module/data/action/healingAction.mjs +++ b/module/data/action/healingAction.mjs @@ -39,4 +39,8 @@ export default class DHHealingAction extends DHBaseAction { get chatTemplate() { return 'systems/daggerheart/templates/ui/chat/healing-roll.hbs'; } + + get modifiers() { + return []; + } } diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 78e08323..6ecee0a1 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -1,13 +1,7 @@ import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs'; import ActionField from '../fields/actionField.mjs'; import BaseDataActor from './base.mjs'; - -const resourceField = () => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - max: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - isReversed: new foundry.data.fields.BooleanField({ initial: true }) - }); +import { resourceField, bonusField } from '../fields/actorField.mjs'; export default class DhpAdversary extends BaseDataActor { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary']; @@ -43,8 +37,8 @@ export default class DhpAdversary extends BaseDataActor { severe: new fields.NumberField({ required: true, initial: 0, integer: true }) }), resources: new fields.SchemaField({ - hitPoints: resourceField(), - stress: resourceField() + hitPoints: resourceField(0, true), + stress: resourceField(0, true) }), attack: new ActionField({ initial: { @@ -59,7 +53,7 @@ export default class DhpAdversary extends BaseDataActor { amount: 1 }, roll: { - type: 'weapon' + type: 'attack' }, damage: { parts: [ @@ -80,9 +74,14 @@ export default class DhpAdversary extends BaseDataActor { }) ), bonuses: new fields.SchemaField({ - difficulty: new fields.SchemaField({ - all: new fields.NumberField({ integer: true, initial: 0 }), - reaction: new fields.NumberField({ integer: true, initial: 0 }) + roll: new fields.SchemaField({ + attack: bonusField(), + action: bonusField(), + reaction: bonusField() + }), + damage: new fields.SchemaField({ + physical: bonusField(), + magical: bonusField() }) }) }; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index e8322671..0df5dd2f 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -2,25 +2,7 @@ import { burden } from '../../config/generalConfig.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import DhLevelData from '../levelData.mjs'; import BaseDataActor from './base.mjs'; - -const attributeField = () => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - tierMarked: new foundry.data.fields.BooleanField({ initial: false }) - }); - -const resourceField = (max, reverse = false) => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - max: new foundry.data.fields.NumberField({ initial: max, integer: true }), - isReversed: new foundry.data.fields.BooleanField({ initial: reverse }) - }); - -const stressDamageReductionRule = () => - new foundry.data.fields.SchemaField({ - enabled: new foundry.data.fields.BooleanField({ required: true, initial: false }), - cost: new foundry.data.fields.NumberField({ integer: true }) - }); +import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; export default class DhCharacter extends BaseDataActor { static get metadata() { @@ -94,22 +76,25 @@ export default class DhCharacter extends BaseDataActor { levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ roll: new fields.SchemaField({ - attack: new fields.NumberField({ integer: true, initial: 0 }), - primaryWeapon: new fields.SchemaField({ - attack: new fields.NumberField({ integer: true, initial: 0 }) - }), - spellcast: new fields.NumberField({ integer: true, initial: 0 }), - action: new fields.NumberField({ integer: true, initial: 0 }), - hopeOrFear: new fields.NumberField({ integer: true, initial: 0 }) + attack: bonusField(), + spellcast: bonusField(), + trait: bonusField(), + action: bonusField(), + reaction: bonusField(), + primaryWeapon: bonusField(), + secondaryWeapon: bonusField() }), damage: new fields.SchemaField({ - all: new fields.NumberField({ integer: true, initial: 0 }), - physical: new fields.NumberField({ integer: true, initial: 0 }), - magic: new fields.NumberField({ integer: true, initial: 0 }), - primaryWeapon: new fields.SchemaField({ - bonus: new fields.NumberField({ integer: true }), - extraDice: new fields.NumberField({ integer: true }) - }) + physical: bonusField(), + magical: bonusField(), + primaryWeapon: bonusField(), + secondaryWeapon: bonusField() + }), + healing: bonusField(), + range: new fields.SchemaField({ + weapon: new fields.NumberField({ integer: true, initial: 0 }), + spell: new fields.NumberField({ integer: true, initial: 0 }), + other: new fields.NumberField({ integer: true, initial: 0 }) }) }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), @@ -181,6 +166,11 @@ export default class DhCharacter extends BaseDataActor { return !this.class.value || !this.class.subclass; } + get spellcastModifier() { + const subClasses = this.parent.items.filter(x => x.type === 'subclass') ?? []; + return Math.max(subClasses?.map(sc => this.traits[sc.system.spellcastingTrait]?.value)); + } + get spellcastingModifiers() { return { main: this.class.subclass?.system?.spellcastingTrait, diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index ea417bd8..f7e94311 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -4,6 +4,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ActionField from '../fields/actionField.mjs'; import { adjustDice, adjustRange } from '../../helpers/utils.mjs'; import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs'; +import { resourceField, bonusField } from '../fields/actorField.mjs'; export default class DhCompanion extends BaseDataActor { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion']; @@ -23,11 +24,7 @@ export default class DhCompanion extends BaseDataActor { ...super.defineSchema(), partner: new ForeignDocumentUUIDField({ type: 'Actor' }), resources: new fields.SchemaField({ - stress: new fields.SchemaField({ - value: new fields.NumberField({ initial: 0, integer: true }), - max: new fields.NumberField({ initial: 3, integer: true }), - isReversed: new foundry.data.fields.BooleanField({ initial: true }) - }), + stress: resourceField(3, true), hope: new fields.NumberField({ initial: 0, integer: true }) }), evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }), @@ -56,7 +53,7 @@ export default class DhCompanion extends BaseDataActor { amount: 1 }, roll: { - type: 'weapon', + type: 'attack', bonus: 0, trait: 'instinct' }, @@ -74,7 +71,13 @@ export default class DhCompanion extends BaseDataActor { } }), actions: new fields.ArrayField(new ActionField()), - levelData: new fields.EmbeddedDataField(DhLevelData) + levelData: new fields.EmbeddedDataField(DhLevelData), + bonuses: new fields.SchemaField({ + damage: new fields.SchemaField({ + physical: bonusField(), + magical: bonusField() + }) + }) }; } @@ -89,7 +92,7 @@ export default class DhCompanion extends BaseDataActor { } prepareBaseData() { - const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main; + const partnerSpellcastingModifier = this.partner?.system?.spellcastModifier; const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.value; this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing; diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs new file mode 100644 index 00000000..dc8dcbac --- /dev/null +++ b/module/data/fields/actorField.mjs @@ -0,0 +1,28 @@ +const fields = foundry.data.fields; + +const attributeField = () => + new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true }), + tierMarked: new fields.BooleanField({ initial: false }) + }); + +const resourceField = (max = 0, reverse = false) => + new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true }), + max: new fields.NumberField({ initial: max, integer: true }), + isReversed: new fields.BooleanField({ initial: reverse }) + }); + +const stressDamageReductionRule = () => + new fields.SchemaField({ + enabled: new fields.BooleanField({ required: true, initial: false }), + cost: new fields.NumberField({ integer: true }) + }); + +const bonusField = () => + new fields.SchemaField({ + bonus: new fields.NumberField({ integer: true, initial: 0 }), + dice: new fields.ArrayField(new fields.StringField()) + }) + +export { attributeField, resourceField, stressDamageReductionRule, bonusField }; \ No newline at end of file diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 9fbb3eba..0d0c7f76 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -50,7 +50,7 @@ export default class DHWeapon extends AttachableItem { }, roll: { trait: 'agility', - type: 'weapon' + type: 'attack' }, damage: { parts: [ diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index c9e9d428..8e13bf46 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -74,7 +74,6 @@ export default class D20Roll extends DHRoll { } constructFormula(config) { - // this.terms = []; this.createBaseDice(); this.configureModifiers(); this.resetFormula(); @@ -91,7 +90,10 @@ export default class D20Roll extends DHRoll { configureModifiers() { this.applyAdvantage(); - this.applyBaseBonus(); + + this.baseTerms = foundry.utils.deepClone(this.terms); + + this.options.roll.modifiers = this.applyBaseBonus(); this.options.experiences?.forEach(m => { if (this.options.data.experiences?.[m]) @@ -100,13 +102,8 @@ export default class D20Roll extends DHRoll { value: this.options.data.experiences[m].value }); }); - - this.options.roll.modifiers?.forEach(m => { - this.terms.push(...this.formatModifier(m.value)); - }); - - this.baseTerms = foundry.utils.deepClone(this.terms); - + + this.addModifiers(); if (this.options.extraFormula) { this.terms.push( new foundry.dice.terms.OperatorTerm({ operator: '+' }), @@ -125,13 +122,18 @@ export default class D20Roll extends DHRoll { } applyBaseBonus() { - this.options.roll.modifiers = []; - if (!this.options.roll.bonus) return; - this.options.roll.modifiers.push({ - label: 'Bonus to Hit', - value: this.options.roll.bonus - // value: Roll.replaceFormulaData('@attackBonus', this.data) - }); + const modifiers = []; + + if(this.options.roll.bonus) + modifiers.push({ + label: 'Bonus to Hit', + value: this.options.roll.bonus + }); + + modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type.capitalize()} Bonus`)); + modifiers.push(...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type.capitalize()} Bonus`)); + + return modifiers; } static async buildEvaluate(roll, config = {}, message = {}) { diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 2182d3d0..8b835583 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -24,8 +24,26 @@ export default class DamageRoll extends DHRoll { } } + applyBaseBonus() { + const modifiers = [], + type = this.options.messageType ?? 'damage'; + + modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`)); + this.options.damageTypes?.forEach(t => { + modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`)); + }); + const weapons = ['primaryWeapon', 'secondaryWeapon']; + weapons.forEach(w => { + if(this.options.source.item && this.options.source.item === this.data[w]?.id) + modifiers.push(...this.getBonus(`${type}.${w}`, 'Weapon Bonus')); + }); + + return modifiers; + } + constructFormula(config) { super.constructFormula(config); + if (config.isCritical) { const tmpRoll = new Roll(this._formula)._evaluateSync({ maximize: true }), criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll); diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 8744223a..22903d6a 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -4,6 +4,7 @@ export default class DHRoll extends Roll { baseTerms = []; constructor(formula, data, options) { super(formula, data, options); + if(!this.data || !Object.keys(this.data).length) this.data = options.data; } static messageType = 'adversaryRoll'; @@ -99,11 +100,44 @@ export default class DHRoll extends Roll { } formatModifier(modifier) { - const numTerm = modifier < 0 ? '-' : '+'; - return [ - new foundry.dice.terms.OperatorTerm({ operator: numTerm }), - new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) }) - ]; + if(Array.isArray(modifier)) { + return [ + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...this.constructor.parse(modifier.join(' + '), this.options.data) + ]; + } else { + const numTerm = modifier < 0 ? '-' : '+'; + return [ + new foundry.dice.terms.OperatorTerm({ operator: numTerm }), + new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) }) + ]; + } + } + + applyBaseBonus() { + return []; + } + + addModifiers() { + this.options.roll.modifiers?.forEach(m => { + this.terms.push(...this.formatModifier(m.value)); + }); + } + + getBonus(path, label) { + const bonus = foundry.utils.getProperty(this.data.bonuses, path), + modifiers = []; + if(bonus?.bonus) + modifiers.push({ + label: label, + value: bonus?.bonus + }); + if(bonus?.dice?.length) + modifiers.push({ + label: label, + value: bonus?.dice + }); + return modifiers; } getFaces(faces) { @@ -113,6 +147,9 @@ export default class DHRoll extends Roll { constructFormula(config) { this.terms = Roll.parse(this.options.roll.formula, config.data); + this.options.roll.modifiers = this.applyBaseBonus(); + this.addModifiers(); + if (this.options.extraFormula) { this.terms.push( new foundry.dice.terms.OperatorTerm({ operator: '+' }), diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 1044b93a..99e4fa42 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -119,12 +119,21 @@ export default class DualityRoll extends D20Roll { } applyBaseBonus() { - this.options.roll.modifiers = []; - if (!this.options.roll.trait) return; - this.options.roll.modifiers.push({ - label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, - value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.value`, this.data) + const modifiers = super.applyBaseBonus(); + + if(this.options.roll.trait && this.data.traits[this.options.roll.trait]) + modifiers.unshift({ + label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, + value: this.data.traits[this.options.roll.trait].value + }); + + const weapons = ['primaryWeapon', 'secondaryWeapon']; + weapons.forEach(w => { + if(this.options.source.item && this.options.source.item === this.data[w]?.id) + modifiers.push(...this.getBonus(`roll.${w}`, 'Weapon Bonus')); }); + + return modifiers; } static postEvaluate(roll, config = {}) { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index c789064b..73a8a3db 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -371,7 +371,7 @@ export default class DhpActor extends Actor { getRollData() { const rollData = super.getRollData(); rollData.prof = this.system.proficiency ?? 1; - rollData.cast = this.system.spellcast ?? 1; + rollData.cast = this.system.spellcastModifier ?? 1; return rollData; } diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index 407f065e..17326dc2 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -39,8 +39,9 @@ export default class RegisterHandlebarsHelpers { } static damageSymbols(damageParts) { - const symbols = new Set(); - damageParts.forEach(part => symbols.add(...CONFIG.DH.GENERAL.damageTypes[part.type].icon)); + const symbols = [...new Set(damageParts.reduce((a, c) => a.concat([...c.type]), []))].map( + p => CONFIG.DH.GENERAL.damageTypes[p].icon + ); return new Handlebars.SafeString(Array.from(symbols).map(symbol => ``)); } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 1e694aad..336ecf5b 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -306,7 +306,7 @@ export const itemAbleRollParse = (value, actor, item) => { const isItemTarget = value.toLowerCase().startsWith('item.'); const slicedValue = isItemTarget ? value.slice(5) : value; try { - return Roll.safeEval(Roll.replaceFormulaData(slicedValue, isItemTarget ? item : actor)); + return Roll.replaceFormulaData(slicedValue, isItemTarget ? item : actor); } catch (_) { return ''; } diff --git a/styles/less/ui/chat/theme-colorful.less b/styles/less/ui/chat/theme-colorful.less index d9c4e08e..3de5b432 100644 --- a/styles/less/ui/chat/theme-colorful.less +++ b/styles/less/ui/chat/theme-colorful.less @@ -44,12 +44,14 @@ display: flex; gap: 2px; margin-bottom: 4px; + flex-wrap: wrap; .duality-modifier { padding: 2px; border-radius: 6px; border: 1px solid; background: var(--color-dark-6); font-size: 12px; + white-space: nowrap; } } .dice-flavor { diff --git a/templates/dialogs/actionSelect.hbs b/templates/dialogs/actionSelect.hbs index 200ef29c..f2190121 100644 --- a/templates/dialogs/actionSelect.hbs +++ b/templates/dialogs/actionSelect.hbs @@ -5,8 +5,8 @@
    {{#each actions}}
  • - - {{ name }} + +
  • {{/each}}
From 0dd5b53313ae7d4a578561518432d51cef49db2a Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:55:45 +0200 Subject: [PATCH 05/22] [Feature] ActiveEffect Autocomplete (#338) * Fixed translation of TrackedAttributeChoices * Styling improvements * Added hints * fix autocomplete style, fix tagify style, fix magical and physical tag style and fix lang in details adversary settings * Removed commented out code * Some companion fixes --------- Co-authored-by: moliloo --- daggerheart.mjs | 12 +- lang/en.json | 89 +++++++++++-- .../applications/sheets-configs/_module.mjs | 2 + .../sheets-configs/activeEffectConfig.mjs | 89 +++++++++---- .../sheets-configs/prototype-token-config.mjs | 20 +++ .../sheets-configs/token-config.mjs | 20 +++ .../applications/sheets/actors/character.mjs | 2 +- module/config/domainConfig.mjs | 18 +-- module/data/action/baseAction.mjs | 2 +- module/data/actor/adversary.mjs | 35 ++++-- module/data/actor/base.mjs | 8 +- module/data/actor/character.mjs | 119 +++++++++++------- module/data/actor/companion.mjs | 16 ++- module/data/fields/actorField.mjs | 24 ++-- module/dice/d20Roll.mjs | 10 +- module/dice/damageRoll.mjs | 6 +- module/dice/dhRoll.mjs | 8 +- module/dice/dualityRoll.mjs | 4 +- module/documents/_module.mjs | 1 + module/documents/token.mjs | 35 ++++++ package-lock.json | 6 + package.json | 1 + styles/less/global/elements.less | 58 ++++++--- .../less/sheets/actors/companion/details.less | 4 +- styles/less/ux/autocomplete/autocomplete.less | 44 +++++++ styles/less/ux/index.less | 1 + system.json | 2 +- .../adversary-settings/details.hbs | 8 +- .../companion-settings/details.hbs | 12 +- templates/sheets/activeEffect/changes.hbs | 7 +- templates/sheets/actors/companion/details.hbs | 9 +- 31 files changed, 501 insertions(+), 171 deletions(-) create mode 100644 module/applications/sheets-configs/prototype-token-config.mjs create mode 100644 module/applications/sheets-configs/token-config.mjs create mode 100644 module/documents/token.mjs create mode 100644 styles/less/ux/autocomplete/autocomplete.less diff --git a/daggerheart.mjs b/daggerheart.mjs index 5a6d8193..a1742641 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -61,6 +61,14 @@ Hooks.once('init', () => { CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]]; CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate; + const { DocumentSheetConfig } = foundry.applications.apps; + CONFIG.Token.documentClass = documents.DhToken; + CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig; + DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig); + DocumentSheetConfig.registerSheet(TokenDocument, 'dnd5e', applications.sheetConfigs.DhTokenConfig, { + makeDefault: true + }); + CONFIG.Item.documentClass = documents.DHItem; //Registering the Item DataModel @@ -98,12 +106,12 @@ Hooks.once('init', () => { CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect; CONFIG.ActiveEffect.dataModels = models.activeEffects.config; - foundry.applications.apps.DocumentSheetConfig.unregisterSheet( + DocumentSheetConfig.unregisterSheet( CONFIG.ActiveEffect.documentClass, 'core', foundry.applications.sheets.ActiveEffectConfig ); - foundry.applications.apps.DocumentSheetConfig.registerSheet( + DocumentSheetConfig.registerSheet( CONFIG.ActiveEffect.documentClass, SYSTEM.id, applications.sheetConfigs.ActiveEffectConfig, diff --git a/lang/en.json b/lang/en.json index bfaac070..1adf4bc6 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1011,23 +1011,36 @@ "severe": "Severe", "major": "Major", "minor": "Minor", - "none": "None" + "none": "None", + "allDamage": "All Damage", + "physicalDamage": "Physical Damage", + "magicalDamage": "Magical Damage", + "primaryWeapon": "Primary Weapon Damage", + "secondaryWeapon": "Secondary Weapon Damage" }, "DamageResistance": { "none": "None", "resistance": "Resistance", - "immunity": "Immunity" + "immunity": "Immunity", + "physicalReduction": "Physical Damage Reduction", + "magicalReduction": "Magical Damage Reduction" }, "DamageThresholds": { "title": "Damage Thresholds", "minor": "Minor", "major": "Major", - "severe": "Severe" + "severe": "Severe", + "majorThreshold": "Major Damage Threshold", + "severeThreshold": "Severe Damage Threshold" }, "Dice": { "single": "Die", "plural": "Dice" }, + "Difficulty": { + "all": "Difficulty: all", + "reaction": "Difficulty: reaction" + }, "Disadvantage": { "full": "Disadvantage", "short": "Dis" @@ -1037,39 +1050,39 @@ "plural": "Domains", "arcana": { "label": "Arcana", - "Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled." + "description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled." }, "blade": { "label": "Blade", - "Description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death." + "description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death." }, "bone": { "label": "Bone", - "Description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training." + "description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training." }, "codex": { "label": "Codex", - "Description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge." + "description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge." }, "grace": { "label": "Grace", - "Description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language." + "description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language." }, "midnight": { "label": "Midnight", - "Description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas." + "description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas." }, "sage": { "label": "Sage", - "Description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator." + "description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator." }, "splendor": { "label": "Splendor", - "Description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life." + "description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life." }, "valor": { "label": "Valor", - "Description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others." + "description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others." } }, "Effect": { @@ -1080,10 +1093,18 @@ "single": "Experience", "plural": "Experiences" }, + "Healing": { + "healingAmount": "Healing Amount" + }, "Neutral": { "full": "None", "short": "no" }, + "Range": { + "other": "Range Increase: Other", + "spell": "Range Increase: Spell", + "weapon": "Range Increase: Weapon" + }, "RefreshType": { "session": "Session", "shortrest": "Short Rest", @@ -1093,6 +1114,42 @@ "single": "Resource", "plural": "Resources" }, + "Roll": { + "attack": "Attack Roll", + "primaryWeaponAttack": "Primary Weapon Attack Roll", + "secondaryWeaponAttack": "Secondary Weapon Attack Roll", + "spellcast": "Spellcast Roll", + "trait": "Trait Roll", + "action": "Action Roll", + "reaction": "Reaction Roll" + }, + "Rules": { + "damageReduction": { + "increasePerArmorMark": { + "label": "Damage Reduction per Armor Slot", + "hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by." + }, + "maxArmorMarkedBonus": "Max Armor Used", + "maxArmorMarkedStress": { + "label": "Max Armor Used With Stress", + "hint": "If this value is set you can use up to that much stress to spend additional Armor Marks beyond your normal maximum." + }, + "stress": { + "severe": { + "label": "Stress Damage Reduction: Severe", + "hint": "The cost in stress you can pay to reduce severe damage down to major." + }, + "major": { + "label": "Stress Damage Reduction: Major", + "hint": "The cost in stress you can pay to reduce major damage down to minor." + }, + "minor": { + "label": "Stress Damage Reduction: Minor", + "hint": "The cost in stress you can pay to reduce minor damage to none." + } + } + } + }, "Tabs": { "details": "Details", "attack": "Attack", @@ -1137,6 +1194,7 @@ "single": "Trait", "plural": "Traits" }, + "armorScore": "Armor Score", "activeEffects": "Active Effects", "armorSlots": "Armor Slots", "attack": "Attack", @@ -1152,10 +1210,15 @@ "dualityRoll": "Duality Roll", "enabled": "Enabled", "evasion": "Evasion", + "experience": { + "single": "Experience", + "plural": "Experiences" + }, "fear": "Fear", "features": "Features", "hitPoints": "Hit Points", "hope": "Hope", + "hordeHp": "Horde HP", "inactiveEffects": "Inactive Effects", "inventory": "Inventory", "level": "Level", @@ -1163,6 +1226,8 @@ "modifier": "Modifier", "multiclass": "Multiclass", "none": "None", + "partner": "Partner", + "proficiency": "Proficiency", "quantity": "Quantity", "range": "Range", "recovery": "Recovery", diff --git a/module/applications/sheets-configs/_module.mjs b/module/applications/sheets-configs/_module.mjs index e1ae8fe2..fafb1fcf 100644 --- a/module/applications/sheets-configs/_module.mjs +++ b/module/applications/sheets-configs/_module.mjs @@ -3,3 +3,5 @@ export { default as AdversarySettings } from './adversary-settings.mjs'; export { default as CompanionSettings } from './companion-settings.mjs'; export { default as EnvironmentSettings } from './environment-settings.mjs'; export { default as ActiveEffectConfig } from './activeEffectConfig.mjs'; +export { default as DhTokenConfig } from './token-config.mjs'; +export { default as DhPrototypeTokenConfig } from './prototype-token-config.mjs'; diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 6a629583..8574a5db 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -1,4 +1,24 @@ +import autocomplete from 'autocompleter'; + export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig { + constructor(options) { + super(options); + + const ignoredActorKeys = ['config', 'DhEnvironment']; + this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => { + if (!ignoredActorKeys.includes(key)) { + const model = game.system.api.models.actors[key]; + const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model); + const group = game.i18n.localize(model.metadata.label); + const choices = CONFIG.Token.documentClass + .getTrackedAttributeChoices(attributes, model) + .map(x => ({ ...x, group: group })); + acc.push(...choices); + } + return acc; + }, []); + } + static DEFAULT_OPTIONS = { classes: ['daggerheart', 'sheet', 'dh-style'] }; @@ -27,36 +47,59 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac } }; + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + const changeChoices = this.changeChoices; + + htmlElement.querySelectorAll('.effect-change-input').forEach(element => { + autocomplete({ + input: element, + fetch: function (text, update) { + if (!text) { + update(changeChoices); + } else { + text = text.toLowerCase(); + var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text)); + update(suggestions); + } + }, + render: function (item, search) { + const label = game.i18n.localize(item.label); + const matchIndex = label.toLowerCase().indexOf(search); + + const beforeText = label.slice(0, matchIndex); + const matchText = label.slice(matchIndex, matchIndex + search.length); + const after = label.slice(matchIndex + search.length, label.length); + + const element = document.createElement('li'); + element.innerHTML = `${beforeText}${matchText ? `${matchText}` : ''}${after}`; + if (item.hint) { + element.dataset.tooltip = game.i18n.localize(item.hint); + } + + return element; + }, + renderGroup: function (label) { + const itemElement = document.createElement('div'); + itemElement.textContent = game.i18n.localize(label); + return itemElement; + }, + onSelect: function (item) { + element.value = `system.${item.value}`; + }, + click: e => e.fetch(), + minLength: 0 + }); + }); + } + async _preparePartContext(partId, context) { const partContext = await super._preparePartContext(partId, context); switch (partId) { case 'changes': - const fieldPaths = []; - const validFieldPath = fieldPath => this.validFieldPath(fieldPath, this.#unapplicablePaths); - context.document.parent.system.schema.apply(function () { - if (!(this instanceof foundry.data.fields.SchemaField)) { - if (validFieldPath(this.fieldPath)) { - fieldPaths.push(this.fieldPath); - } - } - }); - - context.fieldPaths = fieldPaths; - break; } return partContext; } - - #unapplicablePaths = ['story', 'pronouns', 'description']; - validFieldPath(fieldPath, unapplicablePaths) { - const splitPath = fieldPath.split('.'); - if (splitPath.length > 1 && unapplicablePaths.includes(splitPath[1])) return false; - - /* The current value of a resource should not be modified */ - if (new RegExp(/resources.*\.value/).exec(fieldPath)) return false; - - return true; - } } diff --git a/module/applications/sheets-configs/prototype-token-config.mjs b/module/applications/sheets-configs/prototype-token-config.mjs new file mode 100644 index 00000000..24c9dabb --- /dev/null +++ b/module/applications/sheets-configs/prototype-token-config.mjs @@ -0,0 +1,20 @@ +export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig { + /** @inheritDoc */ + async _prepareResourcesTab() { + const token = this.token; + const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes); + const attributeSource = + this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes + ? this.actor?.type + : this.actor?.system; + const TokenDocument = foundry.utils.getDocumentClass('Token'); + const attributes = TokenDocument.getTrackedAttributes(attributeSource); + return { + barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource), + bar1: token.getBarAttribute?.('bar1'), + bar2: token.getBarAttribute?.('bar2'), + turnMarkerModes: DhPrototypeTokenConfig.TURN_MARKER_MODES, + turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations + }; + } +} diff --git a/module/applications/sheets-configs/token-config.mjs b/module/applications/sheets-configs/token-config.mjs new file mode 100644 index 00000000..ee573e5d --- /dev/null +++ b/module/applications/sheets-configs/token-config.mjs @@ -0,0 +1,20 @@ +export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig { + /** @inheritDoc */ + async _prepareResourcesTab() { + const token = this.token; + const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes); + const attributeSource = + this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes + ? this.actor?.type + : this.actor?.system; + const TokenDocument = foundry.utils.getDocumentClass('Token'); + const attributes = TokenDocument.getTrackedAttributes(attributeSource); + return { + barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource), + bar1: token.getBarAttribute?.('bar1'), + bar2: token.getBarAttribute?.('bar2'), + turnMarkerModes: DhTokenConfig.TURN_MARKER_MODES, + turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations + }; + } +} diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 99545d4e..fcd92842 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -103,7 +103,7 @@ export default class CharacterSheet extends DHBaseActorSheet { htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => { element.addEventListener('change', this.updateItemQuantity.bind(this)); }); - + // Add listener for armor marks input htmlElement.querySelectorAll('.armor-marks-input').forEach(element => { element.addEventListener('change', this.updateArmorMarks.bind(this)); diff --git a/module/config/domainConfig.mjs b/module/config/domainConfig.mjs index 6c8b8bfd..2387e00f 100644 --- a/module/config/domainConfig.mjs +++ b/module/config/domainConfig.mjs @@ -3,55 +3,55 @@ export const domains = { id: 'arcana', label: 'DAGGERHEART.GENERAL.Domain.arcana.label', src: 'systems/daggerheart/assets/icons/domains/arcana.svg', - description: 'DAGGERHEART.GENERAL.Domain.Arcana' + description: 'DAGGERHEART.GENERAL.Domain.arcana.description' }, blade: { id: 'blade', label: 'DAGGERHEART.GENERAL.Domain.blade.label', src: 'systems/daggerheart/assets/icons/domains/blade.svg', - description: 'DAGGERHEART.GENERAL.Domain.Blade' + description: 'DAGGERHEART.GENERAL.Domain.blade.description' }, bone: { id: 'bone', label: 'DAGGERHEART.GENERAL.Domain.bone.label', src: 'systems/daggerheart/assets/icons/domains/bone.svg', - description: 'DAGGERHEART.GENERAL.Domain.Bone' + description: 'DAGGERHEART.GENERAL.Domain.bone.description' }, codex: { id: 'codex', label: 'DAGGERHEART.GENERAL.Domain.codex.label', src: 'systems/daggerheart/assets/icons/domains/codex.svg', - description: 'DAGGERHEART.GENERAL.Domain.Codex' + description: 'DAGGERHEART.GENERAL.Domain.codex.description' }, grace: { id: 'grace', label: 'DAGGERHEART.GENERAL.Domain.grace.label', src: 'systems/daggerheart/assets/icons/domains/grace.svg', - description: 'DAGGERHEART.GENERAL.Domain.Grace' + description: 'DAGGERHEART.GENERAL.Domain.grace.description' }, midnight: { id: 'midnight', label: 'DAGGERHEART.GENERAL.Domain.midnight.label', src: 'systems/daggerheart/assets/icons/domains/midnight.svg', - description: 'DAGGERHEART.GENERAL.Domain.Midnight' + description: 'DAGGERHEART.GENERAL.Domain.midnight.description' }, sage: { id: 'sage', label: 'DAGGERHEART.GENERAL.Domain.sage.label', src: 'systems/daggerheart/assets/icons/domains/sage.svg', - description: 'DAGGERHEART.GENERAL.Domain.Sage' + description: 'DAGGERHEART.GENERAL.Domain.sage.description' }, splendor: { id: 'splendor', label: 'DAGGERHEART.GENERAL.Domain.splendor.label', src: 'systems/daggerheart/assets/icons/domains/splendor.svg', - description: 'DAGGERHEART.GENERAL.Domain.Splendor' + description: 'DAGGERHEART.GENERAL.Domain.splendor.description' }, valor: { id: 'valor', label: 'DAGGERHEART.GENERAL.Domain.valor.label', src: 'systems/daggerheart/assets/icons/domains/valor.svg', - description: 'DAGGERHEART.GENERAL.Domain.Valor' + description: 'DAGGERHEART.GENERAL.Domain.valor.description' } }; diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 4140e024..39051434 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -364,7 +364,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { } get modifiers() { - if(!this.actor) return []; + if (!this.actor) return []; const modifiers = []; /** Placeholder for specific bonuses **/ return modifiers; diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 6ecee0a1..e77a4855 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -31,14 +31,29 @@ export default class DhpAdversary extends BaseDataActor { motivesAndTactics: new fields.StringField(), notes: new fields.HTMLField(), difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }), - hordeHp: new fields.NumberField({ required: true, initial: 1, integer: true }), + hordeHp: new fields.NumberField({ + required: true, + initial: 1, + integer: true, + label: 'DAGGERHEART.GENERAL.hordeHp' + }), damageThresholds: new fields.SchemaField({ - major: new fields.NumberField({ required: true, initial: 0, integer: true }), - severe: new fields.NumberField({ required: true, initial: 0, integer: true }) + major: new fields.NumberField({ + required: true, + initial: 0, + integer: true, + label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold' + }), + severe: new fields.NumberField({ + required: true, + initial: 0, + integer: true, + label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold' + }) }), resources: new fields.SchemaField({ - hitPoints: resourceField(0, true), - stress: resourceField(0, true) + hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints', true), + stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true) }), attack: new ActionField({ initial: { @@ -75,13 +90,13 @@ export default class DhpAdversary extends BaseDataActor { ), bonuses: new fields.SchemaField({ roll: new fields.SchemaField({ - attack: bonusField(), - action: bonusField(), - reaction: bonusField() + attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'), + action: bonusField('DAGGERHEART.GENERAL.Roll.action'), + reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction') }), damage: new fields.SchemaField({ - physical: bonusField(), - magical: bonusField() + physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'), + magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage') }) }) }; diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index cf1eebb1..19de7b06 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -1,10 +1,10 @@ import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; -const resistanceField = () => +const resistanceField = reductionLabel => new foundry.data.fields.SchemaField({ resistance: new foundry.data.fields.BooleanField({ initial: false }), immunity: new foundry.data.fields.BooleanField({ initial: false }), - reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0 }) + reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0, label: reductionLabel }) }); /** @@ -40,8 +40,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true }); if (this.metadata.hasResistances) schema.resistance = new fields.SchemaField({ - physical: resistanceField(), - magical: resistanceField() + physical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.physicalReduction'), + magical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.magicalReduction') }); return schema; } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 0df5dd2f..e25dba85 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -5,6 +5,8 @@ import BaseDataActor from './base.mjs'; import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; export default class DhCharacter extends BaseDataActor { + static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character']; + static get metadata() { return foundry.utils.mergeObject(super.metadata, { label: 'TYPES.Actor.character', @@ -19,24 +21,36 @@ export default class DhCharacter extends BaseDataActor { return { ...super.defineSchema(), resources: new fields.SchemaField({ - hitPoints: resourceField(0, true), - stress: resourceField(6, true), - hope: resourceField(6) + hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints', true), + stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true), + hope: resourceField(6, 'DAGGERHEART.GENERAL.hope') }), traits: new fields.SchemaField({ - agility: attributeField(), - strength: attributeField(), - finesse: attributeField(), - instinct: attributeField(), - presence: attributeField(), - knowledge: attributeField() + agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'), + strength: attributeField('DAGGERHEART.CONFIG.Traits.strength.name'), + finesse: attributeField('DAGGERHEART.CONFIG.Traits.finesse.name'), + instinct: attributeField('DAGGERHEART.CONFIG.Traits.instinct.name'), + presence: attributeField('DAGGERHEART.CONFIG.Traits.presence.name'), + knowledge: attributeField('DAGGERHEART.CONFIG.Traits.knowledge.name') }), - proficiency: new fields.NumberField({ initial: 1, integer: true }), - evasion: new fields.NumberField({ initial: 0, integer: true }), - armorScore: new fields.NumberField({ integer: true, initial: 0 }), + proficiency: new fields.NumberField({ + initial: 1, + integer: true, + label: 'DAGGERHEART.GENERAL.proficiency' + }), + evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }), + armorScore: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.armorScore' }), damageThresholds: new fields.SchemaField({ - severe: new fields.NumberField({ integer: true, initial: 0 }), - major: new fields.NumberField({ integer: true, initial: 0 }) + severe: new fields.NumberField({ + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold' + }), + major: new fields.NumberField({ + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold' + }) }), experiences: new fields.TypedObjectField( new fields.SchemaField({ @@ -76,25 +90,37 @@ export default class DhCharacter extends BaseDataActor { levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ roll: new fields.SchemaField({ - attack: bonusField(), - spellcast: bonusField(), - trait: bonusField(), - action: bonusField(), - reaction: bonusField(), - primaryWeapon: bonusField(), - secondaryWeapon: bonusField() + attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'), + spellcast: bonusField('DAGGERHEART.GENERAL.Roll.spellcast'), + trait: bonusField('DAGGERHEART.GENERAL.Roll.trait'), + action: bonusField('DAGGERHEART.GENERAL.Roll.action'), + reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction'), + primaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.primaryWeaponAttack'), + secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.secondaryWeaponAttack') }), damage: new fields.SchemaField({ - physical: bonusField(), - magical: bonusField(), - primaryWeapon: bonusField(), - secondaryWeapon: bonusField() + physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'), + magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'), + primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'), + secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon') }), - healing: bonusField(), + healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'), range: new fields.SchemaField({ - weapon: new fields.NumberField({ integer: true, initial: 0 }), - spell: new fields.NumberField({ integer: true, initial: 0 }), - other: new fields.NumberField({ integer: true, initial: 0 }) + weapon: new fields.NumberField({ + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.Range.weapon' + }), + spell: new fields.NumberField({ + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.Range.spell' + }), + other: new fields.NumberField({ + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.Range.other' + }) }) }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), @@ -102,25 +128,34 @@ export default class DhCharacter extends BaseDataActor { damageReduction: new fields.SchemaField({ maxArmorMarked: new fields.SchemaField({ value: new fields.NumberField({ required: true, integer: true, initial: 1 }), - bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }), - stressExtra: new fields.NumberField({ required: true, integer: true, initial: 0 }) + bonus: new fields.NumberField({ + required: true, + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus' + }), + stressExtra: new fields.NumberField({ + required: true, + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label', + hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint' + }) }), stressDamageReduction: new fields.SchemaField({ - severe: stressDamageReductionRule(), - major: stressDamageReductionRule(), - minor: stressDamageReductionRule() + severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'), + major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'), + minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor') + }), + increasePerArmorMark: new fields.NumberField({ + integer: true, + initial: 1, + label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label', + hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint' }), - increasePerArmorMark: new fields.NumberField({ integer: true, initial: 1 }), magical: new fields.BooleanField({ initial: false }), physical: new fields.BooleanField({ initial: false }) }), - strangePatterns: new fields.NumberField({ - integer: true, - min: 1, - max: 12, - nullable: true, - initial: null - }), weapon: new fields.SchemaField({ /* Unimplemented -> Should remove the lowest damage dice from weapon damage diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index f7e94311..005d7a83 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -24,10 +24,16 @@ export default class DhCompanion extends BaseDataActor { ...super.defineSchema(), partner: new ForeignDocumentUUIDField({ type: 'Actor' }), resources: new fields.SchemaField({ - stress: resourceField(3, true), - hope: new fields.NumberField({ initial: 0, integer: true }) + stress: resourceField(3, 'DAGGERHEART.GENERAL.stress', true), + hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' }) + }), + evasion: new fields.NumberField({ + required: true, + min: 1, + initial: 10, + integer: true, + label: 'DAGGERHEART.GENERAL.evasion' }), - evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }), experiences: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField({}), @@ -74,8 +80,8 @@ export default class DhCompanion extends BaseDataActor { levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ damage: new fields.SchemaField({ - physical: bonusField(), - magical: bonusField() + physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'), + magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage') }) }) }; diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs index dc8dcbac..fe00e251 100644 --- a/module/data/fields/actorField.mjs +++ b/module/data/fields/actorField.mjs @@ -1,28 +1,32 @@ const fields = foundry.data.fields; -const attributeField = () => +const attributeField = label => new fields.SchemaField({ - value: new fields.NumberField({ initial: 0, integer: true }), + value: new fields.NumberField({ initial: 0, integer: true, label }), tierMarked: new fields.BooleanField({ initial: false }) }); -const resourceField = (max = 0, reverse = false) => +const resourceField = (max = 0, label, reverse = false) => new fields.SchemaField({ - value: new fields.NumberField({ initial: 0, integer: true }), + value: new fields.NumberField({ initial: 0, integer: true, label }), max: new fields.NumberField({ initial: max, integer: true }), isReversed: new fields.BooleanField({ initial: reverse }) }); -const stressDamageReductionRule = () => +const stressDamageReductionRule = localizationPath => new fields.SchemaField({ enabled: new fields.BooleanField({ required: true, initial: false }), - cost: new fields.NumberField({ integer: true }) + cost: new fields.NumberField({ + integer: true, + label: `${localizationPath}.label`, + hint: `${localizationPath}.hint` + }) }); -const bonusField = () => +const bonusField = label => new fields.SchemaField({ - bonus: new fields.NumberField({ integer: true, initial: 0 }), + bonus: new fields.NumberField({ integer: true, initial: 0, label }), dice: new fields.ArrayField(new fields.StringField()) - }) + }); -export { attributeField, resourceField, stressDamageReductionRule, bonusField }; \ No newline at end of file +export { attributeField, resourceField, stressDamageReductionRule, bonusField }; diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index 8e13bf46..004e4806 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -102,7 +102,7 @@ export default class D20Roll extends DHRoll { value: this.options.data.experiences[m].value }); }); - + this.addModifiers(); if (this.options.extraFormula) { this.terms.push( @@ -123,15 +123,17 @@ export default class D20Roll extends DHRoll { applyBaseBonus() { const modifiers = []; - - if(this.options.roll.bonus) + + if (this.options.roll.bonus) modifiers.push({ label: 'Bonus to Hit', value: this.options.roll.bonus }); modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type.capitalize()} Bonus`)); - modifiers.push(...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type.capitalize()} Bonus`)); + modifiers.push( + ...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type.capitalize()} Bonus`) + ); return modifiers; } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 8b835583..bfbfc7d5 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -34,16 +34,16 @@ export default class DamageRoll extends DHRoll { }); const weapons = ['primaryWeapon', 'secondaryWeapon']; weapons.forEach(w => { - if(this.options.source.item && this.options.source.item === this.data[w]?.id) + if (this.options.source.item && this.options.source.item === this.data[w]?.id) modifiers.push(...this.getBonus(`${type}.${w}`, 'Weapon Bonus')); }); - + return modifiers; } constructFormula(config) { super.constructFormula(config); - + if (config.isCritical) { const tmpRoll = new Roll(this._formula)._evaluateSync({ maximize: true }), criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll); diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 22903d6a..1bd9a6e5 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -4,7 +4,7 @@ export default class DHRoll extends Roll { baseTerms = []; constructor(formula, data, options) { super(formula, data, options); - if(!this.data || !Object.keys(this.data).length) this.data = options.data; + if (!this.data || !Object.keys(this.data).length) this.data = options.data; } static messageType = 'adversaryRoll'; @@ -100,7 +100,7 @@ export default class DHRoll extends Roll { } formatModifier(modifier) { - if(Array.isArray(modifier)) { + if (Array.isArray(modifier)) { return [ new foundry.dice.terms.OperatorTerm({ operator: '+' }), ...this.constructor.parse(modifier.join(' + '), this.options.data) @@ -127,12 +127,12 @@ export default class DHRoll extends Roll { getBonus(path, label) { const bonus = foundry.utils.getProperty(this.data.bonuses, path), modifiers = []; - if(bonus?.bonus) + if (bonus?.bonus) modifiers.push({ label: label, value: bonus?.bonus }); - if(bonus?.dice?.length) + if (bonus?.dice?.length) modifiers.push({ label: label, value: bonus?.dice diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 99e4fa42..d983b2d6 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -121,7 +121,7 @@ export default class DualityRoll extends D20Roll { applyBaseBonus() { const modifiers = super.applyBaseBonus(); - if(this.options.roll.trait && this.data.traits[this.options.roll.trait]) + if (this.options.roll.trait && this.data.traits[this.options.roll.trait]) modifiers.unshift({ label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, value: this.data.traits[this.options.roll.trait].value @@ -129,7 +129,7 @@ export default class DualityRoll extends D20Roll { const weapons = ['primaryWeapon', 'secondaryWeapon']; weapons.forEach(w => { - if(this.options.source.item && this.options.source.item === this.data[w]?.id) + if (this.options.source.item && this.options.source.item === this.data[w]?.id) modifiers.push(...this.getBonus(`roll.${w}`, 'Weapon Bonus')); }); diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs index 4dfa6264..540b06c1 100644 --- a/module/documents/_module.mjs +++ b/module/documents/_module.mjs @@ -3,4 +3,5 @@ export { default as DHItem } from './item.mjs'; export { default as DhpCombat } from './combat.mjs'; export { default as DhActiveEffect } from './activeEffect.mjs'; export { default as DhChatMessage } from './chatMessage.mjs'; +export { default as DhToken } from './token.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs'; diff --git a/module/documents/token.mjs b/module/documents/token.mjs new file mode 100644 index 00000000..4592c843 --- /dev/null +++ b/module/documents/token.mjs @@ -0,0 +1,35 @@ +export default class DHToken extends TokenDocument { + /** + * Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar. + * @param {object} attributes The tracked attributes which can be chosen from + * @returns {object} A nested object of attribute choices to display + */ + static getTrackedAttributeChoices(attributes, model) { + attributes = attributes || this.getTrackedAttributes(); + const barGroup = game.i18n.localize('TOKEN.BarAttributes'); + const valueGroup = game.i18n.localize('TOKEN.BarValues'); + + const bars = attributes.bar.map(v => { + const a = v.join('.'); + const modelLabel = model ? game.i18n.localize(model.schema.getField(`${a}.value`).label) : null; + return { group: barGroup, value: a, label: modelLabel ? modelLabel : a }; + }); + bars.sort((a, b) => a.label.compare(b.label)); + + const invalidAttributes = ['gold', 'levelData', 'rules.damageReduction.maxArmorMarked.value']; + const values = attributes.value.reduce((acc, v) => { + const a = v.join('.'); + if (invalidAttributes.some(x => a.startsWith(x))) return acc; + + const field = model ? model.schema.getField(a) : null; + const modelLabel = field ? game.i18n.localize(field.label) : null; + const hint = field ? game.i18n.localize(field.hint) : null; + acc.push({ group: valueGroup, value: a, label: modelLabel ? modelLabel : a, hint: hint }); + + return acc; + }, []); + values.sort((a, b) => a.label.compare(b.label)); + + return bars.concat(values); + } +} diff --git a/package-lock.json b/package-lock.json index 7b1bed60..864d027c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "dependencies": { "@yaireo/tagify": "^4.17.9", + "autocompleter": "^9.3.2", "gulp": "^5.0.0", "gulp-less": "^5.0.0", "rollup": "^4.40.0" @@ -608,6 +609,11 @@ "node": ">= 10.13.0" } }, + "node_modules/autocompleter": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/autocompleter/-/autocompleter-9.3.2.tgz", + "integrity": "sha512-rLbf2TLGOD7y+gOS36ksrZdIsvoHa2KXc2A7503w+NBRPrcF73zzFeYBxEcV/iMPjaBH3jFhNIYObZ7zt1fkCQ==" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", diff --git a/package.json b/package.json index d7b51dfd..a7dd69b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "dependencies": { "@yaireo/tagify": "^4.17.9", + "autocompleter": "^9.3.2", "gulp": "^5.0.0", "gulp-less": "^5.0.0", "rollup": "^4.40.0" diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 39f0d1f1..9d38e386 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -38,7 +38,7 @@ input[type='checkbox'], input[type='radio'] { &:checked::after { - color: light-dark(@dark-40, @golden); + color: light-dark(@dark, @golden); } &:checked::before { color: light-dark(@dark-40, @golden-40); @@ -112,22 +112,17 @@ margin: 5px; height: inherit; .tag { - box-shadow: 0 0 0 1.1em @beige inset; - vertical-align: top; - box-sizing: border-box; - max-width: 100%; - padding: 0.3em 0 0.3em 0.5em; - color: black; + padding: 0.3rem 0.5rem; + color: light-dark(@dark-blue, @golden); + background-color: light-dark(@dark-blue-10, @golden-40); + font-family: @font-body; border-radius: 3px; - white-space: nowrap; transition: 0.13s ease-out; - height: 22px; - font-size: 0.9rem; - gap: 0.5em; + gap: 0.5rem; z-index: 1; + .remove { font-size: 10px; - margin-inline: auto 4.6666666667px; } } } @@ -434,6 +429,34 @@ border: 1px solid light-dark(@dark, @beige); height: 34px; + // tagify rule styles + --tags-disabled-bg: none; + --tags-border-color: none; + --tags-hover-border-color: none; + --tags-focus-border-color: none; + --tag-border-radius: 3px; + --tag-bg: light-dark(@dark-blue, @golden); + --tag-remove-btn-color: light-dark(@dark-blue, @golden); + --tag-hover: light-dark(@dark-blue, @golden); + --tag-text-color: light-dark(@beige, @dark); + --tag-text-color--edit: light-dark(@beige, @dark); + --tag-pad: 0.3em 0.5em; + --tag-inset-shadow-size: 1.2em; + --tag-invalid-color: #d39494; + --tag-invalid-bg: rgba(211, 148, 148, 0.5); + --tag--min-width: 1ch; + --tag--max-width: 100%; + --tag-hide-transition: 0.3s; + --tag-remove-bg: light-dark(@dark-blue-40, @golden-40); + --tag-remove-btn-color: light-dark(@beige, @dark); + --tag-remove-btn-bg: none; + --tag-remove-btn-bg--hover: light-dark(@beige, @dark); + --input-color: inherit; + --placeholder-color: light-dark(@beige-15, @dark-15); + --placeholder-color-focus: light-dark(@beige-15, @dark-15); + --loader-size: 0.8em; + --readonly-striped: 1; + border-radius: 3px; margin-right: 1px; @@ -459,30 +482,27 @@ .tagify__dropdown { border: 1px solid light-dark(@dark, @beige) !important; + font-family: @font-body; + color: light-dark(@dark, @beige); .tagify__dropdown__wrapper { background-image: url(../assets/parchments/dh-parchment-dark.png); background-color: transparent; border: 0; + color: light-dark(@dark, @beige); .tagify__dropdown__item--active { background-color: light-dark(@dark, @beige); - color: var(--color-dark-3); + color: light-dark(@beige, @dark); } } } &.theme-light { .tagify__dropdown { - color: black; - .tagify__dropdown__wrapper { background-image: url(../assets/parchments/dh-parchment-light.png); } - - .tagify__dropdown__item--active { - color: @beige; - } } } } diff --git a/styles/less/sheets/actors/companion/details.less b/styles/less/sheets/actors/companion/details.less index 4edf8aa9..2e76cf44 100644 --- a/styles/less/sheets/actors/companion/details.less +++ b/styles/less/sheets/actors/companion/details.less @@ -3,7 +3,8 @@ .application.sheet.daggerheart.actor.dh-style.companion { .partner-section, - .attack-section { + .attack-section, + .experience-list { display: flex; flex-direction: column; align-items: center; @@ -12,6 +13,7 @@ display: flex; gap: 15px; align-items: center; + width: 100%; h3 { font-size: 20px; diff --git a/styles/less/ux/autocomplete/autocomplete.less b/styles/less/ux/autocomplete/autocomplete.less new file mode 100644 index 00000000..06cabf5a --- /dev/null +++ b/styles/less/ux/autocomplete/autocomplete.less @@ -0,0 +1,44 @@ +.theme-light .autocomplete { + background-image: url('../assets/parchments/dh-parchment-light.png'); + color: black; +} + +.autocomplete { + padding: 2px; + border-width: 0 1px 1px 1px; + border-style: solid; + border-color: light-dark(@dark, @beige); + border-radius: 6px; + background-image: url('../assets/parchments/dh-parchment-dark.png'); + z-index: 200; + max-height: 400px !important; + width: fit-content !important; + overflow-y: auto; + font-family: @font-body; + display: flex; + flex-direction: column; + gap: 2px; + + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + + .group { + font-weight: bold; + font-size: 14px; + padding-left: 8px; + } + + li[role='option'] { + font-size: 14px; + padding-left: 10px; + cursor: pointer; + + &:hover { + background-color: light-dark(@dark, @beige); + color: light-dark(@beige, var(--color-dark-3)); + } + + > div { + white-space: nowrap; + } + } +} diff --git a/styles/less/ux/index.less b/styles/less/ux/index.less index ff645288..68cfc7e5 100644 --- a/styles/less/ux/index.less +++ b/styles/less/ux/index.less @@ -1 +1,2 @@ @import './tooltip/tooltip.less'; +@import './autocomplete/autocomplete.less'; diff --git a/system.json b/system.json index 7bd337f0..1af469e0 100644 --- a/system.json +++ b/system.json @@ -264,7 +264,7 @@ "applyEffect": {} } }, - "primaryTokenAttribute": "resources.health", + "primaryTokenAttribute": "resources.hitPoints", "secondaryTokenAttribute": "resources.stress", "url": "https://your/hosted/system/repo/", "manifest": "https://your/hosted/system/repo/system.json", diff --git a/templates/sheets-settings/adversary-settings/details.hbs b/templates/sheets-settings/adversary-settings/details.hbs index 08668fd8..250d151c 100644 --- a/templates/sheets-settings/adversary-settings/details.hbs +++ b/templates/sheets-settings/adversary-settings/details.hbs @@ -20,19 +20,19 @@
{{localize "DAGGERHEART.GENERAL.hitPoints"}} - {{formGroup systemFields.resources.fields.hitPoints.fields.value value=document.system.resources.hitPoints.value}} + {{formGroup systemFields.resources.fields.hitPoints.fields.value value=document.system.resources.hitPoints.value label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.resources.hitPoints.value.label")}} {{formGroup systemFields.resources.fields.hitPoints.fields.max value=document.system.resources.hitPoints.max}}
{{localize "DAGGERHEART.GENERAL.stress"}} - {{formGroup systemFields.resources.fields.stress.fields.value value=document.system.resources.stress.value}} + {{formGroup systemFields.resources.fields.stress.fields.value value=document.system.resources.stress.value label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.resources.stress.value.label")}} {{formGroup systemFields.resources.fields.stress.fields.max value=document.system.resources.stress.max}}
{{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}} - {{formGroup systemFields.damageThresholds.fields.major value=document.system.damageThresholds.major}} - {{formGroup systemFields.damageThresholds.fields.severe value=document.system.damageThresholds.severe}} + {{formGroup systemFields.damageThresholds.fields.major value=document.system.damageThresholds.major label=(localize "DAGGERHEART.GENERAL.DamageThresholds.majorThreshold")}} + {{formGroup systemFields.damageThresholds.fields.severe value=document.system.damageThresholds.severe label=(localize "DAGGERHEART.GENERAL.DamageThresholds.severeThreshold")}}
\ No newline at end of file diff --git a/templates/sheets-settings/companion-settings/details.hbs b/templates/sheets-settings/companion-settings/details.hbs index 97b04b24..31be666c 100644 --- a/templates/sheets-settings/companion-settings/details.hbs +++ b/templates/sheets-settings/companion-settings/details.hbs @@ -11,13 +11,13 @@ {{formGroup systemFields.resources.fields.stress.fields.max value=document.system.resources.stress.max label='Max Stress'}}
-
- - +
+ + +
-
\ No newline at end of file diff --git a/templates/sheets/activeEffect/changes.hbs b/templates/sheets/activeEffect/changes.hbs index c1047206..9cf137f0 100644 --- a/templates/sheets/activeEffect/changes.hbs +++ b/templates/sheets/activeEffect/changes.hbs @@ -11,12 +11,7 @@ {{#with ../fields.changes.element.fields as |changeFields|}}
  • - - - {{#each @root.fieldPaths}} - - {{/each}} - +
    {{formInput changeFields.mode name=(concat "changes." i ".mode") value=change.mode choices=@root.modes}} diff --git a/templates/sheets/actors/companion/details.hbs b/templates/sheets/actors/companion/details.hbs index eb3665a3..0f5587f3 100644 --- a/templates/sheets/actors/companion/details.hbs +++ b/templates/sheets/actors/companion/details.hbs @@ -6,7 +6,7 @@
    -

    Partner

    +

    {{localize "DAGGERHEART.GENERAL.partner"}}

    {{#if document.system.partner}} @@ -20,7 +20,7 @@
    -

    Attack

    +

    {{localize "DAGGERHEART.GENERAL.attack"}}

      @@ -28,6 +28,11 @@
    +
    + +

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

    + +
    {{#each source.system.experiences as |experience id|}}
    From aa2714e02136a29d1a79d7b1fbfc1df73a58d2f9 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:59:01 +0200 Subject: [PATCH 06/22] [Fix] Automation Fixes (#342) * Initial * Split HopeFear automation by GM and Players * Fixed ActionToken automation --- daggerheart.mjs | 3 +- lang/en.json | 7 ++- module/applications/hud/tokenHUD.mjs | 2 +- .../applications/sheets/actors/character.mjs | 5 +- module/applications/ui/combatTracker.mjs | 17 +++++- module/applications/ui/countdowns.mjs | 59 +++++++------------ module/config/_module.mjs | 1 - module/config/generalConfig.mjs | 6 +- module/config/hooksConfig.mjs | 4 -- module/config/system.mjs | 2 - module/data/action/attackAction.mjs | 9 +++ module/data/countdowns.mjs | 10 +++- module/data/settings/Automation.mjs | 22 +++---- module/dice/dhRoll.mjs | 9 +-- templates/settings/automation-settings.hbs | 7 ++- 15 files changed, 87 insertions(+), 76 deletions(-) delete mode 100644 module/config/hooksConfig.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index a1742641..aeae0dc6 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -5,7 +5,7 @@ import * as documents from './module/documents/_module.mjs'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; import { DhDualityRollEnricher, DhTemplateEnricher } from './module/enrichers/_module.mjs'; import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; -import { NarrativeCountdowns, registerCountdownApplicationHooks } from './module/applications/ui/countdowns.mjs'; +import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs'; import { DualityRollColor } from './module/data/settings/Appearance.mjs'; import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs'; import { renderDualityButton } from './module/enrichers/DualityRollEnricher.mjs'; @@ -168,7 +168,6 @@ Hooks.on('ready', () => { registerCountdownHooks(); socketRegistration.registerSocketHooks(); - registerCountdownApplicationHooks(); registerRollDiceHooks(); registerDHActorHooks(); }); diff --git a/lang/en.json b/lang/en.json index 1adf4bc6..3bf99e3a 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1337,9 +1337,10 @@ "hint": "Automatically increase the GM's fear pool on a fear duality roll result." }, "FIELDS": { - "hope": { - "label": "Hope", - "hint": "Automatically increase a character's hope on a hope duality roll result." + "hopeFear": { + "label": "Hope & Fear", + "gm": { "label": "GM" }, + "players": { "label": "Players" } }, "actionPoints": { "label": "Action Points", diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs index 9a58bab2..572b03f9 100644 --- a/module/applications/hud/tokenHUD.mjs +++ b/module/applications/hud/tokenHUD.mjs @@ -1,4 +1,4 @@ -export default class DHTokenHUD extends TokenHUD { +export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { static DEFAULT_OPTIONS = { classes: ['daggerheart'] }; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index fcd92842..139a1369 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -669,10 +669,7 @@ export default class CharacterSheet extends DHBaseActorSheet { } else if (item instanceof ActiveEffect) { item.toChat(this); } else { - const wasUsed = await item.use(event); - if (wasUsed && item.type === 'weapon') { - Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {}); - } + item.use(event); } } diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index f9f49ad1..b3348fe2 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -66,6 +66,11 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C } async setCombatantSpotlight(combatantId) { + const update = { + system: { + 'spotlight.requesting': false + } + }; const combatant = this.viewed.combatants.get(combatantId); const toggleTurn = this.viewed.combatants.contents @@ -73,10 +78,18 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C .map(x => x.id) .indexOf(combatantId); - if (this.viewed.turn !== toggleTurn) Hooks.callAll(CONFIG.DH.HOOKS.spotlight, {}); + if (this.viewed.turn !== toggleTurn) { + const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; + await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.spotlight.id); + + const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints; + if (autoPoints) { + update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0); + } + } await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn }); - await combatant.update({ 'system.spotlight.requesting': false }); + await combatant.update(update); } static async requestSpotlight(_, target) { diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index c229cda1..5e3ad1ab 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -1,4 +1,3 @@ -import { countdownTypes } from '../../config/generalConfig.mjs'; import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import constructHTMLButton from '../../helpers/utils.mjs'; import OwnershipSelection from '../dialogs/ownershipSelection.mjs'; @@ -328,43 +327,29 @@ export class EncounterCountdowns extends Countdowns { }; } -export const registerCountdownApplicationHooks = () => { - const updateCountdowns = async shouldProgress => { - if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).countdowns) { - const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - for (let countdownCategoryKey in countdownSetting) { - const countdownCategory = countdownSetting[countdownCategoryKey]; - for (let countdownKey in countdownCategory.countdowns) { - const countdown = countdownCategory.countdowns[countdownKey]; - - if (shouldProgress(countdown)) { - await countdownSetting.updateSource({ - [`${countdownCategoryKey}.countdowns.${countdownKey}.progress.current`]: - countdown.progress.current - 1 - }); - await game.settings.set( - CONFIG.DH.id, - CONFIG.DH.SETTINGS.gameSettings.Countdowns, - countdownSetting - ); - foundry.applications.instances.get(`${countdownCategoryKey}-countdowns`)?.render(); - } +export async function updateCountdowns(progressType) { + const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); + const update = Object.keys(countdownSetting).reduce((update, typeKey) => { + return foundry.utils.mergeObject( + update, + Object.keys(countdownSetting[typeKey].countdowns).reduce((acc, countdownKey) => { + const countdown = countdownSetting[typeKey].countdowns[countdownKey]; + if (countdown.progress.current > 0 && countdown.progress.type.value === progressType) { + acc[`${typeKey}.countdowns.${countdownKey}.progress.current`] = countdown.progress.current - 1; } - } - } - }; - Hooks.on(CONFIG.DH.HOOKS.characterAttack, async () => { - updateCountdowns(countdown => { - return ( - countdown.progress.type.value === countdownTypes.characterAttack.id && countdown.progress.current > 0 - ); - }); - }); + return acc; + }, {}) + ); + }, {}); - Hooks.on(CONFIG.DH.HOOKS.spotlight, async () => { - updateCountdowns(countdown => { - return countdown.progress.type.value === countdownTypes.spotlight.id && countdown.progress.current > 0; - }); + await countdownSetting.updateSource(update); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting); + + const data = { refreshType: RefreshType.Countdown }; + await game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data }); -}; + Hooks.callAll(socketEvent.Refresh, data); +} diff --git a/module/config/_module.mjs b/module/config/_module.mjs index 88003595..99069dda 100644 --- a/module/config/_module.mjs +++ b/module/config/_module.mjs @@ -4,7 +4,6 @@ export * as domainConfig from './domainConfig.mjs'; export * as effectConfig from './effectConfig.mjs'; export * as flagsConfig from './flagsConfig.mjs'; export * as generalConfig from './generalConfig.mjs'; -export * as hooksConfig from './hooksConfig.mjs'; export * as itemConfig from './itemConfig.mjs'; export * as settingsConfig from './settingsConfig.mjs'; export * as systemConfig from './system.mjs'; diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 3522b41a..54430860 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -376,15 +376,15 @@ export const abilityCosts = { export const countdownTypes = { spotlight: { id: 'spotlight', - label: 'DAGGERHEART.CONFIG.CountdownTypes.Spotlight' + label: 'DAGGERHEART.CONFIG.CountdownType.spotlight' }, characterAttack: { id: 'characterAttack', - label: 'DAGGERHEART.CONFIG.CountdownTypes.CharacterAttack' + label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack' }, custom: { id: 'custom', - label: 'DAGGERHEART.CONFIG.CountdownTypes.Custom' + label: 'DAGGERHEART.CONFIG.CountdownType.custom' } }; export const rollTypes = { diff --git a/module/config/hooksConfig.mjs b/module/config/hooksConfig.mjs deleted file mode 100644 index 8410c0de..00000000 --- a/module/config/hooksConfig.mjs +++ /dev/null @@ -1,4 +0,0 @@ -export const hooks = { - characterAttack: 'characterAttackHook', - spotlight: 'spotlightHook' -}; diff --git a/module/config/system.mjs b/module/config/system.mjs index 6ad0e689..e72667b1 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -3,7 +3,6 @@ import * as DOMAIN from './domainConfig.mjs'; import * as ACTOR from './actorConfig.mjs'; import * as ITEM from './itemConfig.mjs'; import * as SETTINGS from './settingsConfig.mjs'; -import { hooks as HOOKS } from './hooksConfig.mjs'; import * as EFFECTS from './effectConfig.mjs'; import * as ACTIONS from './actionConfig.mjs'; import * as FLAGS from './flagsConfig.mjs'; @@ -17,7 +16,6 @@ export const SYSTEM = { ACTOR, ITEM, SETTINGS, - HOOKS, EFFECTS, ACTIONS, FLAGS diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index 137879b8..e17c0e9d 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -38,6 +38,15 @@ export default class DHAttackAction extends DHDamageAction { }; } + async use(event, ...args) { + const result = await super.use(event, args); + + const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; + await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id); + + return result; + } + // get modifiers() { // return []; // } diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index e9649f6e..881ecf20 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -102,7 +102,7 @@ class DhCountdown extends foundry.abstract.DataModel { value: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.countdownTypes, - initial: CONFIG.DH.GENERAL.countdownTypes.spotlight.id, + initial: CONFIG.DH.GENERAL.countdownTypes.custom.id, label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.value.label' }), label: new fields.StringField({ @@ -132,7 +132,13 @@ class DhCountdown extends foundry.abstract.DataModel { export const registerCountdownHooks = () => { Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => { if (refreshType === RefreshType.Countdown) { - foundry.applications.instances.get(application)?.render(); + if (application) { + foundry.applications.instances.get(application)?.render(); + } else { + foundry.applications.instances.get('narrative-countdowns').render(); + foundry.applications.instances.get('encounter-countdowns').render(); + } + return false; } }); diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index 4e375919..4291423b 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -4,20 +4,22 @@ export default class DhAutomation extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; return { - hope: new fields.BooleanField({ - required: true, - initial: false, - label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hope.label' - }), // Label need to be updated into something like "Duality Roll Auto Gain" + a hint + hopeFear: new fields.SchemaField({ + gm: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.gm.label' + }), + players: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label' + }) + }), actionPoints: new fields.BooleanField({ required: true, initial: false, label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label' - }), - countdowns: new fields.BooleanField({ - requireD: true, - initial: false, - label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdowns.label' }) }; } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 1bd9a6e5..22f5bb28 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -175,9 +175,10 @@ export default class DHRoll extends Roll { export const registerRollDiceHooks = () => { Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => { + const hopeFearAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hopeFear; if ( !config.source?.actor || - !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hope || + (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) || config.roll.type === 'reaction' ) return; @@ -185,9 +186,9 @@ export const registerRollDiceHooks = () => { const actor = await fromUuid(config.source.actor), updates = []; if (!actor) return; - if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ type: 'hope', value: 1 }); - if (config.roll.isCritical) updates.push({ type: 'stress', value: -1 }); - if (config.roll.result.duality === -1) updates.push({ type: 'fear', value: 1 }); + if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ key: 'hope', value: 1 }); + if (config.roll.isCritical) updates.push({ key: 'stress', value: -1 }); + if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1 }); if (updates.length) actor.modifyResource(updates); diff --git a/templates/settings/automation-settings.hbs b/templates/settings/automation-settings.hbs index 7a637d08..87a48c06 100644 --- a/templates/settings/automation-settings.hbs +++ b/templates/settings/automation-settings.hbs @@ -1,5 +1,10 @@
    - {{formGroup settingFields.schema.fields.hope value=settingFields._source.hope localize=true}} +
    + + {{formGroup settingFields.schema.fields.hopeFear.fields.gm value=settingFields._source.hopeFear.gm localize=true}} + {{formGroup settingFields.schema.fields.hopeFear.fields.players value=settingFields._source.hopeFear.players localize=true}} + +
    {{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}} {{formGroup settingFields.schema.fields.countdowns value=settingFields._source.countdowns localize=true}} From da6d9418b70448e3bd0c954dcff727bde3ea40ae Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:15:58 +0200 Subject: [PATCH 07/22] Fixes tokenHUD import path (#348) --- module/applications/hud/_module.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/hud/_module.mjs b/module/applications/hud/_module.mjs index 70edaf8f..92abe4e8 100644 --- a/module/applications/hud/_module.mjs +++ b/module/applications/hud/_module.mjs @@ -1 +1 @@ -export { default as DHTokenHUD } from './tokenHud.mjs'; +export { default as DHTokenHUD } from './tokenHUD.mjs'; From 045754d107295eccf4580b66be03651f36e58f53 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 15 Jul 2025 17:01:17 +0200 Subject: [PATCH 08/22] [Feature] 340-341 RollMode & ChatSpeaker (#347) * Added RollMode to standalone DamageDialog and to RollDialog. ChatMessage now add ChatSpeaker * Just a little fix for Damage Action --------- Co-authored-by: Dapoolp --- lang/en.json | 1 + module/applications/dialogs/d20RollDialog.mjs | 14 +++-- module/applications/dialogs/damageDialog.mjs | 12 ++++- module/applications/ui/chatLog.mjs | 2 +- module/data/action/baseAction.mjs | 3 +- module/data/action/damageAction.mjs | 13 +++-- module/dice/dhRoll.mjs | 2 +- module/documents/chatMessage.mjs | 11 ++++ .../less/dialog/damage-selection/sheet.less | 54 ++++++++++++------- .../less/dialog/dice-roll/roll-selection.less | 14 +++++ .../dialogs/dice-roll/damageSelection.hbs | 15 ++++-- templates/dialogs/dice-roll/rollSelection.hbs | 13 +++-- 12 files changed, 112 insertions(+), 42 deletions(-) diff --git a/lang/en.json b/lang/en.json index 3bf99e3a..b9e0fefa 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1231,6 +1231,7 @@ "quantity": "Quantity", "range": "Range", "recovery": "Recovery", + "roll": "Roll", "scalable": "Scalable", "stress": "Stress", "take": "Take", diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 6cb0761c..7987dd6b 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -64,6 +64,13 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio context.rollConfig = this.config; context.hasRoll = !!this.config.roll; context.canRoll = true; + context.selectedRollMode = this.config.selectedRollMode; + context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({ + action, + label, + icon + })); + if (this.config.costs?.length) { const updatedCosts = this.action.calcCosts(this.config.costs); context.costs = updatedCosts.map(x => ({ @@ -99,6 +106,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio static updateRollConfiguration(event, _, formData) { const { ...rest } = foundry.utils.expandObject(formData.object); + this.config.selectedRollMode = rest.selectedRollMode; + if (this.config.costs) { this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs); } @@ -122,11 +131,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio } static selectExperience(_, button) { - /* if (this.config.experiences.find(x => x === button.dataset.key)) { - this.config.experiences = this.config.experiences.filter(x => x !== button.dataset.key); - } else { - this.config.experiences = [...this.config.experiences, button.dataset.key]; - } */ this.config.experiences = this.config.experiences.indexOf(button.dataset.key) > -1 ? this.config.experiences.filter(x => x !== button.dataset.key) diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index 4030d7a7..70dcace8 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -48,12 +48,22 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application : game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name'); context.extraFormula = this.config.extraFormula; context.formula = this.roll.constructFormula(this.config); + context.directDamage = this.config.directDamage; + context.selectedRollMode = this.config.selectedRollMode; + context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({ + action, + label, + icon + })); + return context; } - static updateRollConfiguration(event, _, formData) { + static updateRollConfiguration(_event, _, formData) { const { ...rest } = foundry.utils.expandObject(formData.object); this.config.extraFormula = rest.extraFormula; + this.config.selectedRollMode = rest.selectedRollMode; + this.render(); } diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index aa604dda..4570b076 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -88,7 +88,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo onRollDamage = async (event, message) => { event.stopPropagation(); const actor = await this.getActor(message.system.source.actor); - if (!actor || !game.user.isGM) return true; + if (game.user.character?.id !== actor.id && !game.user.isGM) return true; if (message.system.source.item && message.system.source.action) { const action = this.getAction(actor, message.system.source.item, message.system.source.action); if (!action || !action?.rollDamage) return; diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 39051434..a46b0a85 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -268,7 +268,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel { hasDamage: !!this.damage?.parts?.length, hasHealing: !!this.healing, hasEffect: !!this.effects?.length, - hasSave: this.hasSave + hasSave: this.hasSave, + selectedRollMode: game.settings.get('core', 'rollMode') }; } diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 492c4184..388c5eb8 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -10,6 +10,7 @@ export default class DHDamageAction extends DHBaseAction { } async rollDamage(event, data) { + const systemData = data.system ?? data; let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '), damageTypes = [...new Set(this.damage.parts.reduce((a, c) => a.concat([...c.type]), []))]; @@ -19,15 +20,15 @@ export default class DHDamageAction extends DHBaseAction { let roll = { formula: formula, total: formula }, bonusDamage = []; - if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data)); - + if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData)); + const config = { title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }), roll: { formula }, - targets: data.system?.targets.filter(t => t.hit) ?? data.targets, + targets: systemData.targets.filter(t => t.hit) ?? data.targets, hasSave: this.hasSave, - isCritical: data.system?.roll?.isCritical ?? false, - source: data.system?.source, + isCritical: systemData.roll?.isCritical ?? false, + source: systemData.source, data: this.getRollData(), damageTypes, event @@ -36,6 +37,8 @@ export default class DHDamageAction extends DHBaseAction { if (data.system) { config.source.message = data._id; config.directDamage = false; + } else { + config.directDamage = true; } roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 22f5bb28..27288f15 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -87,7 +87,7 @@ export default class DHRoll extends Roll { system: config, rolls: [roll] }; - return await cls.create(msg); + return await cls.create(msg, { rollMode: config.selectedRollMode }); } static applyKeybindings(config) { diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index ef76d18f..409b4dd0 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -37,4 +37,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER')); }); } + + async _preCreate(data, options, user) { + options.speaker = ChatMessage.getSpeaker(); + const rollActorOwner = data.rolls?.[0]?.data?.parent?.owner; + if (rollActorOwner) { + data.author = rollActorOwner ? rollActorOwner.id : data.author; + await this.updateSource({ author: rollActorOwner ?? user }); + } + + return super._preCreate(data, options, rollActorOwner ?? user); + } } diff --git a/styles/less/dialog/damage-selection/sheet.less b/styles/less/dialog/damage-selection/sheet.less index 43e4f4d2..461fb0b5 100644 --- a/styles/less/dialog/damage-selection/sheet.less +++ b/styles/less/dialog/damage-selection/sheet.less @@ -1,20 +1,34 @@ -@import '../../utils/colors.less'; - -.daggerheart.dialog.dh-style.views.damage-selection { - .damage-section-container { - display: flex; - flex-direction: column; - gap: 12px; - - input[type='text'], - input[type='number'] { - color: light-dark(@dark, @beige); - outline: 2px solid transparent; - transition: all 0.3s ease; - - &:hover { - outline: 2px solid light-dark(@dark, @beige); - } - } - } -} +@import '../../utils/colors.less'; + +.daggerheart.dialog.dh-style.views.damage-selection { + .damage-section-container { + display: flex; + flex-direction: column; + gap: 12px; + + input[type='text'], + input[type='number'] { + color: light-dark(@dark, @beige); + outline: 2px solid transparent; + transition: all 0.3s ease; + + &:hover { + outline: 2px solid light-dark(@dark, @beige); + } + } + + .damage-section-controls { + display: flex; + align-items: center; + gap: 16px; + + .roll-mode-select { + width: min-content; + } + + button { + flex: 1; + } + } + } +} diff --git a/styles/less/dialog/dice-roll/roll-selection.less b/styles/less/dialog/dice-roll/roll-selection.less index 575b7ce9..14bccf3d 100644 --- a/styles/less/dialog/dice-roll/roll-selection.less +++ b/styles/less/dialog/dice-roll/roll-selection.less @@ -114,5 +114,19 @@ } } } + + .roll-dialog-controls { + display: flex; + align-items: center; + gap: 16px; + + .roll-mode-select { + width: min-content; + } + + button { + flex: 1; + } + } } } diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index 0286990e..bd97cfdf 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -6,8 +6,15 @@
    - +
    + {{#if directDamage}} + + {{/if}} + +
    \ No newline at end of file diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index 5c0ba41d..11fce27a 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -117,10 +117,15 @@ {{/unless}} Formula: {{@root.formula}} - +
    + + +
    {{else}}
    {{/if}} {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}} + {{#if ../horde}} +
    + {{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}} +
    + {{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." index ".valueAlt.flatMultiplier") label="Multiplier" classes="inline-child" }} + {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." index ".valueAlt.dice") classes="inline-child"}} + {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." index ".valueAlt.bonus") localize=true classes="inline-child"}} +
    +
    + {{/if}} {{else}} {{#with (@root.getRealIndex index) as | realIndex |}}
    @@ -33,11 +43,11 @@ {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
    - With Hope + {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}} {{> formula fields=../../fields.value.fields type=../../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}}
    - With Fear + {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}} {{> formula fields=../../fields.valueAlt.fields type=../../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" realIndex=realIndex}}
    diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index 11fce27a..0d2f3f48 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -5,7 +5,7 @@
    {{#if (eq @root.rollType 'D20Roll')}}
    - +
    +
    + {{#unless (eq @root.rollType 'D20Roll')}} +
    + + - {{/unless}} -
    - +
    + {{/unless}} + {{#if @root.rallyDie.length}} + {{localize "DAGGERHEART.CLASS.Feature.rallyDice"}} + + {{/if}} + {{#if (eq @root.rollType 'DualityRoll')}}Situational Bonus{{/if}} + {{/unless}} Formula: {{@root.formula}} diff --git a/templates/ui/chat/duality-roll.hbs b/templates/ui/chat/duality-roll.hbs index 66d32e95..8b44039b 100644 --- a/templates/ui/chat/duality-roll.hbs +++ b/templates/ui/chat/duality-roll.hbs @@ -16,6 +16,11 @@ {{localize "DAGGERHEART.GENERAL.Disadvantage.full"}}
    {{/if}} + {{#if roll.rally.dice}} +
    + {{localize "DAGGERHEART.CLASS.Feature.rallyDice"}} {{roll.rally.dice}} +
    + {{/if}}
    {{roll.formula}}
    @@ -38,7 +43,7 @@
    {{localize "DAGGERHEART.GENERAL.hope"}}
    - +
    {{roll.hope.value}}
    @@ -49,7 +54,7 @@
    {{localize "DAGGERHEART.GENERAL.fear"}}
    - +
    {{roll.fear.value}}
    @@ -72,7 +77,7 @@
    - +
    {{roll.advantage.value}}
    @@ -82,6 +87,30 @@
    {{/if}} + {{#if roll.rally.dice}} +
    +
    + + 1{{roll.rally.dice}} + + {{roll.rally.value}} +
    +
    +
      +
    1. +
      +
      +
      + +
      +
      {{roll.rally.value}}
      +
      +
      +
    2. +
    +
    +
    + {{/if}} {{#each roll.extra as | extra | }}
    From d7e024be02b3b938c0dc898ed0fa8eeb2fea8954 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:01:15 +0200 Subject: [PATCH 17/22] [Feature] Advantage/Disadvantage Tooltips (#362) * Added advantage/disadvantageSource to Character model. It's shown from a tooltip icon on rolls * Added support for beastform advantageOn --- module/data/actor/character.mjs | 2 ++ module/data/item/beastform.mjs | 1 + module/documents/tooltipManager.mjs | 18 ++++++++++++++++++ .../less/dialog/dice-roll/roll-selection.less | 4 ++++ templates/dialogs/dice-roll/rollSelection.hbs | 6 ++++++ templates/ui/tooltip/advantage.hbs | 5 +++++ 6 files changed, 36 insertions(+) create mode 100644 templates/ui/tooltip/advantage.hbs diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 20bada01..6fe66a53 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -87,6 +87,8 @@ export default class DhCharacter extends BaseDataActor { value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) }), + advantageSources: new fields.ArrayField(new fields.StringField()), + disadvantageSources: new fields.ArrayField(new fields.StringField()), levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ roll: new fields.SchemaField({ diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index b7ea5cb9..d19521d0 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -69,6 +69,7 @@ export default class DHBeastform extends BaseDataItem { const beastformEffect = this.parent.effects.find(x => x.type === 'beastform'); await beastformEffect.updateSource({ + changes: [...beastformEffect.changes, { key: 'system.advantageSources', mode: 2, value: this.advantageOn }], system: { characterTokenData: { tokenImg: this.parent.parent.prototypeToken.texture.src, diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index d9444207..f24823f4 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -21,6 +21,24 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti this.tooltip.innerHTML = html; options.direction = this._determineItemTooltipDirection(element); } + } else { + const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#'); + const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#'); + if (isAdvantage || isDisadvantage) { + const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14); + const actor = await foundry.utils.fromUuid(actorUuid); + + if (actor) { + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/advantage.hbs`, + { + sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources + } + ); + + this.tooltip.innerHTML = html; + } + } } super.activate(element, { ...options, html: html }); diff --git a/styles/less/dialog/dice-roll/roll-selection.less b/styles/less/dialog/dice-roll/roll-selection.less index 55db8bb7..af6c3c20 100644 --- a/styles/less/dialog/dice-roll/roll-selection.less +++ b/styles/less/dialog/dice-roll/roll-selection.less @@ -100,6 +100,10 @@ font-size: 14px; line-height: 17px; } + + .advantage-chip-tooltip { + pointer-events: all; + } } .advantage-chip { diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index b4c7ccac..7c894fd8 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -98,6 +98,9 @@ {{/if}} {{localize "DAGGERHEART.GENERAL.Advantage.full"}} + {{#if @root.rollConfig.data.advantageSources.length}} + + {{/if}}
    {{#unless (eq @root.rollType 'D20Roll')}} diff --git a/templates/ui/tooltip/advantage.hbs b/templates/ui/tooltip/advantage.hbs new file mode 100644 index 00000000..886f336d --- /dev/null +++ b/templates/ui/tooltip/advantage.hbs @@ -0,0 +1,5 @@ +
    + {{#each sources as | source |}} +
    {{{source}}}
    + {{/each}} +
    \ No newline at end of file From f15483c7229a782c6b3b8f9be1d2d0f79673e105 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:03:54 +0200 Subject: [PATCH 18/22] Feature/349 items actions macro use (#356) * Added itemUse macro on drag to hotbar * Fixed item.type logic * Added support for actionMacro drag from items * Added MacroDrag for Attacks * Fixed so UseItem macros get the img set --- daggerheart.mjs | 1 + lang/en.json | 6 +- module/applications/sheets/api/base-actor.mjs | 25 +++- module/applications/sheets/api/base-item.mjs | 20 ++- module/applications/ui/_module.mjs | 1 + module/applications/ui/hotbar.mjs | 129 ++++++++++++++++++ module/data/countdowns.mjs | 4 +- 7 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 module/applications/ui/hotbar.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index 0d78e8fe..22586e90 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -140,6 +140,7 @@ Hooks.once('init', () => { CONFIG.Combat.documentClass = documents.DhpCombat; CONFIG.ui.combat = applications.ui.DhCombatTracker; CONFIG.ui.chat = applications.ui.DhChatLog; + CONFIG.ui.hotbar = applications.ui.DhHotbar; CONFIG.Token.rulerClass = placeables.DhTokenRuler; CONFIG.ui.resources = applications.ui.DhFearTracker; diff --git a/lang/en.json b/lang/en.json index 75f2b886..3456b6b5 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1537,7 +1537,11 @@ "noAvailableArmorMarks": "You have no more available armor marks", "notEnoughStress": "You don't have enough stress", "damageIgnore": "{character} did not take damage", - "featureIsMissing": "Feature is missing" + "featureIsMissing": "Feature is missing", + "actionIsMissing": "Action is missing", + "attackIsMissing": "Attack is missing", + "unownedActionMacro": "Cannot make a Use macro for an Action not on your character", + "unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters" }, "Tooltip": { "openItemWorld": "Open Item World", diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 7102fa1c..78df0aac 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -23,7 +23,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { actions: { openSettings: DHBaseActorSheet.#openSettings }, - dragDrop: [] + dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }] }; /**@type {typeof DHBaseActorSettings}*/ @@ -49,4 +49,27 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { static async #openSettings() { await this.settingSheet.render({ force: true }); } + + /* -------------------------------------------- */ + /* Application Drag/Drop */ + /* -------------------------------------------- */ + + /** + * On dragStart on the item. + * @param {DragEvent} event - The drag event + */ + async _onDragStart(event) { + const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]'); + + if (attackItem) { + const attackData = { + type: 'Attack', + actorUuid: this.document.uuid, + img: this.document.system.attack.img, + fromInternal: true + }; + event.dataTransfer.setData('text/plain', JSON.stringify(attackData)); + event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0); + } + } } diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index 0c25a44d..723b4802 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -30,7 +30,8 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { }, dragDrop: [ { dragSelector: null, dropSelector: '.tab.features .drop-section' }, - { dragSelector: '.feature-item', dropSelector: null } + { dragSelector: '.feature-item', dropSelector: null }, + { dragSelector: '.action-item', dropSelector: null } ] }; @@ -258,6 +259,23 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true }; event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); + } else { + const actionItem = event.currentTarget.closest('.action-item'); + if (actionItem) { + const action = this.document.system.actions[actionItem.dataset.index]; + if (!action) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionIsMissing')); + return; + } + + const actionData = { + type: 'Action', + data: { ...action.toObject(), id: action.id, itemUuid: this.document.uuid }, + fromInternal: true + }; + event.dataTransfer.setData('text/plain', JSON.stringify(actionData)); + event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0); + } } } diff --git a/module/applications/ui/_module.mjs b/module/applications/ui/_module.mjs index f1c32840..6a17a61e 100644 --- a/module/applications/ui/_module.mjs +++ b/module/applications/ui/_module.mjs @@ -2,3 +2,4 @@ export { default as DhChatLog } from './chatLog.mjs'; export { default as DhCombatTracker } from './combatTracker.mjs'; export * as DhCountdowns from './countdowns.mjs'; export { default as DhFearTracker } from './fearTracker.mjs'; +export { default as DhHotbar } from './hotbar.mjs'; diff --git a/module/applications/ui/hotbar.mjs b/module/applications/ui/hotbar.mjs new file mode 100644 index 00000000..b4ebc05c --- /dev/null +++ b/module/applications/ui/hotbar.mjs @@ -0,0 +1,129 @@ +export default class DhHotbar extends foundry.applications.ui.Hotbar { + constructor(options) { + super(options); + + this.setupHooks(); + } + + static async useItem(uuid) { + const item = await fromUuid(uuid); + if (!item) { + return ui.notifications.warn('WARNING.ObjectDoesNotExist', { + format: { + name: game.i18n.localize('Document'), + identifier: uuid + } + }); + } + + await item.use({}); + } + + static async useAction(itemUuid, actionId) { + const item = await foundry.utils.fromUuid(itemUuid); + if (!item) { + return ui.notifications.warn('WARNING.ObjectDoesNotExist', { + format: { + name: game.i18n.localize('Document'), + identifier: itemUuid + } + }); + } + + const action = item.system.actions.find(x => x.id === actionId); + if (!action) { + return ui.notifications.warn('DAGGERHEART.UI.Notifications.actionIsMissing'); + } + + await action.use({}); + } + + static async useAttack(actorUuid) { + const actor = await foundry.utils.fromUuid(actorUuid); + if (!actor) { + return ui.notifications.warn('WARNING.ObjectDoesNotExist', { + format: { + name: game.i18n.localize('Document'), + identifier: actorUuid + } + }); + } + + const attack = actor.system.attack; + if (!attack) { + return ui.notifications.warn('DAGGERHEART.UI.Notifications.attackIsMissing'); + } + + await attack.use({}); + } + + setupHooks() { + Hooks.on('hotbarDrop', (bar, data, slot) => { + if (data.type === 'Item') { + const item = foundry.utils.fromUuidSync(data.uuid); + if (item.uuid.startsWith('Compendium') || !item.isOwned || !item.isOwner) return true; + + switch (item.type) { + case 'ancestry': + case 'community': + case 'class': + case 'subclass': + return true; + default: + this.createItemMacro(item, slot); + return false; + } + } else if (data.type === 'Action') { + const item = foundry.utils.fromUuidSync(data.data.itemUuid); + if (item.uuid.startsWith('Compendium')) return true; + if (!item.isOwned || !item.isOwner) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedActionMacro')); + return false; + } + + this.createActionMacro(data, slot); + return false; + } else if (data.type === 'Attack') { + const actor = foundry.utils.fromUuidSync(data.actorUuid); + if (actor.uuid.startsWith('Compendium')) return true; + if (!actor.isOwner) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedAttackMacro')); + return false; + } + + this.createAttackMacro(data, slot); + return false; + } + }); + } + + async createItemMacro(data, slot) { + const macro = await Macro.implementation.create({ + name: `${game.i18n.localize('Display')} ${name}`, + type: CONST.MACRO_TYPES.SCRIPT, + img: data.img, + command: `await game.system.api.applications.ui.DhHotbar.useItem("${data.uuid}");` + }); + await game.user.assignHotbarMacro(macro, slot); + } + + async createActionMacro(data, slot) { + const macro = await Macro.implementation.create({ + name: `${game.i18n.localize('Display')} ${name}`, + type: CONST.MACRO_TYPES.SCRIPT, + img: data.data.img, + command: `await game.system.api.applications.ui.DhHotbar.useAction("${data.data.itemUuid}", "${data.data.id}");` + }); + await game.user.assignHotbarMacro(macro, slot); + } + + async createAttackMacro(data, slot) { + const macro = await Macro.implementation.create({ + name: `${game.i18n.localize('Display')} ${name}`, + type: CONST.MACRO_TYPES.SCRIPT, + img: data.img, + command: `await game.system.api.applications.ui.DhHotbar.useAttack("${data.actorUuid}");` + }); + await game.user.assignHotbarMacro(macro, slot); + } +} diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 881ecf20..34e8b790 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -135,8 +135,8 @@ export const registerCountdownHooks = () => { if (application) { foundry.applications.instances.get(application)?.render(); } else { - foundry.applications.instances.get('narrative-countdowns').render(); - foundry.applications.instances.get('encounter-countdowns').render(); + foundry.applications.instances.get('narrative-countdowns')?.render(); + foundry.applications.instances.get('encounter-countdowns')?.render(); } return false; From 1d5e26728540b065034a4e9551dad65b28adef91 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:07:11 +0200 Subject: [PATCH 19/22] Improved the datastructure some to avoid errors and simplify useage (#361) --- module/applications/dialogs/resourceDiceDialog.mjs | 2 +- module/data/actor/character.mjs | 10 +++++----- module/data/item/base.mjs | 7 +++++-- templates/dialogs/dice-roll/resourceDice.hbs | 8 ++++---- templates/sheets/global/partials/item-resource.hbs | 2 +- templates/sheets/global/partials/resource-section.hbs | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/module/applications/dialogs/resourceDiceDialog.mjs b/module/applications/dialogs/resourceDiceDialog.mjs index b79ff895..8205dee5 100644 --- a/module/applications/dialogs/resourceDiceDialog.mjs +++ b/module/applications/dialogs/resourceDiceDialog.mjs @@ -67,7 +67,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli static async rerollDice() { const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item); - const diceFormula = `${max}d${this.item.system.resource.dieFaces}`; + const diceFormula = `${max}${this.item.system.resource.dieFaces}`; const roll = await new Roll(diceFormula).evaluate(); if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true); this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false })); diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 6fe66a53..6b0a2fd7 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -252,13 +252,13 @@ export default class DhCharacter extends BaseDataActor { features = []; for (let item of this.parent.items) { - if (item.system.type === CONFIG.DH.ITEM.featureTypes.ancestry.id) { + if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) { ancestryFeatures.push(item); - } else if (item.system.type === CONFIG.DH.ITEM.featureTypes.community.id) { + } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) { communityFeatures.push(item); - } else if (item.system.type === CONFIG.DH.ITEM.featureTypes.class.id) { + } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) { classFeatures.push(item); - } else if (item.system.type === CONFIG.DH.ITEM.featureTypes.subclass.id) { + } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { const subclassState = this.class.subclass.system.featureState; const identifier = item.system.identifier; if ( @@ -268,7 +268,7 @@ export default class DhCharacter extends BaseDataActor { ) { subclassFeatures.push(item); } - } else if (item.system.type === CONFIG.DH.ITEM.featureTypes.companion.id) { + } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) { companionFeatures.push(item); } else if (item.type === 'feature' && !item.system.type) { features.push(item); diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index e99c85c3..24e5e0cc 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -53,11 +53,14 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { }), diceStates: new fields.TypedObjectField( new fields.SchemaField({ - value: new fields.NumberField({ integer: true, nullable: true, initial: null }), + value: new fields.NumberField({ integer: true, initial: 1, min: 1 }), used: new fields.BooleanField({ initial: false }) }) ), - dieFaces: new fields.StringField({ initial: '4' }) + dieFaces: new fields.StringField({ + choices: CONFIG.DH.GENERAL.diceTypes, + initial: CONFIG.DH.GENERAL.diceTypes.d4 + }) }, { nullable: true, initial: null } ); diff --git a/templates/dialogs/dice-roll/resourceDice.hbs b/templates/dialogs/dice-roll/resourceDice.hbs index 33c93386..bebe8f4e 100644 --- a/templates/dialogs/dice-roll/resourceDice.hbs +++ b/templates/dialogs/dice-roll/resourceDice.hbs @@ -3,14 +3,14 @@ {{#times (rollParsed item.system.resource.max actor item numerical=true)}} {{#with (ifThen (lookup ../diceStates this) (lookup ../diceStates this) this) as | state |}}
    - - + +
    {{/with}} {{/times}}
    - - + +
    \ No newline at end of file diff --git a/templates/sheets/global/partials/item-resource.hbs b/templates/sheets/global/partials/item-resource.hbs index 9b92dea0..d90f0b3f 100644 --- a/templates/sheets/global/partials/item-resource.hbs +++ b/templates/sheets/global/partials/item-resource.hbs @@ -10,7 +10,7 @@
    - + {{#if state.used}}{{/if}}
    diff --git a/templates/sheets/global/partials/resource-section.hbs b/templates/sheets/global/partials/resource-section.hbs index f0d322d3..ab329efc 100644 --- a/templates/sheets/global/partials/resource-section.hbs +++ b/templates/sheets/global/partials/resource-section.hbs @@ -19,7 +19,7 @@ {{formGroup systemFields.resource.fields.value value=source.system.resource.value localize=true}} {{formGroup systemFields.resource.fields.max value=source.system.resource.max localize=true}} {{else}} - {{formGroup systemFields.resource.fields.dieFaces value=source.system.resource.dieFaces localize=true}} + {{formGroup systemFields.resource.fields.dieFaces value=source.system.resource.dieFaces localize=true blank=false}} {{formGroup systemFields.resource.fields.max value=source.system.resource.max label="DAGGERHEART.ITEMS.FIELDS.resource.amount.label" localize=true}} {{/if}}
    From 0cc1597dfe1a37566180dca810f892c28556610e Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:43:35 +0200 Subject: [PATCH 20/22] [Fix] Class Feature Change (#364) * Changed to just use a features field * Subclass now uses a simple Features field --- lang/en.json | 11 ++- .../characterCreation/characterCreation.mjs | 4 +- module/applications/sheets/items/ancestry.mjs | 25 ++----- module/applications/sheets/items/class.mjs | 48 ++++++++----- module/applications/sheets/items/subclass.mjs | 69 +++++++++++++++---- module/config/actorConfig.mjs | 2 +- module/config/itemConfig.mjs | 7 +- module/data/actor/character.mjs | 17 ++--- module/data/item/class.mjs | 17 +++-- module/data/item/domainCard.mjs | 1 - module/data/item/subclass.mjs | 22 +++--- .../sheets/items/domainCard/settings.hbs | 2 - templates/sheets/items/subclass/features.hbs | 30 ++++---- 13 files changed, 158 insertions(+), 97 deletions(-) diff --git a/lang/en.json b/lang/en.json index 3456b6b5..80cc67f7 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1325,8 +1325,8 @@ }, "DomainCard": { "type": "Type", - "foundation": "Foundation", "recallCost": "Recall Cost", + "foundationTitle": "Foundation", "specializationTitle": "Specialization", "masteryTitle": "Mastery" }, @@ -1541,7 +1541,14 @@ "actionIsMissing": "Action is missing", "attackIsMissing": "Attack is missing", "unownedActionMacro": "Cannot make a Use macro for an Action not on your character", - "unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters" + "unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters", + "featureNotHope": "This feature is used as something else than a Hope feature and cannot be used here.", + "featureNotClass": "This feature is used as something else than a Class feature and cannot be used here.", + "featureNotPrimary": "This feature is used as something else than a Primary feature and cannot be used here.", + "featureNotSecondary": "This feature is used as something else than a Secondary feature and cannot be used here.", + "featureNotFoundation": "This feature is used as something else than a Foundation feature and cannot be used here.", + "featureNotSpecialization": "This feature is used as something else than a Specialization feature and cannot be used here.", + "featureNotMastery": "This feature is used as something else than a Mastery feature and cannot be used here." }, "Tooltip": { "openItemWorld": "Open Item World", diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index ed0ee5a7..b8759cc5 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -506,9 +506,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl name: this.setup.ancestryName ?? this.setup.primaryAncestry.name, system: { ...this.setup.primaryAncestry.system, - features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid], - primaryFeature: primaryAncestryFeature.uuid, - secondaryFeature: secondaryAncestryFeature.uuid + features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid] } }; diff --git a/module/applications/sheets/items/ancestry.mjs b/module/applications/sheets/items/ancestry.mjs index bd9e3792..de55301d 100644 --- a/module/applications/sheets/items/ancestry.mjs +++ b/module/applications/sheets/items/ancestry.mjs @@ -63,22 +63,8 @@ export default class AncestrySheet extends DHHeritageSheet { event.stopPropagation(); const target = button.closest('.feature-item'); const feature = this.document.system[`${target.dataset.type}Feature`]; - const featureExists = feature && Object.keys(feature).length > 0; - if (featureExists) { - const confirmed = await foundry.applications.api.DialogV2.confirm({ - window: { - title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: game.i18n.localize(`TYPES.Item.feature`), - name: feature.name - }) - }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name }) - }); - if (!confirmed) return; - } - - if (featureExists && target.dataset.type === 'primary') await feature.update({ 'system.primary': null }); + if (feature) await feature.update({ 'system.subType': null }); await this.document.update({ 'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid) }); @@ -94,15 +80,18 @@ export default class AncestrySheet extends DHHeritageSheet { */ async _onDrop(event) { event.stopPropagation(); - event.preventDefault(); - const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const item = await fromUuid(data.uuid); if (item?.type === 'feature') { const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary'; - await item.update({ 'system.subType': subType }); + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes[subType]) { + const error = subType === 'primary' ? 'featureNotPrimary' : 'featureNotSecondary'; + ui.notifications.warn(game.i18n.localize(`DAGGERHEART.UI.Notifications.${error}`)); + return; + } + await item.update({ 'system.subType': subType }); await this.document.update({ 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index b49105be..3a34bcec 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -78,6 +78,7 @@ export default class ClassSheet extends DHBaseItemSheet { /* -------------------------------------------- */ async _onDrop(event) { + event.stopPropagation(); const data = TextEditor.getDragEventData(event); const item = await fromUuid(data.uuid); const target = event.target.closest('fieldset.drop-section'); @@ -87,12 +88,24 @@ export default class ClassSheet extends DHBaseItemSheet { }); } else if (item.type === 'feature') { if (target.classList.contains('hope-feature')) { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.hope) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotHope')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.hope }); await this.document.update({ - 'system.hopeFeatures': [...this.document.system.hopeFeatures.map(x => x.uuid), item.uuid] + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); } else if (target.classList.contains('class-feature')) { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.class) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotClass')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.class }); await this.document.update({ - 'system.classFeatures': [...this.document.system.classFeatures.map(x => x.uuid), item.uuid] + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); } } else if (item.type === 'weapon') { @@ -177,28 +190,25 @@ export default class ClassSheet extends DHBaseItemSheet { doc.sheet.render({ force: true }); } - getActionPath(type) { - return type === 'hope' ? 'hopeFeatures' : 'classFeatures'; - } - static async addFeature(_, target) { - const actionPath = this.getActionPath(target.dataset.type); const feature = await game.items.documentClass.create({ type: 'feature', - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }) + name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }), + system: { + subType: + target.dataset.type === 'hope' + ? CONFIG.DH.ITEM.featureSubTypes.hope + : CONFIG.DH.ITEM.featureSubTypes.class + } }); await this.document.update({ - [`system.${actionPath}`]: [ - ...this.document.system[actionPath].filter(x => x).map(x => x.uuid), - feature.uuid - ] + [`system.features`]: [...this.document.system.features.filter(x => x).map(x => x.uuid), feature.uuid] }); } static async editFeature(_, button) { const target = button.closest('.feature-item'); - const actionPath = this.getActionPath(button.dataset.type); - const feature = this.document.system[actionPath].find(x => x?.id === target.dataset.featureId); + const feature = this.document.system.features.find(x => x?.id === target.dataset.featureId); if (!feature) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing')); return; @@ -210,10 +220,16 @@ export default class ClassSheet extends DHBaseItemSheet { static async deleteFeature(event, button) { event.stopPropagation(); const target = button.closest('.feature-item'); - const actionPath = this.getActionPath(button.dataset.type); + + const feature = this.document.system.features.find( + feature => feature && feature.id === target.dataset.featureId + ); + if (feature) { + await feature.update({ 'system.subType': null }); + } await this.document.update({ - [`system.${actionPath}`]: this.document.system[actionPath] + [`system.features`]: this.document.system.features .filter(feature => feature && feature.id !== target.dataset.featureId) .map(x => x.uuid) }); diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index 31eca43a..fcf62f69 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -40,28 +40,46 @@ export default class SubclassSheet extends DHBaseItemSheet { static async addFeature(_, target) { const feature = await game.items.documentClass.create({ type: 'feature', - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }) + name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }), + system: { + subType: + target.dataset.type === 'foundation' + ? CONFIG.DH.ITEM.featureSubTypes.foundation + : target.dataset.type === 'specialization' + ? CONFIG.DH.ITEM.featureSubTypes.specialization + : CONFIG.DH.ITEM.featureSubTypes.mastery + } }); await this.document.update({ - [`system.${target.dataset.type}`]: feature.uuid + [`system.features`]: [...this.document.system.features.map(x => x.uuid), feature.uuid] }); } static async editFeature(_, button) { - const feature = this.document.system[button.dataset.type]; + const feature = this.document.system.features.find(x => x.id === button.dataset.feature); if (!feature) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing')); return; } + if (feature) { + await feature.update({ 'system.subType': null }); + } + feature.sheet.render(true); } - static async deleteFeature(event, button) { + static async deleteFeature(event, target) { event.stopPropagation(); + const feature = this.document.system.features.find(feature => feature.id === target.dataset.feature); + if (feature) { + await feature.update({ 'system.subType': null }); + } await this.document.update({ - [`system.${button.dataset.type}`]: null + [`system.features`]: this.document.system.features + .filter(feature => feature && feature.id !== target.dataset.feature) + .map(x => x.uuid) }); } @@ -82,18 +100,45 @@ export default class SubclassSheet extends DHBaseItemSheet { } async _onDrop(event) { + event.stopPropagation(); + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); if (data.fromInternal) return; const item = await fromUuid(data.uuid); - if (item?.type === 'feature') { - const dropSection = event.target.closest('.drop-section'); - if (this.document.system[dropSection.dataset.type]) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsFull')); - return; - } + const target = event.target.closest('fieldset.drop-section'); + if (item.type === 'feature') { + if (target.dataset.type === 'foundation') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.foundation) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotFoundation')); + return; + } - await this.document.update({ [`system.${dropSection.dataset.type}`]: item.uuid }); + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.foundation }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } else if (target.dataset.type === 'specialization') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.specialization) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotSpecialization')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.specialization }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } else if (target.dataset.type === 'mastery') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.mastery) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotMastery')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.mastery }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } } } } diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 02cfd4a9..d05dc81c 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -411,7 +411,7 @@ export const levelupData = { }; export const subclassFeatureLabels = { - 1: 'DAGGERHEART.ITEMS.DomainCard.foundation', + 1: 'DAGGERHEART.ITEMS.DomainCard.foundationTitle', 2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle', 3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle' }; diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index ede5ef08..b26a26ca 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -1322,7 +1322,12 @@ export const featureTypes = { export const featureSubTypes = { primary: 'primary', - secondary: 'secondary' + secondary: 'secondary', + hope: 'hope', + class: 'class', + foundation: 'foundation', + specialization: 'specialization', + mastery: 'mastery' }; export const actionTypes = { diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 6b0a2fd7..1c15d036 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -124,12 +124,9 @@ export default class DhCharacter extends BaseDataActor { label: 'DAGGERHEART.GENERAL.Range.other' }) }), - rally: new fields.ArrayField( - new fields.StringField(), - { - label: 'DAGGERHEART.CLASS.Feature.rallyDice' - } - ) + rally: new fields.ArrayField(new fields.StringField(), { + label: 'DAGGERHEART.CLASS.Feature.rallyDice' + }) }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), rules: new fields.SchemaField({ @@ -260,11 +257,11 @@ export default class DhCharacter extends BaseDataActor { classFeatures.push(item); } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { const subclassState = this.class.subclass.system.featureState; - const identifier = item.system.identifier; + const subType = item.system.subType; if ( - identifier === 'foundationFeature' || - (identifier === 'specializationFeature' && subclassState >= 2) || - (identifier === 'masterFeature' && subclassState >= 3) + subType === CONFIG.DH.ITEM.featureSubTypes.foundation || + (subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) || + (subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3) ) { subclassFeatures.push(item); } diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 281b0a48..b8a9ab81 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -27,8 +27,7 @@ export default class DHClass extends BaseDataItem { label: 'DAGGERHEART.GENERAL.hitPoints.plural' }), evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }), - hopeFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }), - classFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }), + features: new ForeignDocumentUUIDArrayField({ type: 'Item' }), subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), inventory: new fields.SchemaField({ take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), @@ -52,12 +51,18 @@ export default class DHClass extends BaseDataItem { }; } - get hopeFeature() { - return this.hopeFeatures.length > 0 ? this.hopeFeatures[0] : null; + get hopeFeatures() { + return ( + this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.hope) ?? + (this.features.filter(x => !x).length > 0 ? {} : null) + ); } - get features() { - return [...this.hopeFeatures.filter(x => x), ...this.classFeatures.filter(x => x)]; + get classFeatures() { + return ( + this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.class) ?? + (this.features.filter(x => !x).length > 0 ? {} : null) + ); } async _preCreate(data, options, user) { diff --git a/module/data/item/domainCard.mjs b/module/data/item/domainCard.mjs index 89bbfb40..df60b9d1 100644 --- a/module/data/item/domainCard.mjs +++ b/module/data/item/domainCard.mjs @@ -29,7 +29,6 @@ export default class DHDomainCard extends BaseDataItem { required: true, initial: CONFIG.DH.DOMAIN.cardTypes.ability.id }), - foundation: new fields.BooleanField({ initial: false }), inVault: new fields.BooleanField({ initial: false }), actions: new fields.ArrayField(new ActionField()) }; diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 265c2566..e0a76092 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -1,4 +1,4 @@ -import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import BaseDataItem from './base.mjs'; export default class DHSubclass extends BaseDataItem { @@ -22,20 +22,22 @@ export default class DHSubclass extends BaseDataItem { nullable: true, initial: null }), - foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }), - specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }), - masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }), + features: new ForeignDocumentUUIDArrayField({ type: 'Item' }), featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }), isMulticlass: new fields.BooleanField({ initial: false }) }; } - get features() { - return [ - { ...this.foundationFeature?.toObject(), identifier: 'foundationFeature' }, - { ...this.specializationFeature?.toObject(), identifier: 'specializationFeature' }, - { ...this.masteryFeature?.toObject(), identifier: 'masteryFeature' } - ]; + get foundationFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.foundation); + } + + get specializationFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.specialization); + } + + get masteryFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.mastery); } async _preCreate(data, options, user) { diff --git a/templates/sheets/items/domainCard/settings.hbs b/templates/sheets/items/domainCard/settings.hbs index 2faa6934..5518b4c3 100644 --- a/templates/sheets/items/domainCard/settings.hbs +++ b/templates/sheets/items/domainCard/settings.hbs @@ -8,8 +8,6 @@ {{localize "DAGGERHEART.GENERAL.type"}} {{formField systemFields.type value=source.system.type localize=true}} - {{localize "DAGGERHEART.ITEMS.DomainCard.foundation"}} - {{formField systemFields.foundation value=source.system.foundation }} {{localize "DAGGERHEART.GENERAL.Domain.single"}} {{formField systemFields.domain value=source.system.domain localize=true}} {{localize "DAGGERHEART.GENERAL.level"}} diff --git a/templates/sheets/items/subclass/features.hbs b/templates/sheets/items/subclass/features.hbs index d2424d01..1a75974e 100644 --- a/templates/sheets/items/subclass/features.hbs +++ b/templates/sheets/items/subclass/features.hbs @@ -3,42 +3,42 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > -
    +
    {{localize "DAGGERHEART.GENERAL.Tabs.foundation"}} - +
    - {{#if source.system.foundationFeature}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='foundationFeature' feature=source.system.foundationFeature}} - {{/if}} + {{#each source.system.foundationFeatures as | feature | }} + {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='foundation' feature=feature}} + {{/each}}
    -
    +
    {{localize "DAGGERHEART.GENERAL.Tabs.specialization"}} - +
    - {{#if source.system.specializationFeature}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='specializationFeature' feature=source.system.specializationFeature}} - {{/if}} + {{#each source.system.specializationFeatures as | feature |}} + {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='specialization' feature=feature}} + {{/each}}
    -
    +
    {{localize "DAGGERHEART.GENERAL.Tabs.mastery"}} - +
    - {{#if source.system.masteryFeature}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='masteryFeature' feature=source.system.masteryFeature}} - {{/if}} + {{#each source.system.masteryFeatures as | feature |}} + {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='mastery' feature=feature}} + {{/each}}
    \ No newline at end of file From 6e87e4dad066173cd4cd47fbb135881725f82330 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:48:59 +0200 Subject: [PATCH 21/22] [Fix] Downtime Rework (#367) * Fixed so that the dropdown for activeEffectAutocomplete never ends up behind dialog * Downtime can now display both ShortRest and LongRest options depending on character rules * Initial downtime layout rework * Fixed styling for downtime tooltip * Added icon to homebrew menu for DowntimeActions * Fixed columns if both types of moves are not available * Changed the lightmode to darkmode * Added downtime buttons * . * Moved extra rest options from rules to bonuses * Improved dialog width --- lang/en.json | 32 +++++- module/applications/dialogs/downtime.mjs | 87 +++++++++++---- .../components/settingsActionsView.mjs | 10 +- .../settings/homebrewSettings.mjs | 2 + .../sheets-configs/activeEffectConfig.mjs | 3 + .../applications/sheets/actors/character.mjs | 9 +- module/config/generalConfig.mjs | 19 ++-- module/data/actor/character.mjs | 38 +++++++ module/data/settings/Homebrew.mjs | 2 + module/documents/token.mjs | 20 ++-- module/documents/tooltipManager.mjs | 51 ++++++++- module/systemRegistration/handlebars.mjs | 3 +- .../dialog/downtime/downtime-container.less | 102 +++++++++--------- styles/less/global/tab-navigation.less | 20 +++- .../less/sheets/actors/character/header.less | 9 ++ styles/less/ux/autocomplete/autocomplete.less | 1 - styles/less/ux/tooltip/tooltip.less | 14 +++ templates/dialogs/downtime.hbs | 24 ----- templates/dialogs/downtime/activities.hbs | 18 ++++ templates/dialogs/downtime/downtime.hbs | 14 +++ templates/settings/automation-settings.hbs | 2 +- templates/settings/components/action-view.hbs | 19 ++-- templates/sheets/actors/character/header.hbs | 11 +- .../sheets/global/tabs/tab-navigation.hbs | 25 +++-- templates/ui/tooltip/downtime.hbs | 7 ++ 25 files changed, 390 insertions(+), 152 deletions(-) delete mode 100644 templates/dialogs/downtime.hbs create mode 100644 templates/dialogs/downtime/activities.hbs create mode 100644 templates/dialogs/downtime/downtime.hbs create mode 100644 templates/ui/tooltip/downtime.hbs diff --git a/lang/en.json b/lang/en.json index 80cc67f7..39e49b67 100755 --- a/lang/en.json +++ b/lang/en.json @@ -249,8 +249,9 @@ "title": "{actor} - Death Move" }, "Downtime": { - "downtimeHeader": "Downtime Moves ({current}/{max})", "longRest": { + "title": "Long Rest", + "moves": "Long Rest Moves ({current}/{max})", "clearStress": { "description": "Describe how you blow off steam or pull yourself together, and clear all marked Stress.", "name": "Clear Stress" @@ -267,7 +268,6 @@ "description": "Describe how you patch yourself up and remove all marked Hit Points. You may also do this on an ally instead.", "name": "Tend to Wounds" }, - "title": "Long Rest", "workOnAProject": { "description": "Establish or continue work on a project.", "name": "Work on a Project" @@ -275,6 +275,7 @@ }, "shortRest": { "title": "Short Rest", + "moves": "Short Rest Moves ({current}/{max})", "tendToWounds": { "name": "Tend to Wounds", "description": "Describe how you hastily patch yourself up, then clear a number of Hit Points equal to 1d4 + your tier. You can do this to an ally instead." @@ -291,7 +292,8 @@ "name": "Prepare", "description": "Describe how you prepare yourself for the path ahead, then gain a Hope. If you choose to Prepare with one or more members of your party, you each gain 2 Hope." } - } + }, + "takeDowntime": "Take Downtime" }, "HUD": { "tokenHUD": { @@ -1012,6 +1014,30 @@ "singular": "Adversary", "plural": "Adversaries" }, + "Bonuses": { + "rest": { + "shortRest": { + "shortRestMoves": { + "label": "Short Rest: Bonus Short Rest Moves", + "hint": "The number of extra Short Rest Moves the character can take during a Short Rest." + }, + "longRestMoves": { + "label": "Short Rest: Bonus Long Rest Moves", + "hint": "The number of extra Long Rest Moves the character can take during a Short Rest." + } + }, + "longRest": { + "shortRestMoves": { + "label": "Long Rest: Bonus Short Rest Moves", + "hint": "The number of extra Short Rest Moves the character can take during a Long Rest." + }, + "longRestMoves": { + "label": "Long Rest: Bonus Long Rest Moves", + "hint": "The number of extra Long Rest Moves the character can take during a Long Rest." + } + } + } + }, "Character": { "singular": "Character", "plural": "Characters" diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 3966f7e4..15add3ad 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -7,8 +7,22 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV this.actor = actor; this.shortrest = shortrest; - const options = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves; - this.moveData = shortrest ? options.shortRest : options.longRest; + this.moveData = foundry.utils.deepClone( + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves + ); + this.nrChoices = { + shortRest: { + max: + (shortrest ? this.moveData.shortRest.nrChoices : 0) + + actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].shortMoves + }, + longRest: { + max: + (!shortrest ? this.moveData.longRest.nrChoices : 0) + + actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].longMoves + } + }; + this.nrChoices.total = { max: this.nrChoices.shortRest.max + this.nrChoices.longRest.max }; } get title() { @@ -17,8 +31,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV static DEFAULT_OPTIONS = { tag: 'form', - classes: ['daggerheart', 'views', 'downtime'], - position: { width: 680, height: 'auto' }, + classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'downtime'], + position: { width: 'auto', height: 'auto' }, actions: { selectMove: this.selectMove, takeDowntime: this.takeDowntime @@ -29,7 +43,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV static PARTS = { application: { id: 'downtime', - template: 'systems/daggerheart/templates/dialogs/downtime.hbs' + template: 'systems/daggerheart/templates/dialogs/downtime/downtime.hbs' } }; @@ -37,46 +51,83 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV super._attachPartListeners(partId, htmlElement, options); htmlElement - .querySelectorAll('.activity-image') + .querySelectorAll('.activity-container') .forEach(element => element.addEventListener('contextmenu', this.deselectMove.bind(this))); } async _prepareContext(_options) { const context = await super._prepareContext(_options); + context.title = game.i18n.localize( + `DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title` + ); context.selectedActivity = this.selectedActivity; context.moveData = this.moveData; - context.nrCurrentChoices = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0); - context.disabledDowntime = context.nrCurrentChoices < context.moveData.nrChoices; + context.nrCurrentChoices = Object.values(this.moveData).reduce((acc, category) => { + acc += Object.values(category.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0); + return acc; + }, 0); + + context.nrChoices = { + ...this.nrChoices, + shortRest: { + ...this.nrChoices.shortRest, + current: Object.values(this.moveData.shortRest.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0) + }, + longRest: { + ...this.nrChoices.longRest, + current: Object.values(this.moveData.longRest.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0) + } + }; + context.nrChoices.total = { + ...this.nrChoices.total, + current: context.nrChoices.shortRest.current + context.nrChoices.longRest.current + }; + + context.shortRestMoves = this.nrChoices.shortRest.max > 0 ? this.moveData.shortRest : null; + context.longRestMoves = this.nrChoices.longRest.max > 0 ? this.moveData.longRest : null; + + context.disabledDowntime = context.nrChoices.total.current < context.nrChoices.total.max; return context; } - static selectMove(_, button) { - const nrSelected = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0); - if (nrSelected === this.moveData.nrChoices) { + static selectMove(_, target) { + const nrSelected = Object.values(this.moveData[target.dataset.category].moves).reduce( + (acc, x) => acc + (x.selected ?? 0), + 0 + ); + + if (nrSelected === this.nrChoices[target.dataset.category].max) { ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noMoreMoves')); return; } - const move = button.dataset.move; - this.moveData.moves[move].selected = this.moveData.moves[move].selected - ? this.moveData.moves[move].selected + 1 + const move = target.dataset.move; + this.moveData[target.dataset.category].moves[move].selected = this.moveData[target.dataset.category].moves[move] + .selected + ? this.moveData[target.dataset.category].moves[move].selected + 1 : 1; this.render(); } deselectMove(event) { - const move = event.currentTarget.dataset.move; - this.moveData.moves[move].selected = this.moveData.moves[move].selected - ? this.moveData.moves[move].selected - 1 + const button = event.target.closest('.activity-container'); + const move = button.dataset.move; + this.moveData[button.dataset.category].moves[move].selected = this.moveData[button.dataset.category].moves[move] + .selected + ? this.moveData[button.dataset.category].moves[move].selected - 1 : 0; this.render(); } static async takeDowntime() { - const moves = Object.values(this.moveData.moves).filter(x => x.selected); + const moves = Object.values(this.moveData).flatMap(category => { + return Object.values(category.moves) + .filter(x => x.selected) + .flatMap(move => [...Array(move.selected).keys()].map(_ => move)); + }); const cls = getDocumentClass('ChatMessage'); const msg = new cls({ diff --git a/module/applications/settings/components/settingsActionsView.mjs b/module/applications/settings/components/settingsActionsView.mjs index a905e824..f77c5fce 100644 --- a/module/applications/settings/components/settingsActionsView.mjs +++ b/module/applications/settings/components/settingsActionsView.mjs @@ -4,13 +4,14 @@ import DHActionConfig from '../../sheets-configs/action-config.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; export default class DhSettingsActionView extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(resolve, reject, title, name, img, description, actions) { + constructor(resolve, reject, title, name, icon, img, description, actions) { super({}); this.resolve = resolve; this.reject = reject; this.viewTitle = title; this.name = name; + this.icon = icon; this.img = img; this.description = description; this.actions = actions; @@ -23,7 +24,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App static DEFAULT_OPTIONS = { tag: 'form', classes: ['daggerheart', 'setting', 'dh-style'], - position: { width: '400', height: 'auto' }, + position: { width: 440, height: 'auto' }, actions: { editImage: this.onEditImage, addItem: this.addItem, @@ -46,6 +47,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App async _prepareContext(_options) { const context = await super._prepareContext(_options); context.name = this.name; + context.icon = this.icon; context.img = this.img; context.description = this.description; context.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(context.description); @@ -55,8 +57,9 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App } static async updateData(event, element, formData) { - const { name, img, description } = foundry.utils.expandObject(formData.object); + const { name, icon, description } = foundry.utils.expandObject(formData.object); this.name = name; + this.icon = icon; this.description = description; this.render(); @@ -65,6 +68,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App static async saveForm(event) { this.resolve({ name: this.name, + icon: this.icon, img: this.img, description: this.description, actions: this.actions diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 08b4cf4e..e516be03 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -76,6 +76,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli reject, game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves'), move.name, + move.icon, move.img, move.description, move.actions @@ -87,6 +88,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli await this.settings.updateSource({ [`restMoves.${type}.moves.${id}`]: { name: data.name, + icon: data.icon, img: data.img, description: data.description } diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 8574a5db..087b5b08 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -88,6 +88,9 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac element.value = `system.${item.value}`; }, click: e => e.fetch(), + customize: function (_input, _inputRect, container) { + container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ; + }, minLength: 0 }); }); diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 139a1369..0f6f8284 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -28,7 +28,8 @@ export default class CharacterSheet extends DHBaseActorSheet { useAction: this.useAction, toggleResourceDice: this.toggleResourceDice, handleResourceDice: this.handleResourceDice, - toChat: this.toChat + toChat: this.toChat, + useDowntime: this.useDowntime }, window: { resizable: true @@ -752,6 +753,12 @@ export default class CharacterSheet extends DHBaseActorSheet { } } + static useDowntime(_, button) { + new game.system.api.applications.dialogs.Downtime(this.document, button.dataset.type === 'shortRest').render( + true + ); + } + async _onDragStart(event) { const item = this.getItem(event); diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 704a5401..632e5448 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -130,6 +130,7 @@ export const defaultRestOptions = { tendToWounds: { id: 'tendToWounds', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.name'), + icon: 'fa-solid fa-bandage', img: 'icons/magic/life/cross-worn-green.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.description'), actions: [ @@ -153,6 +154,7 @@ export const defaultRestOptions = { clearStress: { id: 'clearStress', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.name'), + icon: 'fa-regular fa-face-surprise', img: 'icons/magic/perception/eye-ringed-green.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.description'), actions: [ @@ -176,6 +178,7 @@ export const defaultRestOptions = { repairArmor: { id: 'repairArmor', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.name'), + icon: 'fa-solid fa-hammer', img: 'icons/skills/trades/smithing-anvil-silver-red.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.description'), actions: [] @@ -183,6 +186,7 @@ export const defaultRestOptions = { prepare: { id: 'prepare', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.name'), + icon: 'fa-solid fa-dumbbell', img: 'icons/skills/trades/academics-merchant-scribe.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'), actions: [] @@ -192,6 +196,7 @@ export const defaultRestOptions = { tendToWounds: { id: 'tendToWounds', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.name'), + icon: 'fa-solid fa-bandage', img: 'icons/magic/life/cross-worn-green.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.description'), actions: [] @@ -199,6 +204,7 @@ export const defaultRestOptions = { clearStress: { id: 'clearStress', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.name'), + icon: 'fa-regular fa-face-surprise', img: 'icons/magic/perception/eye-ringed-green.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.description'), actions: [] @@ -206,6 +212,7 @@ export const defaultRestOptions = { repairArmor: { id: 'repairArmor', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.name'), + icon: 'fa-solid fa-hammer', img: 'icons/skills/trades/smithing-anvil-silver-red.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.description'), actions: [] @@ -213,6 +220,7 @@ export const defaultRestOptions = { prepare: { id: 'prepare', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.name'), + icon: 'fa-solid fa-dumbbell', img: 'icons/skills/trades/academics-merchant-scribe.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'), actions: [] @@ -220,19 +228,12 @@ export const defaultRestOptions = { workOnAProject: { id: 'workOnAProject', name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.name'), + icon: 'fa-solid fa-diagram-project', img: 'icons/skills/social/thumbsup-approval-like.webp', description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'), actions: [] } - }), - custom: { - id: 'customActivity', - name: '', - img: 'icons/skills/trades/academics-investigation-puzzles.webp', - description: '', - namePlaceholder: 'DAGGERHEART.APPLICATIONS.Downtime.custom.namePlaceholder', - placeholder: 'DAGGERHEART.APPLICATIONS.Downtime.custom.placeholder' - } + }) }; export const deathMoves = { diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 1c15d036..c15d2221 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -126,6 +126,44 @@ export default class DhCharacter extends BaseDataActor { }), rally: new fields.ArrayField(new fields.StringField(), { label: 'DAGGERHEART.CLASS.Feature.rallyDice' + }), + rest: new fields.SchemaField({ + shortRest: new fields.SchemaField({ + shortMoves: new fields.NumberField({ + required: true, + integer: true, + min: 0, + initial: 0, + label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label', + hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint' + }), + longMoves: new fields.NumberField({ + required: true, + integer: true, + min: 0, + initial: 0, + label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label', + hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint' + }) + }), + longRest: new fields.SchemaField({ + shortMoves: new fields.NumberField({ + required: true, + integer: true, + min: 0, + initial: 0, + label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label', + hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint' + }), + longMoves: new fields.NumberField({ + required: true, + integer: true, + min: 0, + initial: 0, + label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label', + hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint' + }) + }) }) }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), diff --git a/module/data/settings/Homebrew.mjs b/module/data/settings/Homebrew.mjs index ead5a09f..008cd73c 100644 --- a/module/data/settings/Homebrew.mjs +++ b/module/data/settings/Homebrew.mjs @@ -54,6 +54,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel { moves: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField({ required: true }), + icon: new fields.StringField({ required: true }), img: new fields.FilePathField({ initial: 'icons/magic/life/cross-worn-green.webp', categories: ['IMAGE'], @@ -70,6 +71,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel { moves: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField({ required: true }), + icon: new fields.StringField({ required: true }), img: new fields.FilePathField({ initial: 'icons/magic/life/cross-worn-green.webp', categories: ['IMAGE'], diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 3e7b49ea..89305128 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -16,7 +16,7 @@ export default class DHToken extends TokenDocument { }); bars.sort((a, b) => a.label.compare(b.label)); - const invalidAttributes = ['gold', 'levelData', 'rules.damageReduction.maxArmorMarked.value']; + const invalidAttributes = ['gold', 'levelData', 'actions', 'rules.damageReduction.maxArmorMarked.value']; const values = attributes.value.reduce((acc, v) => { const a = v.join('.'); if (invalidAttributes.some(x => a.startsWith(x))) return acc; @@ -32,19 +32,19 @@ export default class DHToken extends TokenDocument { return bars.concat(values); } - - static _getTrackedAttributesFromSchema(schema, _path=[]) { - const attributes = {bar: [], value: []}; - for ( const [name, field] of Object.entries(schema.fields) ) { + + static _getTrackedAttributesFromSchema(schema, _path = []) { + const attributes = { bar: [], value: [] }; + for (const [name, field] of Object.entries(schema.fields)) { const p = _path.concat([name]); - if ( field instanceof foundry.data.fields.NumberField ) attributes.value.push(p); - if ( field instanceof foundry.data.fields.ArrayField ) attributes.value.push(p); + if (field instanceof foundry.data.fields.NumberField) attributes.value.push(p); + if (field instanceof foundry.data.fields.ArrayField) attributes.value.push(p); const isSchema = field instanceof foundry.data.fields.SchemaField; const isModel = field instanceof foundry.data.fields.EmbeddedDataField; - if ( isSchema || isModel ) { + if (isSchema || isModel) { const schema = isModel ? field.model.schema : field; - const isBar = schema.has && schema.has("value") && schema.has("max"); - if ( isBar ) attributes.bar.push(p); + const isBar = schema.has && schema.has('value') && schema.has('max'); + if (isBar) attributes.bar.push(p); else { const inner = this.getTrackedAttributes(schema, p); attributes.bar.push(...inner.bar); diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index f24823f4..e622059d 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -22,6 +22,28 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti options.direction = this._determineItemTooltipDirection(element); } } else { + const shortRest = element.dataset.tooltip?.startsWith('#shortRest#'); + const longRest = element.dataset.tooltip?.startsWith('#longRest#'); + if (shortRest || longRest) { + const key = element.dataset.tooltip.slice(shortRest ? 11 : 10); + const downtimeOptions = shortRest + ? CONFIG.DH.GENERAL.defaultRestOptions.shortRest() + : CONFIG.DH.GENERAL.defaultRestOptions.longRest(); + const move = downtimeOptions[key]; + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/downtime.hbs`, + { + move: move + } + ); + + this.tooltip.innerHTML = html; + options.direction = this._determineItemTooltipDirection( + element, + this.constructor.TOOLTIP_DIRECTIONS.UP + ); + } + const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#'); const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#'); if (isAdvantage || isDisadvantage) { @@ -44,9 +66,34 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti super.activate(element, { ...options, html: html }); } - _determineItemTooltipDirection(element) { + _determineItemTooltipDirection(element, prefered = this.constructor.TOOLTIP_DIRECTIONS.LEFT) { const pos = element.getBoundingClientRect(); const dirs = this.constructor.TOOLTIP_DIRECTIONS; - return dirs[pos.x - this.tooltip.offsetWidth < 0 ? 'DOWN' : 'LEFT']; + switch (prefered) { + case this.constructor.TOOLTIP_DIRECTIONS.LEFT: + return dirs[ + pos.x - this.tooltip.offsetWidth < 0 + ? this.constructor.TOOLTIP_DIRECTIONS.DOWN + : this.constructor.TOOLTIP_DIRECTIONS.LEFT + ]; + case this.constructor.TOOLTIP_DIRECTIONS.UP: + return dirs[ + pos.y - this.tooltip.offsetHeight < 0 + ? this.constructor.TOOLTIP_DIRECTIONS.RIGHT + : this.constructor.TOOLTIP_DIRECTIONS.UP + ]; + case this.constructor.TOOLTIP_DIRECTIONS.RIGHT: + return dirs[ + pos.x + this.tooltip.offsetWidth > document.body.clientWidth + ? this.constructor.TOOLTIP_DIRECTIONS.DOWN + : this.constructor.TOOLTIP_DIRECTIONS.RIGHT + ]; + case this.constructor.TOOLTIP_DIRECTIONS.DOWN: + return dirs[ + pos.y + this.tooltip.offsetHeight > document.body.clientHeight + ? this.constructor.TOOLTIP_DIRECTIONS.LEFT + : this.constructor.TOOLTIP_DIRECTIONS.DOWN + ]; + } } } diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 8456094c..0c455750 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -25,6 +25,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/settings/components/settings-item-line.hbs', 'systems/daggerheart/templates/ui/chat/parts/damage-chat.hbs', 'systems/daggerheart/templates/ui/chat/parts/target-chat.hbs', - 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs' + 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs', + 'systems/daggerheart/templates/dialogs/downtime/activities.hbs' ]); }; diff --git a/styles/less/dialog/downtime/downtime-container.less b/styles/less/dialog/downtime/downtime-container.less index 4e785e7b..0f803d9b 100644 --- a/styles/less/dialog/downtime/downtime-container.less +++ b/styles/less/dialog/downtime/downtime-container.less @@ -1,81 +1,75 @@ @import '../../utils/spacing.less'; @import '../../utils/colors.less'; -.daggerheart.views { +.theme-light .daggerheart.dh-style.views.downtime { + .downtime-container .activity-container .activity-selected-marker { + background-image: url(../assets/parchments/dh-parchment-light.png); + } +} + +.daggerheart.dh-style.views.downtime { + font-family: @font-body; + .downtime-container { - .downtime-header { - margin: 0; - color: light-dark(@dark-blue, @golden); - text-align: center; + .activities-grouping { + width: 280px; } - .activity-container { - display: flex; - align-items: center; - padding: 8px; + .activities-container { + width: 100%; - .activity-title { - flex: 1; + .activity-container { display: flex; align-items: center; + justify-content: space-between; + padding: 8px; - .activity-title-text { - font-size: 24px; - font-weight: bold; - } - - .activity-image { - width: 80px; - position: relative; + .activity-inner-container { display: flex; - justify-content: center; - margin-right: 8px; - border: 2px solid black; - border-radius: 50%; - cursor: pointer; + align-items: center; + gap: 4px; - .activity-select-label { - position: absolute; - top: -9px; - font-size: 14px; - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 6px; - color: light-dark(@beige, @dark); - background-image: url(../assets/parchments/dh-parchment-light.png); - padding: 0 8px; - line-height: 1; - font-weight: bold; + .activity-marker { + font-size: 8px; + flex: none; + color: light-dark(#18162e, #f3c267); + margin-right: 4px; } - img { - border-radius: 50%; - } + .activity-select-section { + display: flex; + align-items: center; + gap: 4px; - &:hover, - &.selected { - filter: drop-shadow(0 0 6px gold); + .activity-icon { + min-width: 24px; + text-align: center; + } } } - .custom-name-input { - font-size: 24px; + .activity-selected-marker { + font-size: 14px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + color: light-dark(@dark, @beige); + background-image: url(../assets/parchments/dh-parchment-dark.png); + padding: 0 8px; + line-height: 1; font-weight: bold; - padding: 0; - background: transparent; - color: rgb(239, 230, 216); } } - - .activity-body { - flex: 1; - font-style: italic; - } } } - &.downtime { - .activity-text-area { - resize: none; + footer { + margin-top: 8px; + display: flex; + gap: 8px; + + button { + flex: 1; + font-family: 'Montserrat', sans-serif; } } } diff --git a/styles/less/global/tab-navigation.less b/styles/less/global/tab-navigation.less index 2880711d..014da89f 100755 --- a/styles/less/global/tab-navigation.less +++ b/styles/less/global/tab-navigation.less @@ -7,12 +7,22 @@ height: 40px; width: 100%; - .feature-tab { - border: none; + .navigation-container { + display: flex; + align-items: center; + gap: 8px; - a { - color: light-dark(@dark-blue, @golden); - font-family: @font-body; + .navigation-inner-container { + flex: 1; + + .feature-tab { + border: none; + + a { + color: light-dark(@dark-blue, @golden); + font-family: @font-body; + } + } } } } diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index 6110fcc6..b80da83d 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -193,5 +193,14 @@ } } } + + .character-downtime-container { + display: flex; + gap: 2px; + + button { + flex: 1; + } + } } } diff --git a/styles/less/ux/autocomplete/autocomplete.less b/styles/less/ux/autocomplete/autocomplete.less index 06cabf5a..868b4f43 100644 --- a/styles/less/ux/autocomplete/autocomplete.less +++ b/styles/less/ux/autocomplete/autocomplete.less @@ -10,7 +10,6 @@ border-color: light-dark(@dark, @beige); border-radius: 6px; background-image: url('../assets/parchments/dh-parchment-dark.png'); - z-index: 200; max-height: 400px !important; width: fit-content !important; overflow-y: auto; diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less index 38502d09..0060f74b 100644 --- a/styles/less/ux/tooltip/tooltip.less +++ b/styles/less/ux/tooltip/tooltip.less @@ -4,6 +4,20 @@ align-items: center; gap: 4px; + .tooltip-title-container { + width: 100%; + display: flex; + align-items: center; + gap: 16px; + + .tooltip-image { + height: 40px; + width: 40px; + border-radius: 6px; + border: 1px solid @golden; + } + } + .tooltip-title { margin: 0; text-align: center; diff --git a/templates/dialogs/downtime.hbs b/templates/dialogs/downtime.hbs deleted file mode 100644 index fd5fa405..00000000 --- a/templates/dialogs/downtime.hbs +++ /dev/null @@ -1,24 +0,0 @@ -
    -
    -

    {{localize "DAGGERHEART.APPLICATIONS.Downtime.downtimeHeader" current=nrCurrentChoices max=moveData.nrChoices}}

    - {{#each moveData.moves as |move key|}} -
    -
    -
    - {{#if this.selected}}
    {{move.selected}}
    {{/if}} - -
    - - {{localize this.name}} -
    -
    - {{localize this.description}} -
    -
    - {{/each}} -
    -
    - - -
    -
    \ No newline at end of file diff --git a/templates/dialogs/downtime/activities.hbs b/templates/dialogs/downtime/activities.hbs new file mode 100644 index 00000000..f67e8a10 --- /dev/null +++ b/templates/dialogs/downtime/activities.hbs @@ -0,0 +1,18 @@ +
    + {{localize (concat "DAGGERHEART.APPLICATIONS.Downtime." category ".moves") max=nrChoices.max current=nrChoices.current}} + + +
    \ No newline at end of file diff --git a/templates/dialogs/downtime/downtime.hbs b/templates/dialogs/downtime/downtime.hbs new file mode 100644 index 00000000..c8e44e5d --- /dev/null +++ b/templates/dialogs/downtime/downtime.hbs @@ -0,0 +1,14 @@ +
    +
    +

    {{title}}

    +
    + +
    + {{#if shortRestMoves.moves}}{{> "systems/daggerheart/templates/dialogs/downtime/activities.hbs" moves=shortRestMoves.moves category='shortRest' nrChoices=nrChoices.shortRest}}{{/if}} + {{#if longRestMoves.moves}}{{> "systems/daggerheart/templates/dialogs/downtime/activities.hbs" moves=longRestMoves.moves category='longRest' nrChoices=nrChoices.longRest}}{{/if}} +
    +
    + + +
    +
    \ No newline at end of file diff --git a/templates/settings/automation-settings.hbs b/templates/settings/automation-settings.hbs index 0e158ab6..910ace56 100644 --- a/templates/settings/automation-settings.hbs +++ b/templates/settings/automation-settings.hbs @@ -6,7 +6,7 @@
    {{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}} -s {{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}} + {{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
    - {{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} + {{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} +
    + + +
    + {{/'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} \ No newline at end of file diff --git a/templates/sheets/global/tabs/tab-navigation.hbs b/templates/sheets/global/tabs/tab-navigation.hbs index 976aac89..10d76619 100755 --- a/templates/sheets/global/tabs/tab-navigation.hbs +++ b/templates/sheets/global/tabs/tab-navigation.hbs @@ -1,11 +1,18 @@
    - - - +
    \ No newline at end of file diff --git a/templates/ui/tooltip/downtime.hbs b/templates/ui/tooltip/downtime.hbs new file mode 100644 index 00000000..7b7f5c16 --- /dev/null +++ b/templates/ui/tooltip/downtime.hbs @@ -0,0 +1,7 @@ +
    +
    + +

    {{move.name}}

    +
    +
    {{{move.description}}}
    +
    \ No newline at end of file From 1d75b080875586e26cb7ba5cadceeaaba51bbd68 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:49:44 +0200 Subject: [PATCH 22/22] Fixed so active effects can handle expressions again (#368) --- lang/en.json | 10 +++------- module/config/itemConfig.mjs | 11 +---------- module/documents/activeEffect.mjs | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lang/en.json b/lang/en.json index 39e49b67..4e9a9b11 100755 --- a/lang/en.json +++ b/lang/en.json @@ -777,7 +777,7 @@ "WeaponFeature": { "barrier": { "name": "Barrier", - "description": "+{armorScore} to Armor Score; -1 to Evasion" + "description": "Gain your character's Tier + 1 to Armor Score; -1 to Evasion" }, "bonded": { "name": "Bonded", @@ -893,7 +893,7 @@ }, "paired": { "name": "Paired", - "description": "+{bonusDamage} to primary weapon damage to targets within Melee range" + "description": "Add your character's Tier + 1 to primary weapon damage against targets within Melee range" }, "parry": { "name": "Parry", @@ -913,7 +913,7 @@ }, "protective": { "name": "Protective", - "description": "+{tier} to Armor Score" + "description": "Add your character's Tier to your Armor Score" }, "quick": { "name": "Quick", @@ -962,10 +962,6 @@ "timebending": { "name": "Timebending", "description": "You can choose the target of your attack after making your attack roll." - }, - "versatile": { - "name": "Versatile", - "description": "This weapon can also be used with these statistics—{characterTrait}, {range}, {damage}." } } }, diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index b26a26ca..6b28a1ae 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -439,7 +439,7 @@ export const weaponFeatures = { { key: 'system.bonuses.damage.primaryWeapon.bonus', mode: 2, - value: '@system.levelData.levels.current' + value: '@system.levelData.level.current' } ] } @@ -1261,15 +1261,6 @@ export const weaponFeatures = { timebending: { label: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.name', description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.description' - }, - versatile: { - label: 'DAGGERHEART.CONFIG.WeaponFeature.versatile.name', - description: 'DAGGERHEART.CONFIG.WeaponFeature.versatile.description' - // versatile: { - // characterTrait: '', - // range: '', - // damage: '' - // } } }; diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index aa1a5b0d..6c4545b1 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -55,10 +55,24 @@ export default class DhActiveEffect extends ActiveEffect { } static applyField(model, change, field) { - change.value = itemAbleRollParse(change.value, model, change.effect.parent); + change.value = this.effectSafeEval(itemAbleRollParse(change.value, model, change.effect.parent)); super.applyField(model, change, field); } + /* Altered Foundry safeEval to allow non-numeric returns */ + static effectSafeEval(expression) { + let result; + try { + // eslint-disable-next-line no-new-func + const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`); + result = evl(Roll.MATH_PROXY); + } catch (err) { + return expression; + } + + return result; + } + async toChat(origin) { const cls = getDocumentClass('ChatMessage'); const systemData = {