From 0aabcec340eef7053353808721a42183eab600a6 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 19 Aug 2025 18:56:30 +0200 Subject: [PATCH 001/304] Raised version --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index d954c6d5..e6b7650f 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.0.5", + "version": "1.0.6", "compatibility": { "minimum": "13", "verified": "13.347", From 1a928e950c79f5870e0f0cd7bc5c82916ea023c2 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 29 Jan 2026 18:46:39 +0100 Subject: [PATCH 002/304] Initial v14 fixes --- .../sheets-configs/activeEffectConfig.mjs | 34 ++++++++ .../sheets/api/application-mixin.mjs | 29 +------ module/applications/ui/countdowns.mjs | 11 +-- module/canvas/placeables/templateLayer.mjs | 2 +- module/config/generalConfig.mjs | 39 ++++++++- module/data/action/beastformAction.mjs | 80 ------------------- module/data/activeEffect/baseEffect.mjs | 35 +++++++- module/data/activeEffect/beastformEffect.mjs | 1 + module/data/fields/actionField.mjs | 14 +++- module/data/item/beastform.mjs | 29 +++---- module/data/item/subclass.mjs | 6 +- module/dice/damageRoll.mjs | 2 +- module/dice/dhRoll.mjs | 4 +- module/dice/dualityRoll.mjs | 4 +- module/documents/activeEffect.mjs | 20 +++-- module/helpers/utils.mjs | 7 +- system.json | 8 +- templates/sheets/activeEffect/change.hbs | 17 ++++ templates/sheets/activeEffect/changes.hbs | 35 +++----- 19 files changed, 197 insertions(+), 180 deletions(-) create mode 100644 templates/sheets/activeEffect/change.hbs diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index d7b1b536..aa6d9d54 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -33,6 +33,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' }, changes: { template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs', + templates: ['systems/daggerheart/templates/sheets/activeEffect/change.hbs'], scrollable: ['ol[data-changes]'] }, footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' } @@ -121,8 +122,41 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac })); } break; + case 'changes': + const fields = this.document.system.schema.fields.changes.element.fields; + partContext.changes = await Promise.all( + foundry.utils + .deepClone(context.source.changes) + .map((c, i) => this._prepareChangeContext(c, i, fields)) + ); + break; } return partContext; } + + _prepareChangeContext(change, index, fields) { + if (typeof change.value !== 'string') change.value = JSON.stringify(change.value); + const defaultPriority = game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type]?.defaultPriority; + Object.assign( + change, + ['key', 'type', 'value', 'priority'].reduce((paths, fieldName) => { + paths[`${fieldName}Path`] = `system.changes.${index}.${fieldName}`; + return paths; + }, {}) + ); + return ( + game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type].render?.( + change, + index, + defaultPriority + ) ?? + renderTemplate('systems/daggerheart/templates/sheets/activeEffect/change.hbs', { + change, + index, + defaultPriority, + fields + }) + ); + } } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 3c0444eb..ab6a352d 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -72,18 +72,6 @@ const typeSettingsMap = { */ export default function DHApplicationMixin(Base) { class DHSheetV2 extends HandlebarsApplicationMixin(Base) { - /** - * @param {DHSheetV2Configuration} [options={}] - */ - constructor(options = {}) { - super(options); - /** - * @type {foundry.applications.ux.DragDrop[]} - * @private - */ - this._dragDrop = this._createDragDropHandlers(); - } - #nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard']; /** @@ -177,7 +165,7 @@ export default function DHApplicationMixin(Base) { /**@inheritdoc */ _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); - this._dragDrop.forEach(d => d.bind(htmlElement)); + // this._dragDrop.forEach(d => d.bind(htmlElement)); // Handle delta inputs for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) { @@ -350,21 +338,6 @@ export default function DHApplicationMixin(Base) { /* Drag and Drop */ /* -------------------------------------------- */ - /** - * Creates drag-drop handlers from the configured options. - * @returns {foundry.applications.ux.DragDrop[]} - * @private - */ - _createDragDropHandlers() { - return this.options.dragDrop.map(d => { - d.callbacks = { - dragstart: this._onDragStart.bind(this), - drop: this._onDrop.bind(this) - }; - return new foundry.applications.ux.DragDrop.implementation(d); - }); - } - /** * Handle dragStart event. * @param {DragEvent} event diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 42920a4a..0f83a05f 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -52,10 +52,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application } }; - get element() { - return document.body.querySelector('.daggerheart.dh-style.countdowns'); - } - /**@inheritdoc */ async _renderFrame(options) { const frame = await super._renderFrame(options); @@ -68,6 +64,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application const header = frame.querySelector('.window-header'); header.querySelector('button[data-action="close"]').remove(); + header.querySelector('button[data-action="toggleControls"]').remove(); if (game.user.isGM) { const editTooltip = game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle'); @@ -278,10 +275,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return acc; }, {}) }; - await emitAsGM(GMUpdateEvent.UpdateCountdowns, - DhCountdowns.gmSetSetting.bind(settings), - settings, null, { - refreshType: RefreshType.Countdown + await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, { + refreshType: RefreshType.Countdown }); } diff --git a/module/canvas/placeables/templateLayer.mjs b/module/canvas/placeables/templateLayer.mjs index 551a06cc..55bd7eb2 100644 --- a/module/canvas/placeables/templateLayer.mjs +++ b/module/canvas/placeables/templateLayer.mjs @@ -6,7 +6,7 @@ export default class DhTemplateLayer extends foundry.canvas.layers.TemplateLayer order: 2, title: 'CONTROLS.GroupMeasure', icon: 'fa-solid fa-ruler-combined', - visible: game.user.can('TEMPLATE_CREATE'), + visible: game.user.can('REGION_CREATE'), onChange: (event, active) => { if (active) canvas.templates.activate(); }, diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index be1dfce1..677351cd 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -70,8 +70,12 @@ export const range = { } }; +/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */ export const templateTypes = { - ...CONST.MEASURED_TEMPLATE_TYPES, + CIRCLE: 'circle', + CONE: 'cone', + RECTANGLE: 'rect', + RAY: 'ray', EMANATION: 'emanation', INFRONT: 'inFront' }; @@ -737,3 +741,36 @@ export const sceneRangeMeasurementSetting = { label: 'Custom' } }; + +export const activeEffectModes = { + custom: { + id: 'custom', + priority: 0, + label: 'EFFECT.CHANGES.TYPES.custom' + }, + multiply: { + id: 'multiply', + priority: 10, + label: 'EFFECT.CHANGES.TYPES.multiply' + }, + add: { + id: 'add', + priority: 20, + label: 'EFFECT.CHANGES.TYPES.add' + }, + downgrade: { + id: 'downgrade', + priority: 30, + label: 'EFFECT.CHANGES.TYPES.downgrade' + }, + upgrade: { + id: 'upgrade', + priority: 40, + label: 'EFFECT.CHANGES.TYPES.upgrade' + }, + override: { + id: 'override', + priority: 50, + label: 'EFFECT.CHANGES.TYPES.override' + } +}; diff --git a/module/data/action/beastformAction.mjs b/module/data/action/beastformAction.mjs index 657cfde2..8855b122 100644 --- a/module/data/action/beastformAction.mjs +++ b/module/data/action/beastformAction.mjs @@ -2,84 +2,4 @@ import DHBaseAction from './baseAction.mjs'; export default class DhBeastformAction extends DHBaseAction { static extraSchemas = [...super.extraSchemas, 'beastform']; - - /* async use(event, options) { - const beastformConfig = this.prepareBeastformConfig(); - - const abort = await this.handleActiveTransformations(); - if (abort) return; - - const calcCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(this, this.cost); - const hasCost = game.system.api.fields.ActionFields.CostField.hasCost.call(this, calcCosts); - if (!hasCost) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources')); - return; - } - - const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, this.item); - if (!selected) return; - - const result = await super.use(event, options); - if (!result) return; - - await this.transform(selected, evolved, hybrid); - } - - prepareBeastformConfig(config) { - const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers; - const actorLevel = this.actor.system.levelData.level.current; - const actorTier = - Object.values(settingsTiers).find( - tier => actorLevel >= tier.levels.start && actorLevel <= tier.levels.end - ) ?? 1; - - return { - tierLimit: this.beastform.tierAccess.exact ?? actorTier - }; - } - - async transform(selectedForm, evolvedData, hybridData) { - const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject(); - const beastformEffect = formData.effects.find(x => x.type === 'beastform'); - if (!beastformEffect) { - ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); - return; - } - - if (evolvedData?.form) { - const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform'); - if (!evolvedForm) { - ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); - return; - } - - beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes]; - formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)]; - } - - if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) { - formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => { - Object.keys(formCategory).forEach(advantageKey => { - advantages[advantageKey] = formCategory[advantageKey]; - }); - return advantages; - }, {}); - formData.system.features = [ - ...formData.system.features, - ...Object.values(hybridData.features).flatMap(x => Object.keys(x)) - ]; - } - - this.actor.createEmbeddedDocuments('Item', [formData]); - } - - async handleActiveTransformations() { - const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform'); - const existingEffects = beastformEffects.length > 0; - await this.actor.deleteEmbeddedDocuments( - 'ActiveEffect', - beastformEffects.map(x => x.id) - ); - return existingEffects; - } */ } diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index ea74531d..b8f2c65f 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -12,11 +12,27 @@ * "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility) */ -export default class BaseEffect extends foundry.abstract.TypeDataModel { +export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { static defineSchema() { const fields = foundry.data.fields; return { + ...super.defineSchema(), + changes: new fields.ArrayField( + new fields.SchemaField({ + key: new fields.StringField({ required: true }), + type: new fields.StringField({ + required: true, + blank: false, + choices: CONFIG.DH.GENERAL.activeEffectModes, + initial: CONFIG.DH.GENERAL.activeEffectModes.add.id, + validate: BaseEffect.#validateType + }), + value: new fields.AnyField({ required: true, nullable: true, serializable: true, initial: '' }), + phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), + priority: new fields.NumberField() + }) + ), rangeDependence: new fields.SchemaField({ enabled: new fields.BooleanField({ required: true, @@ -45,6 +61,23 @@ export default class BaseEffect extends foundry.abstract.TypeDataModel { }; } + /** + * Validate that an {@link EffectChangeData#type} string is well-formed. + * @param {string} type The string to be validated + * @returns {true} + * @throws {Error} An error if the type string is malformed + */ + static #validateType(type) { + if (type.length < 3) throw new Error('must be at least three characters long'); + if (!/^custom\.-?\d+$/.test(type) && !type.split('.').every(s => /^[a-z0-9]+$/i.test(s))) { + throw new Error( + 'A change type must either be a sequence of dot-delimited, alpha-numeric substrings or of the form' + + ' "custom.{number}"' + ); + } + return true; + } + static getDefaultObject() { return { name: 'New Effect', diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 65e36606..53430ba3 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -5,6 +5,7 @@ export default class BeastformEffect extends BaseEffect { static defineSchema() { const fields = foundry.data.fields; return { + ...super.defineSchema(), characterTokenData: new fields.SchemaField({ usesDynamicToken: new fields.BooleanField({ initial: false }), tokenImg: new fields.FilePathField({ diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index 0d71ab86..a6d0abbe 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -111,9 +111,17 @@ export class ActionField extends foundry.data.fields.ObjectField { * @param {object} sourceData Candidate source data of the root model. * @param {any} fieldData The value of this field within the source data. */ - migrateSource(sourceData, fieldData) { - const cls = this.getModel(fieldData); - if (cls) cls.migrateDataSafe(fieldData); + _migrate(sourceData, _fieldData) { + const source = sourceData ?? this.options.initial; + if (!source) return sourceData; + + const cls = this.getModel(source); + if (cls) { + cls.migrateDataSafe(source); + return source; + } + + return sourceData; } } diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index dd491169..9c8df9ea 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -101,12 +101,13 @@ export default class DHBeastform extends BaseDataItem { const effect = this.parent.effects.find(x => x.type === 'beastform'); if (!effect) return null; - const traitBonus = effect.changes.find(x => x.key === `system.traits.${this.mainTrait}.value`)?.value ?? 0; - const evasionBonus = effect.changes.find(x => x.key === 'system.evasion')?.value ?? 0; + const traitBonus = + effect.system.changes.find(x => x.key === `system.traits.${this.mainTrait}.value`)?.value ?? 0; + const evasionBonus = effect.system.changes.find(x => x.key === 'system.evasion')?.value ?? 0; - const damageDiceIndex = effect.changes.find(x => x.key === 'system.rules.attack.damage.diceIndex'); + const damageDiceIndex = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.diceIndex'); const damageDice = damageDiceIndex ? Object.keys(CONFIG.DH.GENERAL.diceTypes)[damageDiceIndex.value] : null; - const damageBonus = effect.changes.find(x => x.key === 'system.rules.attack.damage.bonus')?.value ?? 0; + const damageBonus = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.bonus')?.value ?? 0; return { trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.mainTrait].label), @@ -169,17 +170,17 @@ 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: Object.values(this.advantageOn) - .map(x => x.value) - .join(', ') - } - ], system: { + changes: [ + ...beastformEffect.system.changes, + { + key: 'system.advantageSources', + mode: 2, + value: Object.values(this.advantageOn) + .map(x => x.value) + .join(', ') + } + ], characterTokenData: { usesDynamicToken: this.parent.parent.prototypeToken.ring.enabled, tokenImg: this.parent.parent.prototypeToken.texture.src, diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 375588fb..33acb311 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -51,6 +51,9 @@ export default class DHSubclass extends BaseDataItem { } async _preCreate(data, options, user) { + const allowed = await super._preCreate(data, options, user); + if (allowed === false) return; + if (this.actor?.type === 'character') { const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `Item.${data._id}`; if (this.actor.system.class.subclass) { @@ -85,8 +88,5 @@ export default class DHSubclass extends BaseDataItem { } } } - - const allowed = await super._preCreate(data, options, user); - if (allowed === false) return; } } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index cd26eb21..e1acaf40 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -191,7 +191,7 @@ export default class DamageRoll extends DHRoll { if (config.data.parent.appliedEffects) { // Bardic Rally const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => { - const change = c.changes.find(ch => ch.key === 'system.bonuses.rally'); + const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally'); if (change) a.push({ value: c.id, label: change.value }); return a; }, []); diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 1977c7ea..317536a0 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -249,12 +249,12 @@ export default class DHRoll extends Roll { const changeKeys = this.getActionChangeKeys(); return ( this.options.effects?.reduce((acc, effect) => { - if (effect.changes.some(x => changeKeys.some(key => x.key.includes(key)))) { + if (effect.system.changes.some(x => changeKeys.some(key => x.key.includes(key)))) { acc[effect.id] = { id: effect.id, name: effect.name, description: effect.description, - changes: effect.changes, + changes: effect.system.changes, origEffect: effect, selected: !effect.disabled }; diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index e65d0ff5..a19c7441 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -67,7 +67,7 @@ export default class DualityRoll extends D20Roll { setRallyChoices() { return this.data?.parent?.appliedEffects.reduce((a, c) => { - const change = c.changes.find(ch => ch.key === 'system.bonuses.rally'); + const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally'); if (change) a.push({ value: c.id, label: change.value }); return a; }, []); @@ -179,7 +179,7 @@ export default class DualityRoll extends D20Roll { static async buildConfigure(config = {}, message = {}) { config.dialog ??= {}; config.guaranteedCritical = config.data?.parent?.appliedEffects.reduce((a, c) => { - const change = c.changes.find(ch => ch.key === 'system.rules.roll.guaranteedCritical'); + const change = c.system.changes.find(ch => ch.key === 'system.rules.roll.guaranteedCritical'); if (change) a = true; return a; }, false); diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 5e9b0c3b..330bd838 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -109,17 +109,25 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /**@inheritdoc*/ static applyField(model, change, field) { - change.value = DhActiveEffect.getChangeValue(model, change, change.effect); + change.key = DhActiveEffect.getChangeKey(model, change, change.effect); super.applyField(model, change, field); } /** */ + static getChangeKey(model, change, effect) { + return DhActiveEffect.parseValue(change.key, model, change, effect); + } + static getChangeValue(model, change, effect) { - let value = change.value; - const isOriginTarget = value.toLowerCase().includes('origin.@'); + return DhActiveEffect.parseValue(change.value, model, change, effect); + } + + static parseValue(value, model, change, effect) { + let key = value; + const isOriginTarget = key.toLowerCase().includes('origin.@'); let parseModel = model; if (isOriginTarget && effect.origin) { - value = change.value.replaceAll(/origin\.@/gi, '@'); + key = change.key.replaceAll(/origin\.@/gi, '@'); try { const originEffect = foundry.utils.fromUuidSync(effect.origin); const doc = @@ -130,8 +138,8 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { } catch (_) {} } - const evalValue = this.effectSafeEval(itemAbleRollParse(value, parseModel, effect.parent)); - return evalValue ?? value; + const evalValue = this.effectSafeEval(itemAbleRollParse(key, parseModel, effect.parent)); + return evalValue ?? key; } /** diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index c0dd45bd..bb9a0cfa 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -415,7 +415,12 @@ export async function createEmbeddedItemWithEffects(actor, baseData, update) { ...baseData, id: data.id, uuid: data.uuid, - effects: data.effects?.map(effect => effect.toObject()) + _uuid: data.uuid, + effects: data.effects?.map(effect => effect.toObject()), + _stats: { + ...data._stats, + compendiumSource: data.pack ? `Compendium.${data.pack}.Item.${data.id}` : null + } } ]); diff --git a/system.json b/system.json index 4a5eee0f..bd650096 100644 --- a/system.json +++ b/system.json @@ -2,11 +2,11 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.6.1", + "version": "2.0.0", "compatibility": { - "minimum": "13.346", - "verified": "13.351", - "maximum": "13" + "minimum": "14.353", + "verified": "14.353", + "maximum": "14" }, "authors": [ { diff --git a/templates/sheets/activeEffect/change.hbs b/templates/sheets/activeEffect/change.hbs new file mode 100644 index 00000000..60cb0d85 --- /dev/null +++ b/templates/sheets/activeEffect/change.hbs @@ -0,0 +1,17 @@ +
  • +
    + {{formInput fields.key name=change.keyPath value=change.key}} +
    +
    + {{formInput fields.type name=change.typePath value=change.type localize=true}} +
    +
    + {{formInput fields.value name=change.valuePath value=change.value elementType="input"}} +
    +
    + {{formInput fields.priority name=change.priorityPath value=change.priority placeholder=defaultPriority}} +
    +
    + +
    +
  • diff --git a/templates/sheets/activeEffect/changes.hbs b/templates/sheets/activeEffect/changes.hbs index 75f49e4a..8604daf6 100644 --- a/templates/sheets/activeEffect/changes.hbs +++ b/templates/sheets/activeEffect/changes.hbs @@ -1,31 +1,16 @@
    -
    {{localize "EFFECT.ChangeKey"}}
    -
    {{localize "EFFECT.ChangeMode"}}
    -
    {{localize "EFFECT.ChangeValue"}}
    -
    {{localize "EFFECT.ChangePriority"}}
    -
    +
    {{localize "EFFECT.FIELDS.changes.element.key.label"}}
    +
    {{localize "EFFECT.FIELDS.changes.element.type.label"}}
    +
    {{localize "EFFECT.FIELDS.changes.element.value.label"}}
    +
    {{localize "EFFECT.FIELDS.changes.element.priority.label"}}
    +
    + +
      - {{#each source.changes as |change i|}} - {{#with ../fields.changes.element.fields as |changeFields|}} -
    1. -
      - -
      -
      - {{formInput changeFields.mode name=(concat "changes." i ".mode") value=change.mode choices=@root.modes}} -
      -
      - {{formInput changeFields.value name=(concat "changes." i ".value") value=change.value}} -
      -
      - {{formInput changeFields.priority name=(concat "changes." i ".priority") value=change.priority - placeholder=(lookup ../../priorities change.mode)}} -
      -
      -
    2. - {{/with}} + {{#each changes as |change|}} + {{{change}}} {{/each}}
    -
    \ No newline at end of file + From 6e2d70094540f4750a56e0857d1cefedc685aca8 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 29 Jan 2026 18:51:13 +0100 Subject: [PATCH 003/304] . --- templates/sheets/activeEffect/details.hbs | 3 +- templates/sheets/activeEffect/settings.hbs | 67 +++++++++++++--------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/templates/sheets/activeEffect/details.hbs b/templates/sheets/activeEffect/details.hbs index 22c95a1e..72e77d5e 100644 --- a/templates/sheets/activeEffect/details.hbs +++ b/templates/sheets/activeEffect/details.hbs @@ -12,4 +12,5 @@ {{/if}} {{formGroup fields.statuses value=source.statuses options=statuses rootId=rootId classes="statuses"}} - + {{formGroup fields.showIcon value=source.showIcon options=showIconOptions rootId=rootId}} + \ No newline at end of file diff --git a/templates/sheets/activeEffect/settings.hbs b/templates/sheets/activeEffect/settings.hbs index cf98c786..6ccd9ee9 100644 --- a/templates/sheets/activeEffect/settings.hbs +++ b/templates/sheets/activeEffect/settings.hbs @@ -7,34 +7,47 @@ {{formGroup systemFields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }} {{formGroup systemFields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }} +{{!-- + {{#if start}} +
    + {{localize "EFFECT.START.Header"}} +
    + {{localize "EFFECT.FIELDS.start.time.label"}} +
    {{start.time}}
    +
    + {{#if start.combat}} +
    + {{localize "DOCUMENT.Combat"}} +
    + {{localize "EFFECT.START.Combat" round=start.round turn=start.turn}} +
    +
    + {{#if start.combatant}} +
    + {{localize "DOCUMENT.Combatant"}} +
    + {{localize "EFFECT.START.Combatant" combatant=start.combatantName initiative=start.combatantInitiative}} +
    +
    + {{/if}} + {{/if}} +
    + {{/if}} --}} -
    - {{formGroup fields.duration.fields.seconds value=source.duration.seconds rootId=rootId}} - {{formGroup fields.duration.fields.startTime value=source.duration.startTime rootId=rootId}} -
    - -
    -
    - + {{!--
    + {{localize "EFFECT.DURATION.Header"}} +
    +
    - - {{formInput fields.duration.fields.rounds value=source.duration.rounds - id=(concat rootId "-duration.rounds")}} - - {{formInput fields.duration.fields.turns value=source.duration.turns - id=(concat rootId "-duration.turns")}} + {{formInput fields.duration.fields.value value=source.duration.value id=(concat rootId "-duration.value") + aria=(object label=(localize "EFFECT.FIELDS.duration.value.label"))}} + {{formInput fields.duration.fields.units value=source.duration.units id=(concat rootId "-duration.units") + options=durationUnits aria=(object label=(localize "EFFECT.FIELDS.duration.units.label"))}}
    -
    - -
    - - {{formInput fields.duration.fields.startRound value=source.duration.startRound - id=(concat rootId "-duration.startRound")}} - - {{formInput fields.duration.fields.startTurn value=source.duration.startTurn - id=(concat rootId "-duration.startTurn")}} -
    -
    -
    - + {{formGroup fields.duration.fields.expiry choices=expiryEvents value=source.duration.expiry rootId=rootId}} +
    --}} + \ No newline at end of file From 2c36da84334e7a7b924d6932623e7e239a3b82b4 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 31 Jan 2026 01:46:24 +0100 Subject: [PATCH 004/304] Raised v14 version and fixed startup warnings --- module/documents/activeEffect.mjs | 19 ++++++------------- system.json | 4 ++-- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 330bd838..c34ec62f 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -108,22 +108,15 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /* -------------------------------------------- */ /**@inheritdoc*/ - static applyField(model, change, field) { - change.key = DhActiveEffect.getChangeKey(model, change, change.effect); - super.applyField(model, change, field); - } - - /** */ - static getChangeKey(model, change, effect) { - return DhActiveEffect.parseValue(change.key, model, change, effect); + static applyChangeField(model, change, field) { + change.value = Number.isNumeric(change.value) + ? change.value + : DhActiveEffect.getChangeValue(model, change, change.effect); + super.applyChangeField(model, change, field); } static getChangeValue(model, change, effect) { - return DhActiveEffect.parseValue(change.value, model, change, effect); - } - - static parseValue(value, model, change, effect) { - let key = value; + let key = change.value; const isOriginTarget = key.toLowerCase().includes('origin.@'); let parseModel = model; if (isOriginTarget && effect.origin) { diff --git a/system.json b/system.json index bd650096..48a2319a 100644 --- a/system.json +++ b/system.json @@ -4,8 +4,8 @@ "description": "An unofficial implementation of the Daggerheart system", "version": "2.0.0", "compatibility": { - "minimum": "14.353", - "verified": "14.353", + "minimum": "14.354", + "verified": "14.354", "maximum": "14" }, "authors": [ From 4c51bb589996b4b643c5b9aac11781a26946e780 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 31 Jan 2026 15:08:07 +0100 Subject: [PATCH 005/304] 1613-Rolltable-delete-formula-buttons --- module/applications/sheets/rollTables/rollTable.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/module/applications/sheets/rollTables/rollTable.mjs b/module/applications/sheets/rollTables/rollTable.mjs index 9ead6814..edb0a734 100644 --- a/module/applications/sheets/rollTables/rollTable.mjs +++ b/module/applications/sheets/rollTables/rollTable.mjs @@ -108,14 +108,16 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa getSystemFlagUpdate() { const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce( (acc, formulaKey) => { - if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[`-=${formulaKey}`] = null; + if (!this.daggerheartFlag.altFormula[formulaKey]) + acc.altFormula[formulaKey] = foundry.data.operators.ForcedDeletion.create(); return acc; }, { altFormula: {} } ); - return { ['flags.daggerheart']: foundry.utils.mergeObject(this.daggerheartFlag.toObject(), deleteUpdate) }; + const flagData = this.daggerheartFlag.toObject(); + return { ...flagData, altFormula: { ...flagData.altFormula, ...deleteUpdate.altFormula } }; } static async #addFormula() { @@ -127,7 +129,7 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa static async #removeFormula(_event, target) { await this.daggerheartFlag.updateSource({ - [`altFormula.-=${target.dataset.key}`]: null + [`altFormula.${target.dataset.key}`]: foundry.data.operators.ForcedDeletion.create() }); this.render({ internalRefresh: true }); } From 9553f3387f3987cca55a16c9f1b8b4f78e89a358 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:09:07 +0100 Subject: [PATCH 006/304] Fixed sceneConfig, sceneNavigation and SceneEnvironments (#1616) --- .../scene/sceneConfigSettings.mjs | 8 +++ module/applications/ui/sceneNavigation.mjs | 5 +- styles/less/ui/scene-config/scene-config.less | 2 + .../ui/sceneNavigation/scene-navigation.hbs | 52 ++++++++++++++++++- 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index 98e18f09..dda4330a 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -62,7 +62,15 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S } async _onDrop(event) { + event.stopPropagation(); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); + if (data.type === 'Level') { + const level = await foundry.documents.Level.fromDropData(data); + if (level?.parent === this.document) return this._onSortLevel(event, level); + + return; + } + const item = await foundry.utils.fromUuid(data.uuid); if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') { let sceneUuid = data.uuid; diff --git a/module/applications/ui/sceneNavigation.mjs b/module/applications/ui/sceneNavigation.mjs index 0a3e08a5..bc906dac 100644 --- a/module/applications/ui/sceneNavigation.mjs +++ b/module/applications/ui/sceneNavigation.mjs @@ -31,7 +31,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi const environments = daggerheartInfo.sceneEnvironments.filter( x => x && x.testUserPermission(game.user, 'LIMITED') ); - const hasEnvironments = environments.length > 0 && x.isView; + const hasEnvironments = environments.length > 0 && x.active; return { ...x, hasEnvironments, @@ -39,9 +39,10 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi environments: environments }; }); + context.scenes.active = extendScenes(context.scenes.active); context.scenes.inactive = extendScenes(context.scenes.inactive); - + context.scenes.viewed = context.scenes.viewed ? extendScenes([context.scenes.viewed])[0] : null; return context; } diff --git a/styles/less/ui/scene-config/scene-config.less b/styles/less/ui/scene-config/scene-config.less index 664e7526..ba1afb0b 100644 --- a/styles/less/ui/scene-config/scene-config.less +++ b/styles/less/ui/scene-config/scene-config.less @@ -13,6 +13,8 @@ .application.sheet.scene-config { .sheet-tabs.tabs { + font-size: 12px; + a[data-tab='dh'] { display: flex; align-items: center; diff --git a/templates/ui/sceneNavigation/scene-navigation.hbs b/templates/ui/sceneNavigation/scene-navigation.hbs index 41e9e3e8..933d2074 100644 --- a/templates/ui/sceneNavigation/scene-navigation.hbs +++ b/templates/ui/sceneNavigation/scene-navigation.hbs @@ -1,10 +1,40 @@ + + + +{{#*inline ".scene"}} +
  • +
    + {{ name }} + {{#if users}} +
      + {{#each users}} +
    • {{ letter }}
    • + {{/each}} +
    + {{/if}} +
    + {{#if hasEnvironments}} + + {{/if}} +
  • +{{/inline}} + From cd52aa8f9ceab205263a1f5037b6342609c65e6c Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 31 Jan 2026 18:31:10 +0100 Subject: [PATCH 007/304] Updated from special database update syntax to DataFieldOperators --- .../characterCreation/characterCreation.mjs | 2 +- module/applications/dialogs/characterResetDialog.mjs | 6 +++--- module/applications/dialogs/tagTeamDialog.mjs | 2 +- module/applications/levelup/levelup.mjs | 6 +++--- module/applications/scene/sceneConfigSettings.mjs | 2 +- module/applications/settings/homebrewSettings.mjs | 12 ++++++------ .../sheets-configs/adversary-settings.mjs | 2 +- .../sheets-configs/character-settings.mjs | 6 +++--- .../sheets-configs/companion-settings.mjs | 2 +- .../sheets-configs/environment-settings.mjs | 2 +- .../sheets-configs/setting-feature-config.mjs | 2 +- .../sheets-configs/token-config-mixin.mjs | 2 +- module/applications/sheets/items/beastform.mjs | 2 +- module/applications/sheets/rollTables/rollTable.mjs | 5 ++--- module/applications/ui/countdownEdit.mjs | 2 +- module/data/activeEffect/beastformEffect.mjs | 2 +- module/data/actor/base.mjs | 4 +--- module/data/actor/party.mjs | 2 +- module/data/fields/actionField.mjs | 4 ++-- module/data/item/armor.mjs | 2 +- module/data/item/base.mjs | 6 +++--- module/data/item/weapon.mjs | 2 +- module/documents/actor.mjs | 4 ++-- module/helpers/utils.mjs | 4 ++-- module/systemRegistration/migrations.mjs | 2 +- 25 files changed, 42 insertions(+), 45 deletions(-) diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index aa764c56..e6c0f299 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -554,7 +554,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl experiences: { ...this.setup.experiences, ...Object.keys(this.character.system.experiences).reduce((acc, key) => { - acc[`-=${key}`] = null; + acc[`${key}`] = _del; return acc; }, {}) } diff --git a/module/applications/dialogs/characterResetDialog.mjs b/module/applications/dialogs/characterResetDialog.mjs index 0836af9c..aecebc7c 100644 --- a/module/applications/dialogs/characterResetDialog.mjs +++ b/module/applications/dialogs/characterResetDialog.mjs @@ -77,8 +77,8 @@ export default class CharacterResetDialog extends HandlebarsApplicationMixin(App if (!this.data.optional.portrait.keep) { foundry.utils.setProperty(update, 'img', this.actor.schema.fields.img.initial(this.actor)); - foundry.utils.setProperty(update, 'prototypeToken.==texture', {}); - foundry.utils.setProperty(update, 'prototypeToken.==ring', {}); + foundry.utils.setProperty(update, 'prototypeToken.texture', _replace({})); + foundry.utils.setProperty(update, 'prototypeToken.ring', _replace({})); } if (this.data.optional.biography.keep) @@ -89,7 +89,7 @@ export default class CharacterResetDialog extends HandlebarsApplicationMixin(App const { system, ...rest } = update; await this.actor.update({ ...rest, - '==system': system ?? {} + system: _replace(system ?? {}) }); const inventoryItemTypes = ['weapon', 'armor', 'consumable', 'loot']; diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index d1a1e123..c28b773c 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -168,7 +168,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } static async #removeMember(_, button) { - const update = { [`members.-=${button.dataset.characterId}`]: null }; + const update = { [`members.${button.dataset.characterId}`]: _del }; if (this.data.initiator.id === button.dataset.characterId) { update.iniator = { id: null }; } diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index ba6110cc..c4616d9a 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -477,7 +477,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const secondaryData = Object.keys( foundry.utils.getProperty(this.levelup, `${target.dataset.path}.secondaryData`) ).reduce((acc, key) => { - acc[`-=${key}`] = null; + acc[key] = _del; return acc; }, {}); await this.levelup.updateSource({ @@ -511,9 +511,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`); if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) { // Simple handling that doesn't cover potential Custom LevelTiers. - update[`${basePath}.-=${button.dataset.option}`] = null; + update[`${basePath}.${button.dataset.option}`] = _del; } else { - update[`${basePath}.${button.dataset.option}.-=${button.dataset.checkboxNr}`] = null; + update[`${basePath}.${button.dataset.option}.${button.dataset.checkboxNr}`] = _del; } } else { if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) { diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index dda4330a..ceb403cf 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -118,7 +118,7 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) { if (!submitData.flags.daggerheart.sceneEnvironments[key]) { - submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null; + submitData.flags.daggerheart.sceneEnvironments[key] = _del; } } diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 6e2e665d..cecd12d7 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -228,7 +228,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli const isDowntime = ['shortRest', 'longRest'].includes(type); const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`; await this.settings.updateSource({ - [`${path}.-=${id}`]: null + [`${path}.${id}`]: _del }); this.render(); } @@ -250,7 +250,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli const fields = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).schema.fields; const removeUpdate = Object.keys(this.settings.restMoves[target.dataset.type].moves).reduce((acc, key) => { - acc[`-=${key}`] = null; + acc[key] = _del; return acc; }, {}); @@ -310,7 +310,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli [`itemFeatures.${target.dataset.type}`]: Object.keys( this.settings.itemFeatures[target.dataset.type] ).reduce((acc, key) => { - acc[`-=${key}`] = null; + acc[key] = _del; return acc; }, {}) @@ -383,12 +383,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli if (!confirmed) return; await this.settings.updateSource({ - [`domains.-=${this.selected.domain}`]: null + [`domains.${this.selected.domain}`]: _del }); const currentSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew); if (currentSettings.domains[this.selected.domain]) { - await currentSettings.updateSource({ [`domains.-=${this.selected.domain}`]: null }); + await currentSettings.updateSource({ [`domains.${this.selected.domain}`]: _del }); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, currentSettings); } @@ -435,7 +435,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli static async deleteAdversaryType(_, target) { const { key } = target.dataset; - await this.settings.updateSource({ [`adversaryTypes.-=${key}`]: null }); + await this.settings.updateSource({ [`adversaryTypes.${key}`]: _del }); this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType; this.render(); diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index d3d215be..6593f23d 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -95,7 +95,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings { }); if (!confirmed) return; - await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null }); + await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del }); } async _onDragStart(event) { diff --git a/module/applications/sheets-configs/character-settings.mjs b/module/applications/sheets-configs/character-settings.mjs index 20a09cfc..c655b23f 100644 --- a/module/applications/sheets-configs/character-settings.mjs +++ b/module/applications/sheets-configs/character-settings.mjs @@ -101,8 +101,8 @@ export default class DHCharacterSettings extends DHBaseActorSettings { if (relinkAchievementData.length > 0) { relinkAchievementData.forEach(data => { - updates[`system.levelData.levelups.${data.levelKey}.achievements.experiences.-=${data.experience}`] = - null; + updates[`system.levelData.levelups.${data.levelKey}.achievements.experiences.${data.experience}`] = + _del; }); } else if (relinkSelectionData.length > 0) { relinkSelectionData.forEach(data => { @@ -137,7 +137,7 @@ export default class DHCharacterSettings extends DHBaseActorSettings { await this.actor.update({ ...updates, - [`system.experiences.-=${target.dataset.experience}`]: null + [`system.experiences.${target.dataset.experience}`]: _del }); } } diff --git a/module/applications/sheets-configs/companion-settings.mjs b/module/applications/sheets-configs/companion-settings.mjs index 8aa21479..6c1265af 100644 --- a/module/applications/sheets-configs/companion-settings.mjs +++ b/module/applications/sheets-configs/companion-settings.mjs @@ -117,6 +117,6 @@ export default class DHCompanionSettings extends DHBaseActorSettings { }); if (!confirmed) return; - await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null }); + await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del }); } } diff --git a/module/applications/sheets-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index 15f5701d..5c039f85 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -79,7 +79,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { * @type {ApplicationClickAction} */ static async #removeCategory(_, target) { - await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null }); + await this.actor.update({ [`system.potentialAdversaries.${target.dataset.categoryId}`]: _del }); } /** diff --git a/module/applications/sheets-configs/setting-feature-config.mjs b/module/applications/sheets-configs/setting-feature-config.mjs index e8bf6109..e8ff7818 100644 --- a/module/applications/sheets-configs/setting-feature-config.mjs +++ b/module/applications/sheets-configs/setting-feature-config.mjs @@ -206,7 +206,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App } }); } else { - await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null }); + await this.settings.updateSource({ [`${this.actionsPath}.${target.dataset.id}`]: _del }); } this.move = foundry.utils.getProperty(this.settings, this.movePath); diff --git a/module/applications/sheets-configs/token-config-mixin.mjs b/module/applications/sheets-configs/token-config-mixin.mjs index c29b54ff..a23aa7f4 100644 --- a/module/applications/sheets-configs/token-config-mixin.mjs +++ b/module/applications/sheets-configs/token-config-mixin.mjs @@ -67,7 +67,7 @@ export default function DHTokenConfigMixin(Base) { changes.height = tokenSize; } - const deletions = { '-=actorId': null, '-=actorLink': null }; + const deletions = { actorId: _del, actorLink: _del }; const mergeOptions = { inplace: false, performDeletions: true }; this._preview.updateSource(mergeObject(changes, deletions, mergeOptions)); diff --git a/module/applications/sheets/items/beastform.mjs b/module/applications/sheets/items/beastform.mjs index 880c0796..0c9991c4 100644 --- a/module/applications/sheets/items/beastform.mjs +++ b/module/applications/sheets/items/beastform.mjs @@ -102,7 +102,7 @@ export default class BeastformSheet extends DHBaseItemSheet { async advantageOnRemove(event) { await this.document.update({ - [`system.advantageOn.-=${event.detail.data.value}`]: null + [`system.advantageOn.${event.detail.data.value}`]: _del }); } } diff --git a/module/applications/sheets/rollTables/rollTable.mjs b/module/applications/sheets/rollTables/rollTable.mjs index edb0a734..d7498e80 100644 --- a/module/applications/sheets/rollTables/rollTable.mjs +++ b/module/applications/sheets/rollTables/rollTable.mjs @@ -108,8 +108,7 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa getSystemFlagUpdate() { const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce( (acc, formulaKey) => { - if (!this.daggerheartFlag.altFormula[formulaKey]) - acc.altFormula[formulaKey] = foundry.data.operators.ForcedDeletion.create(); + if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[formulaKey] = _del; return acc; }, @@ -129,7 +128,7 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa static async #removeFormula(_event, target) { await this.daggerheartFlag.updateSource({ - [`altFormula.${target.dataset.key}`]: foundry.data.operators.ForcedDeletion.create() + [`altFormula.${target.dataset.key}`]: _del }); this.render({ internalRefresh: true }); } diff --git a/module/applications/ui/countdownEdit.mjs b/module/applications/ui/countdownEdit.mjs index 7f1deea3..8bb9fc1d 100644 --- a/module/applications/ui/countdownEdit.mjs +++ b/module/applications/ui/countdownEdit.mjs @@ -233,6 +233,6 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio } if (this.editingCountdowns.has(countdownId)) this.editingCountdowns.delete(countdownId); - this.updateSetting({ [`countdowns.-=${countdownId}`]: null }); + this.updateSetting({ [`countdowns.${countdownId}`]: _del }); } } diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 53430ba3..47e28b4c 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -100,7 +100,7 @@ export default class BeastformEffect extends BaseEffect { token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg } }, - 'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null } + 'flags.daggerheart': { beastformTokenImg: _del, beastformSubjectTexture: _del } }; }; diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 08308eab..833f1222 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -169,9 +169,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); await tagTeam.updateSource({ initiator: this.parent.id === tagTeam.initiator ? null : tagTeam.initiator, - members: Object.keys(tagTeam.members).find(x => x === this.parent.id) - ? { [`-=${this.parent.id}`]: null } - : {} + members: Object.keys(tagTeam.members).find(x => x === this.parent.id) ? { [this.parent.id]: _del } : {} }); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam); } diff --git a/module/data/actor/party.mjs b/module/data/actor/party.mjs index 236d65db..3eddf235 100644 --- a/module/data/actor/party.mjs +++ b/module/data/actor/party.mjs @@ -48,7 +48,7 @@ export default class DhParty extends BaseDataActor { initiator: this.partyMembers.some(x => x.id === tagTeam.initiator) ? null : tagTeam.initiator, members: Object.keys(tagTeam.members).reduce((acc, key) => { if (this.partyMembers.find(x => x.id === key)) { - acc[`-=${key}`] = null; + acc[key] = _del; } return acc; diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index a6d0abbe..1305a6d8 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -243,11 +243,11 @@ export function ActionMixin(Base) { : foundry.utils.getProperty(result, basePath); } - delete() { + async delete() { if (!this.inCollection) return this.item; const action = foundry.utils.getProperty(this.item, `system.${this.systemPath}`)?.get(this.id); if (!action) return this.item; - this.item.update({ [`system.${this.systemPath}.-=${this.id}`]: null }); + await this.item.update({ [`system.${this.systemPath}.${this.id}`]: _del }); // Does not work. Unsure why. It worked in v13 <_<' this.constructor._sheets.get(this.uuid)?.close(); } diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 3d4a62fa..050b66d4 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -87,7 +87,7 @@ export default class DHArmor extends AttachableItem { } await this.parent.deleteEmbeddedDocuments('ActiveEffect', effectIds); changes.system.actions = actionIds.reduce((acc, id) => { - acc[`-=${id}`] = null; + acc[id] = _del; return acc; }, {}); diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 84f39103..447da3bf 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -230,9 +230,9 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { if (changed.system?.actions) { const triggersToRemove = Object.keys(changed.system.actions).reduce((acc, key) => { - if (!changed.system.actions[key]) { - const strippedKey = key.replace('-=', ''); - acc.push(...this.actions.get(strippedKey).triggers.map(x => x.trigger)); + const action = changed.system.actions[key]; + if (action && Object.keys(action).length === 0) { + acc.push(...this.actions.get(key).triggers.map(x => x.trigger)); } return acc; diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index f333e5f3..85849a83 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -147,7 +147,7 @@ export default class DHWeapon extends AttachableItem { await this.parent.deleteEmbeddedDocuments('ActiveEffect', removedEffectsUpdate); changes.system.actions = removedActionsUpdate.reduce((acc, id) => { - acc[`-=${id}`] = null; + acc[id] = _del; return acc; }, {}); diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index e8bea0bf..cb51a255 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -142,7 +142,7 @@ export default class DhpActor extends Actor { } const updatedLevelups = Object.keys(this.system.levelData.levelups).reduce((acc, level) => { - if (Number(level) > usedLevel) acc[`-=${level}`] = null; + if (Number(level) > usedLevel) acc[level] = _del; return acc; }, {}); @@ -187,7 +187,7 @@ export default class DhpActor extends Actor { if (experiences.length > 0) { const getUpdate = () => ({ 'system.experiences': experiences.reduce((acc, key) => { - acc[`-=${key}`] = null; + acc[key] = _del; return acc; }, {}) }); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index bb9a0cfa..7ca2c41c 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -171,10 +171,10 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue [innerProperty]: innerPropertyDefaultValue }; } else { - acc[`${key}.-=${innerProperty}`] = null; + acc[`${key}.${innerProperty}`] = _del; } } else { - acc[`-=${key}`] = null; + acc[`${key}`] = _del; } return acc; diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index 743d42a4..4216c38f 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -197,7 +197,7 @@ export async function runMigrations() { const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator); const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => { if (!game.actors.some(actor => actor.id === id)) { - acc[`-=${id}`] = null; + acc[id] = _del; } return acc; }, {}); From ae91d6786f85686420a8c5f828de67f2800cd3cc Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 31 Jan 2026 19:32:51 +0100 Subject: [PATCH 008/304] Fixed actions not being delete:able --- module/data/fields/_module.mjs | 1 - module/data/fields/actionField.mjs | 11 ++- module/data/fields/mappingField.mjs | 128 ---------------------------- 3 files changed, 5 insertions(+), 135 deletions(-) delete mode 100644 module/data/fields/mappingField.mjs diff --git a/module/data/fields/_module.mjs b/module/data/fields/_module.mjs index 2a8ba454..58423979 100644 --- a/module/data/fields/_module.mjs +++ b/module/data/fields/_module.mjs @@ -3,5 +3,4 @@ export { default as FormulaField } from './formulaField.mjs'; export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs'; export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs'; export { default as TriggerField } from './triggerField.mjs'; -export { default as MappingField } from './mappingField.mjs'; export * as ActionFields from './action/_module.mjs'; diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index 1305a6d8..d0cc808b 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -1,6 +1,5 @@ import DHActionConfig from '../../applications/sheets-configs/action-config.mjs'; import { itemAbleRollParse } from '../../helpers/utils.mjs'; -import MappingField from './mappingField.mjs'; /** * Specialized collection type for stored actions. @@ -11,9 +10,9 @@ export class ActionCollection extends Collection { constructor(model, entries) { super(); this.#model = model; - for (const entry of entries) { - if (!(entry instanceof game.system.api.models.actions.actionsTypes.base)) continue; - this.set(entry._id, entry); + for (const [key, value] of entries) { + if (!(value instanceof game.system.api.models.actions.actionsTypes.base)) continue; + this.set(key, value); } } @@ -61,7 +60,7 @@ export class ActionCollection extends Collection { /** * Field that stores actions. */ -export class ActionsField extends MappingField { +export class ActionsField extends foundry.data.fields.TypedObjectField { constructor(options) { super(new ActionField(), options); } @@ -70,7 +69,7 @@ export class ActionsField extends MappingField { /** @inheritDoc */ initialize(value, model, options) { - const actions = Object.values(super.initialize(value, model, options)); + const actions = Object.entries(super.initialize(value, model, options)); return new ActionCollection(model, actions); } } diff --git a/module/data/fields/mappingField.mjs b/module/data/fields/mappingField.mjs deleted file mode 100644 index 31d91c76..00000000 --- a/module/data/fields/mappingField.mjs +++ /dev/null @@ -1,128 +0,0 @@ -/** - * A subclass of ObjectField that represents a mapping of keys to the provided DataField type. - * - * @param {DataField} model The class of DataField which should be embedded in this field. - * @param {MappingFieldOptions} [options={}] Options which configure the behavior of the field. - * @property {string[]} [initialKeys] Keys that will be created if no data is provided. - * @property {MappingFieldInitialValueBuilder} [initialValue] Function to calculate the initial value for a key. - * @property {boolean} [initialKeysOnly=false] Should the keys in the initialized data be limited to the keys provided - * by `options.initialKeys`? - */ -export default class MappingField extends foundry.data.fields.ObjectField { - constructor(model, options) { - if (!(model instanceof foundry.data.fields.DataField)) { - throw new Error('MappingField must have a DataField as its contained element'); - } - super(options); - - /** - * The embedded DataField definition which is contained in this field. - * @type {DataField} - */ - this.model = model; - model.parent = this; - } - - /* -------------------------------------------- */ - - /** @inheritDoc */ - static get _defaults() { - return foundry.utils.mergeObject(super._defaults, { - initialKeys: null, - initialValue: null, - initialKeysOnly: false - }); - } - - /* -------------------------------------------- */ - - /** @inheritDoc */ - _cleanType(value, options) { - Object.entries(value).forEach(([k, v]) => { - if (k.startsWith('-=')) return; - value[k] = this.model.clean(v, options); - }); - return value; - } - - /* -------------------------------------------- */ - - /** @inheritDoc */ - getInitialValue(data) { - let keys = this.initialKeys; - const initial = super.getInitialValue(data); - if (!keys || !foundry.utils.isEmpty(initial)) return initial; - if (!(keys instanceof Array)) keys = Object.keys(keys); - for (const key of keys) initial[key] = this._getInitialValueForKey(key); - return initial; - } - - /* -------------------------------------------- */ - - /** - * Get the initial value for the provided key. - * @param {string} key Key within the object being built. - * @param {object} [object] Any existing mapping data. - * @returns {*} Initial value based on provided field type. - */ - _getInitialValueForKey(key, object) { - const initial = this.model.getInitialValue(); - return this.initialValue?.(key, initial, object) ?? initial; - } - - /* -------------------------------------------- */ - - /** @override */ - _validateType(value, options = {}) { - if (foundry.utils.getType(value) !== 'Object') throw new Error('must be an Object'); - const errors = this._validateValues(value, options); - if (!foundry.utils.isEmpty(errors)) { - const failure = new foundry.data.validation.DataModelValidationFailure(); - failure.elements = Object.entries(errors).map(([id, failure]) => ({ id, failure })); - throw failure.asError(); - } - } - - /* -------------------------------------------- */ - - /** - * Validate each value of the object. - * @param {object} value The object to validate. - * @param {object} options Validation options. - * @returns {Record} An object of value-specific errors by key. - */ - _validateValues(value, options) { - const errors = {}; - for (const [k, v] of Object.entries(value)) { - if (k.startsWith('-=')) continue; - const error = this.model.validate(v, options); - if (error) errors[k] = error; - } - return errors; - } - - /* -------------------------------------------- */ - - /** @override */ - initialize(value, model, options = {}) { - if (!value) return value; - const obj = {}; - const initialKeys = this.initialKeys instanceof Array ? this.initialKeys : Object.keys(this.initialKeys ?? {}); - const keys = this.initialKeysOnly ? initialKeys : Object.keys(value); - for (const key of keys) { - const data = value[key] ?? this._getInitialValueForKey(key, value); - obj[key] = this.model.initialize(data, model, options); - } - return obj; - } - - /* -------------------------------------------- */ - - /** @inheritDoc */ - _getField(path) { - if (path.length === 0) return this; - else if (path.length === 1) return this.model; - path.shift(); - return this.model._getField(path); - } -} From 57e51ee8411d390f181526ac71ef9c01352acf20 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 1 Feb 2026 00:25:40 +0100 Subject: [PATCH 009/304] Added in comments about Beastform failing --- .../sheets-configs/token-config-mixin.mjs | 2 +- module/documents/token.mjs | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/module/applications/sheets-configs/token-config-mixin.mjs b/module/applications/sheets-configs/token-config-mixin.mjs index a23aa7f4..5242d797 100644 --- a/module/applications/sheets-configs/token-config-mixin.mjs +++ b/module/applications/sheets-configs/token-config-mixin.mjs @@ -69,7 +69,7 @@ export default function DHTokenConfigMixin(Base) { const deletions = { actorId: _del, actorLink: _del }; const mergeOptions = { inplace: false, performDeletions: true }; - this._preview.updateSource(mergeObject(changes, deletions, mergeOptions)); + this._preview.updateSource(foundry.utils.mergeObject(changes, deletions, mergeOptions)); if (this._preview?.object?.destroyed === false) { this._preview.object.initializeSources(); diff --git a/module/documents/token.mjs b/module/documents/token.mjs index b9507c2f..b70d7834 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -542,4 +542,62 @@ export default class DHToken extends CONFIG.Token.documentClass { game.system.registeredTriggers.unregisterItemTriggers(this.actor.items); } } + + /* V14 TEMP until foundry fixes: https://discord.com/channels/170995199584108546/1421197211194228907/1467296028700049566 */ + _onRelatedUpdate(update = {}, operation = {}) { + this.#refreshOverrides(operation); + this._prepareBars(); + + // Update tracked Combat resource + const combatant = this.combatant; + if (combatant) { + const isActorUpdate = [this, null, undefined].includes(operation.parent); + const resource = game.combat.settings.resource; + const updates = Array.isArray(update) ? update : [update]; + if (isActorUpdate && resource && updates.some(u => foundry.utils.hasProperty(u.system ?? {}, resource))) { + combatant.updateResource(); + } + ui.combat.render(); + } + + // Trigger redraws on the token + if (this.parent.isView) { + if (this.object?.hasActiveHUD) canvas.tokens.hud.render(); + this.object?.renderFlags.set({ redrawEffects: true }); + for (const key of ['bar1', 'bar2']) { + const name = `${this.object?.objectId}.animate${key.capitalize()}`; + const easing = foundry.canvas.animation.CanvasAnimation.easeInOutCosine; + this.object?.animate({ [key]: this[key] }, { name, easing }); + } + for (const app of foundry.applications.sheets.TokenConfig.instances()) { + app._preview?.updateSource({ delta: this.toObject().delta }, { diff: false, recursive: false }); + app._preview?.object?.renderFlags.set({ refreshBars: true, redrawEffects: true }); + } + } + } + + /* V14 TEMP until foundry fixes: https://discord.com/channels/170995199584108546/1421197211194228907/1467296028700049566 */ + #refreshOverrides(operation) { + if (!this.actor) return; + + const { deepClone, mergeObject, equals, isEmpty } = foundry.utils; + const oldOverrides = deepClone(this._overrides) ?? {}; + const newOverrides = deepClone(this.actor?.tokenOverrides ?? {}, { prune: true }); + if (!equals(oldOverrides, newOverrides)) { + this._overrides = newOverrides; + this.reset(); + + // Send emulated update data to the PlaceableObject + if (!canvas.ready || canvas.scene !== this.scene) return; + const { width, height, depth, ...changes } = mergeObject( + mergeObject(oldOverrides, this, { insertKeys: false, insertValues: false }), + this._overrides + ); + this.object?._onUpdate(changes, {}, game.user.id); + + // Hand off size changes to a secondary handler requiring downstream implementation. + const sizeChanges = deepClone({ width, height, depth }, { prune: true }); + if (!isEmpty(sizeChanges)) this._onOverrideSize(sizeChanges, operation); + } + } } From 578b090f088d90dd01428f613cc64b2cef2e1734 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:21:56 +0100 Subject: [PATCH 010/304] [V14] 1605 - Template Migration (#1621) * Fixed so that our templates make use of SceneRegions instead * Fixed visibility --- daggerheart.mjs | 4 +- module/canvas/placeables/_module.mjs | 2 +- module/canvas/placeables/regionLayer.mjs | 48 +++++++++++ module/documents/_module.mjs | 1 - module/documents/templateManager.mjs | 105 ----------------------- module/enrichers/TemplateEnricher.mjs | 25 ++++-- 6 files changed, 70 insertions(+), 115 deletions(-) create mode 100644 module/canvas/placeables/regionLayer.mjs delete mode 100644 module/documents/templateManager.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index f75ff1da..8c817327 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -20,7 +20,6 @@ import { } from './module/systemRegistration/_module.mjs'; import { placeables, DhTokenLayer } from './module/canvas/_module.mjs'; import './node_modules/@yaireo/tagify/dist/tagify.css'; -import TemplateManager from './module/documents/templateManager.mjs'; import TokenManager from './module/documents/tokenManager.mjs'; CONFIG.DH = SYSTEM; @@ -55,7 +54,7 @@ CONFIG.ChatMessage.documentClass = documents.DhChatMessage; CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs'; CONFIG.Canvas.rulerClass = placeables.DhRuler; -CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer; +CONFIG.Canvas.layers.regions.layerClass = placeables.DhRegionLayer; CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer; CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate; @@ -83,7 +82,6 @@ CONFIG.ui.resources = applications.ui.DhFearTracker; CONFIG.ui.countdowns = applications.ui.DhCountdowns; CONFIG.ux.ContextMenu = applications.ux.DHContextMenu; CONFIG.ux.TooltipManager = documents.DhTooltipManager; -CONFIG.ux.TemplateManager = new TemplateManager(); CONFIG.ux.TokenManager = new TokenManager(); CONFIG.debug.triggers = false; diff --git a/module/canvas/placeables/_module.mjs b/module/canvas/placeables/_module.mjs index 78242839..6ba047fb 100644 --- a/module/canvas/placeables/_module.mjs +++ b/module/canvas/placeables/_module.mjs @@ -1,5 +1,5 @@ export { default as DhMeasuredTemplate } from './measuredTemplate.mjs'; export { default as DhRuler } from './ruler.mjs'; -export { default as DhTemplateLayer } from './templateLayer.mjs'; +export { default as DhRegionLayer } from './regionLayer.mjs'; export { default as DhTokenPlaceable } from './token.mjs'; export { default as DhTokenRuler } from './tokenRuler.mjs'; diff --git a/module/canvas/placeables/regionLayer.mjs b/module/canvas/placeables/regionLayer.mjs new file mode 100644 index 00000000..d50845b7 --- /dev/null +++ b/module/canvas/placeables/regionLayer.mjs @@ -0,0 +1,48 @@ +export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer { + static prepareSceneControls() { + const sc = foundry.applications.ui.SceneControls; + const { tools, ...rest } = super.prepareSceneControls(); + + return { + ...rest, + tools: { + select: tools.select, + templateMode: tools.templateMode, + rectangle: tools.rectangle, + circle: tools.circle, + ellipse: tools.ellipse, + cone: tools.cone, + inFront: { + name: 'inFront', + order: 7, + title: 'CONTROLS.inFront', + icon: 'fa-solid fa-eye', + toolclip: { + src: 'toolclips/tools/measure-cone.webm', + heading: 'CONTROLS.inFront', + items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate']) + } + }, + ring: { ...tools.ring, order: 8 }, + line: { ...tools.line, order: 9 }, + emanation: { ...tools.emanation, order: 10 }, + polygon: { ...tools.polygon, order: 11 }, + hole: { ...tools.hole, order: 12 }, + snap: { ...tools.snap, order: 13 }, + clear: { ...tools.clear, order: 14 } + } + }; + } + + /** @inheritDoc */ + _isCreationToolActive() { + return this.active && (game.activeTool === 'inFront' || game.activeTool in foundry.data.BaseShapeData.TYPES); + } + + _createDragShapeData(event) { + const hole = ui.controls.controls[this.options.name].tools.hole?.active ?? false; + if (game.activeTool === 'inFront') return { type: 'cone', x: 0, y: 0, radius: 0, angle: 180, hole }; + + return super._createDragShapeData(event); + } +} diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs index b9cfd3f2..aa08f0f4 100644 --- a/module/documents/_module.mjs +++ b/module/documents/_module.mjs @@ -8,5 +8,4 @@ export { default as DhRollTable } from './rollTable.mjs'; export { default as DhScene } from './scene.mjs'; export { default as DhToken } from './token.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs'; -export { default as DhTemplateManager } from './templateManager.mjs'; export { default as DhTokenManager } from './tokenManager.mjs'; diff --git a/module/documents/templateManager.mjs b/module/documents/templateManager.mjs deleted file mode 100644 index cf15c2e3..00000000 --- a/module/documents/templateManager.mjs +++ /dev/null @@ -1,105 +0,0 @@ -/** - * A singleton class that handles preview templates. - */ - -export default class DhTemplateManager { - #activePreview; - - /** - * Create a template preview, deactivating any existing ones. - * @param {object} data - */ - async createPreview(data) { - const template = await canvas.templates._createPreview(data, { renderSheet: false }); - - this.#activePreview = { - document: template.document, - object: template, - origin: { x: template.document.x, y: template.document.y } - }; - - this.#activePreview.events = { - contextmenu: this.#cancelTemplate.bind(this), - mousedown: this.#confirmTemplate.bind(this), - mousemove: this.#onDragMouseMove.bind(this), - wheel: this.#onMouseWheel.bind(this) - }; - canvas.stage.on('mousemove', this.#activePreview.events.mousemove); - canvas.stage.on('mousedown', this.#activePreview.events.mousedown); - - canvas.app.view.addEventListener('wheel', this.#activePreview.events.wheel, true); - canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu); - } - - /** - * Handles the movement of the temlate preview on mousedrag. - * @param {mousemove Event} event - */ - #onDragMouseMove(event) { - event.stopPropagation(); - const { moveTime, object } = this.#activePreview; - const update = {}; - - const now = Date.now(); - if (now - (moveTime || 0) <= 16) return; - this.#activePreview.moveTime = now; - - let cursor = event.getLocalPosition(canvas.templates); - - Object.assign(update, canvas.grid.getCenterPoint(cursor)); - - object.document.updateSource(update); - object.renderFlags.set({ refresh: true }); - } - - /** - * Handles the rotation of the preview template on scrolling. - * @param {wheel Event} event - */ - #onMouseWheel(event) { - if (!this.#activePreview) { - return; - } - if (!event.shiftKey && !event.ctrlKey) return; - event.stopPropagation(); - event.preventDefault(); - const { moveTime, object } = this.#activePreview; - - const now = Date.now(); - if (now - (moveTime || 0) <= 16) return; - this.#activePreview.moveTime = now; - - const multiplier = event.shiftKey ? 0.2 : 0.1; - - object.document.updateSource({ - direction: object.document.direction + event.deltaY * multiplier - }); - object.renderFlags.set({ refresh: true }); - } - - /** - * Cancels the preview template on right-click. - * @param {contextmenu Event} event - */ - #cancelTemplate(event) { - const { mousemove, mousedown, contextmenu, wheel } = this.#activePreview.events; - canvas.templates._onDragLeftCancel(event); - - canvas.stage.off('mousemove', mousemove); - canvas.stage.off('mousedown', mousedown); - canvas.app.view.removeEventListener('contextmenu', contextmenu); - canvas.app.view.removeEventListener('wheel', wheel); - } - - /** - * Creates a real MeasuredTemplate at the preview location and cancels the preview. - * @param {click Event} event - */ - #confirmTemplate(event) { - event.stopPropagation(); - this.#cancelTemplate(event); - - canvas.scene.createEmbeddedDocuments('MeasuredTemplate', [this.#activePreview.document.toObject()]); - this.#activePreview = undefined; - } -} diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index 4b9b052e..0683a4bb 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -58,7 +58,7 @@ export const renderMeasuredTemplate = async event => { const usedType = type === 'inFront' ? 'cone' : type === 'emanation' ? 'circle' : type; const usedAngle = - type === CONST.MEASURED_TEMPLATE_TYPES.CONE + type === CONFIG.DH.GENERAL.templateTypes.CONE ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) : type === CONFIG.DH.GENERAL.templateTypes.INFRONT ? '180' @@ -71,17 +71,32 @@ export const renderMeasuredTemplate = async event => { ]; } const distance = type === CONFIG.DH.GENERAL.templateTypes.EMANATION ? baseDistance + 2.5 : baseDistance; + const radius = (distance / game.scenes.active.grid.distance) * game.scenes.active.grid.size; const { width, height } = game.canvas.scene.dimensions; - const data = { + const shapeData = { x: width / 2, y: height / 2, t: usedType, distance: distance, - width: type === CONST.MEASURED_TEMPLATE_TYPES.RAY ? 5 : undefined, + width: type === CONFIG.DH.GENERAL.templateTypes.RAY ? 5 : undefined, angle: usedAngle, - direction: direction + radius: radius, + direction: direction, + type: usedType }; - CONFIG.ux.TemplateManager.createPreview(data); + await canvas.regions.placeRegion( + { + name: usedType.capitalize(), + shapes: [shapeData], + restriction: { enabled: false, type: 'move', priority: 0 }, + behaviors: [], + displayMeasurements: true, + locked: false, + ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE }, + visibility: CONST.REGION_VISIBILITY.ALWAYS + }, + { create: true } + ); }; From c17020c2c81ae8de13a13bf76ec1327ae6ae7c4d Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 2 Feb 2026 20:00:15 +0100 Subject: [PATCH 011/304] Fixed ActiveEffect.getChangeValue not expecting a number as change.value --- module/documents/activeEffect.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index c34ec62f..c36ed97e 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -116,7 +116,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { } static getChangeValue(model, change, effect) { - let key = change.value; + let key = change.value.toString(); const isOriginTarget = key.toLowerCase().includes('origin.@'); let parseModel = model; if (isOriginTarget && effect.origin) { From 6a0a8d8d6eba248be7c60815852f2c7416042eb8 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 2 Feb 2026 23:56:57 +0100 Subject: [PATCH 012/304] Updated the sidebar so the new Placeables tab coems in --- module/applications/sidebar/sidebar.mjs | 61 ++++++------------------- 1 file changed, 14 insertions(+), 47 deletions(-) diff --git a/module/applications/sidebar/sidebar.mjs b/module/applications/sidebar/sidebar.mjs index ab6b0cdb..64f7473d 100644 --- a/module/applications/sidebar/sidebar.mjs +++ b/module/applications/sidebar/sidebar.mjs @@ -1,52 +1,19 @@ export default class DhSidebar extends foundry.applications.sidebar.Sidebar { + static buildTabs() { + const { settings, ...tabs } = super.TABS; + return { + ...tabs, + daggerheartMenu: { + tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title', + img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg', + gmOnly: true + }, + settings + }; + } + /** @override */ - static TABS = { - chat: { - documentName: 'ChatMessage' - }, - combat: { - documentName: 'Combat' - }, - scenes: { - documentName: 'Scene', - gmOnly: true - }, - actors: { - documentName: 'Actor' - }, - items: { - documentName: 'Item' - }, - journal: { - documentName: 'JournalEntry', - tooltip: 'SIDEBAR.TabJournal' - }, - tables: { - documentName: 'RollTable' - }, - cards: { - documentName: 'Cards' - }, - macros: { - documentName: 'Macro' - }, - playlists: { - documentName: 'Playlist' - }, - compendium: { - tooltip: 'SIDEBAR.TabCompendium', - icon: 'fa-solid fa-book-atlas' - }, - daggerheartMenu: { - tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title', - img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg', - gmOnly: true - }, - settings: { - tooltip: 'SIDEBAR.TabSettings', - icon: 'fa-solid fa-gears' - } - }; + static TABS = DhSidebar.buildTabs(); /** @override */ static PARTS = { From 115a31423e4b358619c47027ca845ada550c7381 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 4 Feb 2026 10:15:42 +0100 Subject: [PATCH 013/304] Corrected ActiveEffect template styling and added useage of the new 'showIcon' property --- .../sheets-configs/activeEffectConfig.mjs | 15 +++++++++------ module/applications/ui/effectsDisplay.mjs | 3 ++- module/canvas/placeables/token.mjs | 3 ++- module/helpers/utils.mjs | 9 +++++++++ .../less/sheets/activeEffects/activeEffects.less | 11 +++++++++++ styles/less/sheets/index.less | 2 ++ templates/sheets/activeEffect/change.hbs | 4 ++-- templates/sheets/activeEffect/changes.hbs | 2 +- templates/sheets/activeEffect/details.hbs | 7 ++++++- 9 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 styles/less/sheets/activeEffects/activeEffects.less diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index aa6d9d54..d6dcd499 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -151,12 +151,15 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac index, defaultPriority ) ?? - renderTemplate('systems/daggerheart/templates/sheets/activeEffect/change.hbs', { - change, - index, - defaultPriority, - fields - }) + foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/sheets/activeEffect/change.hbs', + { + change, + index, + defaultPriority, + fields + } + ) ); } } diff --git a/module/applications/ui/effectsDisplay.mjs b/module/applications/ui/effectsDisplay.mjs index 8c0c939c..3bc5e716 100644 --- a/module/applications/ui/effectsDisplay.mjs +++ b/module/applications/ui/effectsDisplay.mjs @@ -1,3 +1,4 @@ +import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs'; import { RefreshType } from '../../systemRegistration/socket.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -72,7 +73,7 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica ? game.user.character : null : canvas.tokens.controlled[0].actor; - return actor?.getActiveEffects() ?? []; + return getIconVisibleActiveEffects(actor?.getActiveEffects() ?? []); }; toggleHidden(token, focused) { diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 7bd92226..05140d76 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -1,3 +1,4 @@ +import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs'; import DhMeasuredTemplate from './measuredTemplate.mjs'; export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { @@ -20,7 +21,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { this.effects.overlay = null; // Categorize effects - const activeEffects = this.actor?.getActiveEffects() ?? []; + const activeEffects = getIconVisibleActiveEffects(Array.from(this.actor?.allApplicableEffects() ?? [])); const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay')); // Draw effects diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 7ca2c41c..980349ba 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -500,3 +500,12 @@ export function htmlToText(html) { return tempDivElement.textContent || tempDivElement.innerText || ''; } + +export function getIconVisibleActiveEffects(effects) { + return effects.filter(effect => { + const alwaysShown = effect.showIcon === CONST.ACTIVE_EFFECT_SHOW_ICON.ALWAYS; + const conditionalShown = effect.showIcon === CONST.ACTIVE_EFFECT_SHOW_ICON.CONDITIONAL && !effect.transfer; // TODO: system specific logic + + return !effect.disabled && (alwaysShown || conditionalShown); + }); +} diff --git a/styles/less/sheets/activeEffects/activeEffects.less b/styles/less/sheets/activeEffects/activeEffects.less new file mode 100644 index 00000000..f1879463 --- /dev/null +++ b/styles/less/sheets/activeEffects/activeEffects.less @@ -0,0 +1,11 @@ +.application.sheet.daggerheart.dh-style.active-effect-config { + .tab.changes { + gap: 0; + + header { + div { + text-align: center; + } + } + } +} diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index 1bdb451a..e5ffbf3e 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -42,3 +42,5 @@ @import './rollTables/sheet.less'; @import './actions/actions.less'; + +@import './activeEffects/activeEffects.less'; diff --git a/templates/sheets/activeEffect/change.hbs b/templates/sheets/activeEffect/change.hbs index 60cb0d85..30f643b8 100644 --- a/templates/sheets/activeEffect/change.hbs +++ b/templates/sheets/activeEffect/change.hbs @@ -1,6 +1,6 @@
  • - {{formInput fields.key name=change.keyPath value=change.key}} +
    {{formInput fields.type name=change.typePath value=change.type localize=true}} @@ -12,6 +12,6 @@ {{formInput fields.priority name=change.priorityPath value=change.priority placeholder=defaultPriority}}
    - +
  • diff --git a/templates/sheets/activeEffect/changes.hbs b/templates/sheets/activeEffect/changes.hbs index 8604daf6..026ffd90 100644 --- a/templates/sheets/activeEffect/changes.hbs +++ b/templates/sheets/activeEffect/changes.hbs @@ -5,7 +5,7 @@
    {{localize "EFFECT.FIELDS.changes.element.value.label"}}
    {{localize "EFFECT.FIELDS.changes.element.priority.label"}}
    - +
      diff --git a/templates/sheets/activeEffect/details.hbs b/templates/sheets/activeEffect/details.hbs index 72e77d5e..f7e465bd 100644 --- a/templates/sheets/activeEffect/details.hbs +++ b/templates/sheets/activeEffect/details.hbs @@ -4,7 +4,12 @@ {{formGroup fields.disabled value=source.disabled rootId=rootId}} {{#if isActorEffect}} - {{formGroup fields.origin value=source.origin rootId=rootId disabled=true}} +
      + +
      + +
      +
      {{/if}} {{#if isItemEffect}} From 593105b163afeb1fdce7bf0e2d9212284950dc24 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 4 Feb 2026 20:08:52 +0100 Subject: [PATCH 014/304] Fixed ActiveEffect create dialog options --- lang/en.json | 4 +++- module/documents/activeEffect.mjs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/lang/en.json b/lang/en.json index 0186ae3e..c4b611d0 100755 --- a/lang/en.json +++ b/lang/en.json @@ -14,7 +14,9 @@ "beastform": "Beastform" }, "ActiveEffect": { - "beastform": "Beastform" + "base": "Standard", + "beastform": "Beastform", + "horde": "Horde" }, "Actor": { "character": "Character", diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index c36ed97e..9a326282 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -54,6 +54,37 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /* Event Handlers */ /* -------------------------------------------- */ + /** @inheritdoc */ + static async createDialog(data = {}, createOptions = {}, options = {}) { + const { folders, types, template, context = {}, ...dialogOptions } = options; + + if (types?.length === 0) { + throw new Error('The array of sub-types to restrict to must not be empty.'); + } + + const creatableEffects = ['base']; + const documentTypes = this.TYPES.filter(type => creatableEffects.includes(type)).map(type => { + const labelKey = `TYPES.ActiveEffect.${type}`; + const label = game.i18n.has(labelKey) ? game.i18n.localize(labelKey) : type; + + return { value: type, label }; + }); + + if (!documentTypes.length) { + throw new Error('No document types were permitted to be created.'); + } + + const sortedTypes = documentTypes.sort((a, b) => a.label.localeCompare(b.label, game.i18n.lang)); + + return await super.createDialog(data, createOptions, { + folders, + types, + template, + context: { types: sortedTypes, ...context }, + ...dialogOptions + }); + } + /**@inheritdoc*/ async _preCreate(data, options, user) { const update = {}; From 4aab5d315a47e110861109420b5a9b30423f0790 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 17 Feb 2026 18:57:03 +0100 Subject: [PATCH 015/304] [V14] 1604 - ActiveEffect Durations (#1634) * Added daggerheart durations and auto expiration of them * Added duration to all tier1 adversaries * Finished all adversaries and environments * Remaining compendiums updated * Improved styling of duration in tooltips * . --- lang/en.json | 13 +++ module/applications/dialogs/downtime.mjs | 4 +- .../sheets-configs/activeEffectConfig.mjs | 33 ++++++ .../sidebar/tabs/daggerheartMenu.mjs | 4 +- module/applications/ui/combatTracker.mjs | 3 + module/config/generalConfig.mjs | 31 ++++++ module/data/activeEffect/baseEffect.mjs | 8 ++ module/data/settings/Automation.mjs | 5 + module/documents/activeEffect.mjs | 14 +++ module/helpers/utils.mjs | 32 ++++++ ...ersary_Acid_Burrower_89yAh30vaNQOALlz.json | 26 +++-- ...ary_Arch_Necromancer_WPEOIGfclNJxWb87.json | 27 +++-- ...ry_Assassin_Poisoner_h5RuhzGL17dW5FBT.json | 29 +++-- ...adversary_Battle_Box_dgH3fW9FTYLaIDvS.json | 58 ++++++---- .../adversary_Bear_71qKDLKO3CsrNkdy.json | 27 +++-- ...versary_Bladed_Guard_B4LZcGuBAHzyVdzy.json | 27 +++-- ...ersary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json | 26 +++-- .../adversary_Courtesan_ZxWaWPdzFIUPNC62.json | 27 +++-- .../adversary_Courtier_CBBuEXAlLKFMJdjg.json | 26 +++-- ...adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json | 80 +++++++++----- .../adversary_Cult_Fang_tyBOpLfigAhI9bU3.json | 26 +++-- ...ry_Deeproot_Defender_9x2xY9zwc3xzbXo5.json | 27 +++-- ...ary_Demon_of_Despair_kE4dfhqmIQpNd44e.json | 40 ++++--- ...rsary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json | 40 ++++--- .../adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json | 27 +++-- ...sary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json | 58 ++++++---- ...ry_Giant_Beastmaster_8VZIgU12cB3cvlyH.json | 27 +++-- ...dversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json | 26 +++-- ...rsary_Giant_Scorpion_fmfntuJ8mHRCAktP.json | 26 +++-- .../adversary_Gorgon_8mJYMpbLTb8qIOrr.json | 40 ++++--- ...ater_Earth_Elemental_dsfB3YhoL5SudvS2.json | 27 +++-- ...ater_Water_Elemental_xIICT6tEdnA7dKDV.json | 31 ++++-- ...adversary_Green_Ooze_SHXedd9zZPVfUgUa.json | 27 +++-- ...dversary_High_Seraph_r1mbfSSwKWdcFdAU.json | 29 +++-- ...sary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json | 27 +++-- .../adversary_Hydra_MI126iMOOobQ1Obn.json | 41 ++++--- ...y_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json | 26 +++-- ...ed_Knife_Kneebreaker_CBKixLH3yhivZZuL.json | 41 ++++--- .../adversary_Kraken_4nqv3ZwJGjnmic8j.json | 31 ++++-- ...versary_Masked_Thief_niBpVU7yeo5ccskE.json | 31 ++++-- ...inor_Chaos_Elemental_sRn4bqerfARvhgSV.json | 26 +++-- ...ersary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json | 26 +++-- ...adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json | 41 ++++--- ...r_Realms_Abomination_A0SeeDzwjvqOsyof.json | 28 +++-- ...ary_Perfected_Zombie_CP6iRfHdyFWniTHY.json | 27 +++-- .../adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json | 27 +++-- .../adversary_Siren_BK4jwyXSRx7IOQiO.json | 29 +++-- ...dversary_Stonewraith_3aAS2Qm3R6cgaYfE.json | 26 +++-- ...sary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json | 10 +- ...ault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json | 29 +++-- ...lt_Guardian_Sentinel_FVgYb28fhxlVcGwA.json | 10 +- ...Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json | 27 +++-- ...agon__Molten_Scourge_eArAPuB38CNR0ZIM.json | 27 +++-- ...versary_Weaponmaster_ZNbQ2jg35LG4t9eH.json | 27 +++-- ...dversary_Young_Dryad_8yUj2Mzvnifhxegm.json | 29 +++-- ...ary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json | 68 ++++++++---- ...ure_Retracting_Claws_Zj69cAeb3NjIa8Hn.json | 26 +++-- .../feature_Demolish_DfBXO8jTchwFG8dZ.json | 29 +++-- ...ture_Hobbling_Strike_8u0HkK3WgtU9lWYs.json | 29 +++-- ...feature_Ocean_Master_tGDdEH40wyOCsFmH.json | 29 +++-- ...ture_Snapping_Strike_Ky3rZD3sJMXYZOBC.json | 29 +++-- .../feature_Trample_A0lgd6eVEfX6oqSB.json | 29 +++-- ...eature_Venomous_Bite_2KlTnfzO03vneVS8.json | 29 +++-- ...ture_Venomous_Strike_uW3853pViM9VAfHb.json | 29 +++-- ...feature_Vicious_Maul_jYUBi7yLHap5ljpa.json | 29 +++-- .../feature_Webslinger_D73fS1iM4SZPFimu.json | 29 +++-- ...feature_Make_a_Scene_N9E5skDDK2VgvohR.json | 44 +++++--- .../feature_No_Mercy_njj2C3tMDeCHHOoh.json | 40 ++++--- ...eature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json | 41 ++++--- ...mainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json | 28 +++-- ...mainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json | 40 ++++--- ...nCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json | 53 ++++++--- ...inCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json | 26 +++-- ...nCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json | 40 ++++--- ...ainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json | 26 +++-- ...nCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json | 27 +++-- ...omainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json | 28 +++-- ...domainCard_Enrapture_a8lFiKX1o8T924ze.json | 26 +++-- ...inCard_Forceful_Push_z8FFPhDh2SdFkFfS.json | 26 +++-- .../domainCard_Frenzy_MMl7abdGRLl7TJLO.json | 77 +++++++------ ...omainCard_Full_Surge_SgvjJfMyubZowPxS.json | 102 ++++++++++-------- ...d_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json | 95 ++++++---------- ...inCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json | 26 +++-- .../domainCard_Hush_gwmYasmfgXZ7tFS6.json | 69 ++++-------- ...ard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json | 28 +++-- ...domainCard_Life_Ward_OszbCj0jTqq2ADx9.json | 26 +++-- ...nCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json | 28 +++-- ...ainCard_Night_Terror_zcldCuqOg3dphUVI.json | 28 +++-- ...rd_Overwhelming_Aura_iEBLySZD9z8CLdz7.json | 40 ++++--- .../domainCard_Redirect_faU0XkJCbar69PiN.json | 2 +- ...omainCard_Shadowbind_kguhWlidhxe2GbT0.json | 26 +++-- ...rd_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json | 28 +++-- .../domainCard_Tempest_X7YaZgFieBlqaPdZ.json | 26 +++-- ...rd_Through_Your_Eyes_7b0mzV5QMPjVPT4o.json | 28 +++-- ...d_Transcendent_Union_kVkoCLBXLAIifqpz.json | 26 +++-- ...ard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json | 52 ++++++--- ...ment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json | 27 +++-- ...g_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json | 33 ++++-- ...Bustling_Marketplace_HZKA7hkej7JJY503.json | 10 +- ...ronment_Raging_River_t4cdqTfzcqP3H1vJ.json | 27 +++-- ...sumable_Blinding_Orb_eAXHdzA5qNPldOpn.json | 32 ++++-- ...mable_Growing_Potion_fl2f3ees8RFMze9t.json | 53 +++++---- ..._Major_Stride_Potion_yK6eEDUrsPbZA8G0.json | 41 ++++--- ...umable_Morphing_Clay_f1NHVSIHJJCIOaBl.json | 28 +++-- ...consumable_Ogre_Musk_qr1bosjFcUfuwq4B.json | 28 +++-- ...ble_Shrinking_Potion_HGixKenQwhyRAYNk.json | 53 +++++---- ...ble_Vial_of_Moondrip_VqEX5YwK5oL3r1t6.json | 28 +++-- ...ture_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json | 22 +++- ...eature_Battle_Bonded_hWsKyed1vfILg0I8.json | 41 ++++--- ...e_Elemental_Dominion_EFUJHrkTuyv8uA9l.json | 26 +++-- ...ure_Elusive_Predator_Cjtc43V3IzAmfIFG.json | 41 ++++--- ...ure_Gifted_Performer_99U7YWNCxFZHCiT0.json | 28 +++-- ...eature_Transcendence_th6HZwEFnVBjUtqm.json | 42 +++++--- .../sheets/activeEffects/activeEffects.less | 24 +++++ styles/less/ux/tooltip/bordered-tooltip.less | 32 ++++++ styles/less/ux/tooltip/tooltip.less | 27 +++++ .../settings/automation-settings/general.hbs | 1 + templates/sheets/activeEffect/settings.hbs | 64 +++++------ templates/ui/tooltip/effect-display.hbs | 10 ++ templates/ui/tooltip/effect.hbs | 8 ++ 120 files changed, 2514 insertions(+), 1256 deletions(-) diff --git a/lang/en.json b/lang/en.json index 651a661e..24d6168a 100755 --- a/lang/en.json +++ b/lang/en.json @@ -684,6 +684,15 @@ } }, "CONFIG": { + "ActiveEffectDuration": { + "temporary": "Temporary", + "act": "Next Spotlight", + "scene": "Next Scene", + "shortRest": "Next Rest", + "longRest": "Next Long Rest", + "session": "Next Session", + "custom": "Custom" + }, "AdversaryTrait": { "relentless": { "name": "Relentless", @@ -2523,6 +2532,10 @@ "hint": "Automatically increase the GM's fear pool on a fear duality roll result." }, "FIELDS": { + "autoExpireActiveEffects": { + "label": "Auto Expire Active Effects", + "hint": "Active Effects with set durations will automatically be removed when their durations are up" + }, "damageReductionRulesDefault": { "label": "Damage Reduction Rules Default", "hint": "Wether using armor and reductions has rules on by default" diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 4c01c2a9..52cced3e 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -1,4 +1,4 @@ -import { refreshIsAllowed } from '../../helpers/utils.mjs'; +import { expireActiveEffects, refreshIsAllowed } from '../../helpers/utils.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -264,6 +264,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV await feature.update({ 'system.resource.value': resetValue }); } + expireActiveEffects(this.actor, [this.shortRest ? 'shortRest' : 'longRest']); + this.close(); } else { this.render(); diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 27a94736..75173f8d 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -166,6 +166,17 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac })); } break; + case 'settings': + const groups = { + time: _loc('EFFECT.DURATION.UNITS.GROUPS.time'), + combat: _loc('EFFECT.DURATION.UNITS.GROUPS.combat') + }; + partContext.durationUnits = CONST.ACTIVE_EFFECT_DURATION_UNITS.map(value => ({ + value, + label: _loc(`EFFECT.DURATION.UNITS.${value}`), + group: CONST.ACTIVE_EFFECT_TIME_DURATION_UNITS.includes(value) ? groups.time : groups.combat + })); + break; case 'changes': const fields = this.document.system.schema.fields.changes.element.fields; partContext.changes = await Promise.all( @@ -206,4 +217,26 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac ) ); } + + /** @inheritDoc */ + _onChangeForm(_formConfig, event) { + if (foundry.utils.isElementInstanceOf(event.target, 'select') && event.target.name === 'system.duration.type') { + const durationSection = this.element.querySelector('.custom-duration-section'); + if (event.target.value === 'custom') durationSection.classList.add('visible'); + else durationSection.classList.remove('visible'); + + const durationDescription = this.element.querySelector('.duration-description'); + if (event.target.value === 'temporary') durationDescription.classList.add('visible'); + else durationDescription.classList.remove('visible'); + } + } + + /** @inheritDoc */ + _processFormData(event, form, formData) { + const submitData = super._processFormData(event, form, formData); + if (submitData.start && !submitData.start.time) submitData.start.time = '0'; + else if (!submitData) submitData.start = null; + + return submitData; + } } diff --git a/module/applications/sidebar/tabs/daggerheartMenu.mjs b/module/applications/sidebar/tabs/daggerheartMenu.mjs index b29437bf..a16a1490 100644 --- a/module/applications/sidebar/tabs/daggerheartMenu.mjs +++ b/module/applications/sidebar/tabs/daggerheartMenu.mjs @@ -1,4 +1,4 @@ -import { refreshIsAllowed } from '../../../helpers/utils.mjs'; +import { expireActiveEffects, refreshIsAllowed } from '../../../helpers/utils.mjs'; const { HandlebarsApplicationMixin } = foundry.applications.api; const { AbstractSidebarTab } = foundry.applications.sidebar; @@ -58,6 +58,8 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract const refreshedActors = {}; for (let actor of game.actors) { if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) { + expireActiveEffects(actor, types); + const updates = {}; for (let item of actor.items) { if (item.system.metadata?.hasResource && refreshIsAllowed(types, item.system.resource?.recovery)) { diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index fc47f085..345c6fcd 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -1,4 +1,5 @@ import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs'; +import { expireActiveEffects } from '../../helpers/utils.mjs'; export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker { static DEFAULT_OPTIONS = { @@ -177,6 +178,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C if (autoPoints) { update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0); } + + if (combatant.actor) expireActiveEffects(combatant.actor, [CONFIG.DH.GENERAL.activeEffectDurations.act.id]); } await this.viewed.update({ diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 1d9f8126..165342b4 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -885,3 +885,34 @@ export const activeEffectModes = { label: 'EFFECT.CHANGES.TYPES.override' } }; + +export const activeEffectDurations = { + temporary: { + id: 'temporary', + label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.temporary' + }, + act: { + id: 'act', + label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.act' + }, + scene: { + id: 'scene', + label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.scene' + }, + shortRest: { + id: 'shortRest', + label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.shortRest' + }, + longRest: { + id: 'longRest', + label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.longRest' + }, + session: { + id: 'session', + label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.session' + }, + custom: { + id: 'custom', + label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.custom' + } +}; diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index b8f2c65f..98a961d7 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -33,6 +33,14 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { priority: new fields.NumberField() }) ), + duration: new fields.SchemaField({ + type: new fields.StringField({ + choices: CONFIG.DH.GENERAL.activeEffectDurations, + blank: true, + label: 'DAGGERHEART.GENERAL.type' + }), + description: new fields.HTMLField({ label: 'DAGGERHEART.GENERAL.description' }) + }), rangeDependence: new fields.SchemaField({ enabled: new fields.BooleanField({ required: true, diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index e9952b1c..edc4eb28 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -192,6 +192,11 @@ export default class DhAutomation extends foundry.abstract.DataModel { }) }) }), + autoExpireActiveEffects: new fields.BooleanField({ + required: true, + initial: true, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.autoExpireActiveEffects.label' + }), triggers: new fields.SchemaField({ enabled: new fields.BooleanField({ nullable: false, diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 9ebbb386..f8b19a3a 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -50,6 +50,20 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { }); } + /** + * Whether this Active Effect is eligible to be registered with the {@link ActiveEffectRegistry} + */ + get isExpiryTrackable() { + return ( + this.persisted && + !this.inCompendium && + this.modifiesActor && + this.start && + this.isTemporary && + !this.isExpired + ); + } + /* -------------------------------------------- */ /* Event Handlers */ /* -------------------------------------------- */ diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 2fc18a63..b1b74d9f 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -473,6 +473,8 @@ export async function waitForDiceSoNice(message) { } export function refreshIsAllowed(allowedTypes, typeToCheck) { + if (!allowedTypes) return true; + switch (typeToCheck) { case CONFIG.DH.GENERAL.refreshTypes.scene.id: case CONFIG.DH.GENERAL.refreshTypes.session.id: @@ -489,6 +491,34 @@ export function refreshIsAllowed(allowedTypes, typeToCheck) { } } +function expireActiveEffectIsAllowed(allowedTypes, typeToCheck) { + if (typeToCheck === CONFIG.DH.GENERAL.activeEffectDurations.act.id) return true; + + return refreshIsAllowed(allowedTypes, typeToCheck); +} + +export function expireActiveEffects(actor, allowedTypes = null) { + const shouldExpireEffects = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.Automation + ).autoExpireActiveEffects; + if (!shouldExpireEffects) return; + + const effectsToExpire = actor + .getActiveEffects() + .filter(effect => { + if (!effect.system?.duration.type) return false; + + const { temporary, custom } = CONFIG.DH.GENERAL.activeEffectDurations; + if ([temporary.id, custom.id].includes(effect.system.duration.type)) return false; + + return expireActiveEffectIsAllowed(allowedTypes, effect.system.duration.type); + }) + .map(x => x.id); + + actor.deleteEmbeddedDocuments('ActiveEffect', effectsToExpire); +} + export async function getCritDamageBonus(formula) { const critRoll = new Roll(formula); return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0); @@ -503,6 +533,8 @@ export function htmlToText(html) { export function getIconVisibleActiveEffects(effects) { return effects.filter(effect => { + if (!(effect instanceof game.system.api.documents.DhActiveEffect)) return true; + const alwaysShown = effect.showIcon === CONST.ACTIVE_EFFECT_SHOW_ICON.ALWAYS; const conditionalShown = effect.showIcon === CONST.ACTIVE_EFFECT_SHOW_ICON.CONDITIONAL && !effect.transfer; // TODO: system specific logic diff --git a/src/packs/adversaries/adversary_Acid_Burrower_89yAh30vaNQOALlz.json b/src/packs/adversaries/adversary_Acid_Burrower_89yAh30vaNQOALlz.json index e2b3a444..70d0072b 100644 --- a/src/packs/adversaries/adversary_Acid_Burrower_89yAh30vaNQOALlz.json +++ b/src/packs/adversaries/adversary_Acid_Burrower_89yAh30vaNQOALlz.json @@ -400,18 +400,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "act" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -423,6 +423,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!89yAh30vaNQOALlz.ctXYwil2D1zfsekT.9PsnogEPsp1OOK64" } ], diff --git a/src/packs/adversaries/adversary_Arch_Necromancer_WPEOIGfclNJxWb87.json b/src/packs/adversaries/adversary_Arch_Necromancer_WPEOIGfclNJxWb87.json index d4e506cb..3b1f3535 100644 --- a/src/packs/adversaries/adversary_Arch_Necromancer_WPEOIGfclNJxWb87.json +++ b/src/packs/adversaries/adversary_Arch_Necromancer_WPEOIGfclNJxWb87.json @@ -488,18 +488,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you roll with Hope.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until you roll with Hope.

      ", "tint": "#ffffff", @@ -511,6 +512,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!WPEOIGfclNJxWb87.4EECsXzHFG0RoIg0.KGdf2eqcXkdigg0u" } ], diff --git a/src/packs/adversaries/adversary_Assassin_Poisoner_h5RuhzGL17dW5FBT.json b/src/packs/adversaries/adversary_Assassin_Poisoner_h5RuhzGL17dW5FBT.json index 8b553c83..324fc25e 100644 --- a/src/packs/adversaries/adversary_Assassin_Poisoner_h5RuhzGL17dW5FBT.json +++ b/src/packs/adversaries/adversary_Assassin_Poisoner_h5RuhzGL17dW5FBT.json @@ -277,20 +277,21 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you clear a HP.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Vulnerable until you clear a HP.

      ", + "description": "

      Vulnerable until you clear a HP.

      ", "tint": "#ffffff", "statuses": [ "vulnerable" @@ -300,6 +301,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!h5RuhzGL17dW5FBT.Fz2lnUEeBxsDpx0G.2iBVUGHtGW3I9VIj" } ], diff --git a/src/packs/adversaries/adversary_Battle_Box_dgH3fW9FTYLaIDvS.json b/src/packs/adversaries/adversary_Battle_Box_dgH3fW9FTYLaIDvS.json index 96a1b752..02553014 100644 --- a/src/packs/adversaries/adversary_Battle_Box_dgH3fW9FTYLaIDvS.json +++ b/src/packs/adversaries/adversary_Battle_Box_dgH3fW9FTYLaIDvS.json @@ -667,20 +667,21 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until their next roll with Hope.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Vulnerable until your next roll with Hope.

      ", + "description": "

      Vulnerable until your next roll with Hope.

      ", "tint": "#ffffff", "statuses": [ "vulnerable" @@ -690,6 +691,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!dgH3fW9FTYLaIDvS.XtnByqUr9AuYU9Ip.9NQcCXMhjyBReJRd" } ], @@ -883,20 +894,21 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until the cube is defeated.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Vulnerable until the cube is defeated.

      ", + "description": "

      Vulnerable until the cube is defeated.

      ", "tint": "#ffffff", "statuses": [ "vulnerable" @@ -906,6 +918,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!dgH3fW9FTYLaIDvS.ijIaKjroxq3xZd9Z.S7kJlhnV8Nexzi8l" } ], diff --git a/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json b/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json index da5de611..959ef9dd 100644 --- a/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json +++ b/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json @@ -368,18 +368,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you break free with a successful Strength Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Restrained until you break free with a successful Strength Roll.

      ", "tint": "#ffffff", @@ -391,6 +392,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!71qKDLKO3CsrNkdy.zgR0MEqyobKp2yXr.U50Ccm9emMqAxma6" } ], diff --git a/src/packs/adversaries/adversary_Bladed_Guard_B4LZcGuBAHzyVdzy.json b/src/packs/adversaries/adversary_Bladed_Guard_B4LZcGuBAHzyVdzy.json index 8ee7c56c..0a332575 100644 --- a/src/packs/adversaries/adversary_Bladed_Guard_B4LZcGuBAHzyVdzy.json +++ b/src/packs/adversaries/adversary_Bladed_Guard_B4LZcGuBAHzyVdzy.json @@ -376,18 +376,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you break free with a successful attack, Finesse Roll, or Strength Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Restrained until you break free with a successful attack, Finesse Roll, or Strength Roll.

      ", "tint": "#ffffff", @@ -399,6 +400,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!B4LZcGuBAHzyVdzy.9gizFt9ovKL05DXu.LmzztuktRkwOCy1a" } ], diff --git a/src/packs/adversaries/adversary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json b/src/packs/adversaries/adversary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json index c829c3f9..a169bf61 100644 --- a/src/packs/adversaries/adversary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json +++ b/src/packs/adversaries/adversary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json @@ -452,18 +452,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -475,6 +475,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!2UeZ0tEe7AzgSJNd.69reUZ5tv3splqyO.CjMrSdL6kgD8mKRQ" } ], diff --git a/src/packs/adversaries/adversary_Courtesan_ZxWaWPdzFIUPNC62.json b/src/packs/adversaries/adversary_Courtesan_ZxWaWPdzFIUPNC62.json index 668cd943..24572103 100644 --- a/src/packs/adversaries/adversary_Courtesan_ZxWaWPdzFIUPNC62.json +++ b/src/packs/adversaries/adversary_Courtesan_ZxWaWPdzFIUPNC62.json @@ -320,18 +320,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until the scene ends or they succeed on a social action against the Courtesan.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until the scene ends or they succeed on a social action against the Courtesan.

      ", "tint": "#ffffff", @@ -343,6 +344,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!ZxWaWPdzFIUPNC62.rSMUPC5GhR982ifg.blcRqns0PHqiuPac" } ], diff --git a/src/packs/adversaries/adversary_Courtier_CBBuEXAlLKFMJdjg.json b/src/packs/adversaries/adversary_Courtier_CBBuEXAlLKFMJdjg.json index 6721666f..1ffc7ece 100644 --- a/src/packs/adversaries/adversary_Courtier_CBBuEXAlLKFMJdjg.json +++ b/src/packs/adversaries/adversary_Courtier_CBBuEXAlLKFMJdjg.json @@ -336,18 +336,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "scene" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -359,6 +359,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!CBBuEXAlLKFMJdjg.LYNaKEYcYMgvF4Rf.YNMhgBZW8ndrCjIp" } ], diff --git a/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json b/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json index 14eb579b..e428d05d 100644 --- a/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json +++ b/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json @@ -422,31 +422,32 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.resistance.magical.resistance", + "value": 1, + "priority": null, + "type": "override" + }, + { + "key": "system.resistance.physical.resistance", + "value": 1, + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "temporary", + "description": "

      Until the Cult Adept marks their last HP.

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

      Resistance to all damage until the Adept marks their last HP

      ", "tint": "#ffffff", @@ -456,6 +457,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!0NxCSugvKQ4W8OYZ.IHWDn097sRgjlZXO.U9lWz1LgeAiK5L85" } ], @@ -533,18 +544,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you break free with a successful Strength or Instinct Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Restrained in smoky chains until you break free with a successful Strength or Instinct Roll. A target Restrained by this feature must spend a Hope to make an action roll.

      ", "tint": "#ffffff", @@ -556,6 +568,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!0NxCSugvKQ4W8OYZ.JpSrduK3vjd9h098.lNH6srSPyEprXZ4o" } ], diff --git a/src/packs/adversaries/adversary_Cult_Fang_tyBOpLfigAhI9bU3.json b/src/packs/adversaries/adversary_Cult_Fang_tyBOpLfigAhI9bU3.json index 57e7a7c7..d3b341f0 100644 --- a/src/packs/adversaries/adversary_Cult_Fang_tyBOpLfigAhI9bU3.json +++ b/src/packs/adversaries/adversary_Cult_Fang_tyBOpLfigAhI9bU3.json @@ -384,18 +384,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -407,6 +407,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!tyBOpLfigAhI9bU3.ohASSruBxcvuItIK.LwWxRz7FTMA80VdA" } ], diff --git a/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json b/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json index cd745eb6..f66ce7f0 100644 --- a/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json +++ b/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json @@ -407,18 +407,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until the Deeproot Defender takes Severe damage.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Restrained until the Defender takes Severe damage.

      ", "tint": "#ffffff", @@ -430,6 +431,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!9x2xY9zwc3xzbXo5.rreGFW5TbhUoZf2T.F3E7fiz01AbF2kr5" } ], diff --git a/src/packs/adversaries/adversary_Demon_of_Despair_kE4dfhqmIQpNd44e.json b/src/packs/adversaries/adversary_Demon_of_Despair_kE4dfhqmIQpNd44e.json index 830848c3..27c6bac2 100644 --- a/src/packs/adversaries/adversary_Demon_of_Despair_kE4dfhqmIQpNd44e.json +++ b/src/packs/adversaries/adversary_Demon_of_Despair_kE4dfhqmIQpNd44e.json @@ -354,25 +354,25 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.rules.dualityRoll.defaultHopeDice", + "value": "d8", + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "shortRest" } }, - "changes": [ - { - "key": "system.rules.dualityRoll.defaultHopeDice", - "mode": 5, - "value": "d8", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      All targets affected replace their Hope Die with a d8 until they roll a success with Hope or their next rest.

      ", "tint": "#ffffff", @@ -382,6 +382,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!kE4dfhqmIQpNd44e.FC8PIf4BVkhmoJX8.6WSx03mFbpbPWnOI" } ], diff --git a/src/packs/adversaries/adversary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json b/src/packs/adversaries/adversary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json index 2341ee8a..33ded6b9 100644 --- a/src/packs/adversaries/adversary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json +++ b/src/packs/adversaries/adversary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json @@ -317,25 +317,25 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.rules.dualityRoll.defaultFearDice", + "value": "d20", + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "scene" } }, - "changes": [ - { - "key": "system.rules.dualityRoll.defaultFearDice", - "mode": 5, - "value": "d20", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You use a d20 as your Fear Die until the end of the scene.

      ", "tint": "#ffffff", @@ -345,6 +345,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!5lphJAgzoqZI3VoG.a33PW8UkziliowlR.gFeHLGgeRoDdd3VG" } ], diff --git a/src/packs/adversaries/adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json b/src/packs/adversaries/adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json index e3ecda4e..69301cb2 100644 --- a/src/packs/adversaries/adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json +++ b/src/packs/adversaries/adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json @@ -435,18 +435,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until they clear at least 1 HP.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -458,6 +459,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!wNzeuQLfLUMvgHlQ.85XrqDvLP30YOO43.YNKHEFQ4ucGr4Rmc" } ], diff --git a/src/packs/adversaries/adversary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json b/src/packs/adversaries/adversary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json index fc064958..bfad5cf6 100644 --- a/src/packs/adversaries/adversary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json +++ b/src/packs/adversaries/adversary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json @@ -385,30 +385,41 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until youbreak free with a successful Instinct Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Restrained and Vulnerable until you break free, ending both conditions, with a successful Instinct Roll.

      ", "tint": "#ffffff", "statuses": [ - "restrained", - "vulnerable" + "vulnerable", + "restrained" ], "sort": 0, "flags": {}, "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!PELRry1vqjBzSAlr.ecp9o8t1dQFXGsse.Q99saHj6NOY2PSXf" } ], @@ -572,18 +583,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      A target can break free from their regret with a successful Presence or Strength Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until you break free from your regret with a successful Presence or Strength Roll. If you fail to break free, you lose a Hope.

      ", "tint": "#ffffff", @@ -595,6 +607,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!PELRry1vqjBzSAlr.gwSgBhkcekCGvXxz.pWDg0MADoohKu40O" } ], diff --git a/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json b/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json index 6d09a490..e576e1e0 100644 --- a/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json +++ b/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json @@ -352,18 +352,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you break free with a successful Finesse or Strength Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Restrained until you break free with a successful Finesse or Strength Roll.

      ", "tint": "#ffffff", @@ -375,6 +376,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!8VZIgU12cB3cvlyH.6ZrDjgnWufJohkp1.vb1sZCOLwDNLsr3j" } ], diff --git a/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json b/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json index b0ba4170..ea578762 100644 --- a/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json +++ b/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json @@ -430,18 +430,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "act" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until the next time you act.

      ", "tint": "#ffffff", @@ -453,6 +453,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!OMQ0v6PE8s1mSU0K.MabIQE1Kjn60j08J.m6qqQZxukDPVcGdQ" } ], diff --git a/src/packs/adversaries/adversary_Giant_Scorpion_fmfntuJ8mHRCAktP.json b/src/packs/adversaries/adversary_Giant_Scorpion_fmfntuJ8mHRCAktP.json index 99b5ed46..fda3e656 100644 --- a/src/packs/adversaries/adversary_Giant_Scorpion_fmfntuJ8mHRCAktP.json +++ b/src/packs/adversaries/adversary_Giant_Scorpion_fmfntuJ8mHRCAktP.json @@ -438,18 +438,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Poisoned until your next rest or until you succeed on a Knowledge Roll (16). While Poisoned, you must roll a d6 before you make an action roll. On a result of 4 or lower, you must mark a Stress.

      [[/dr trait=knowledge difficulty=16]]

      ", "tint": "#ffffff", @@ -459,6 +459,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!fmfntuJ8mHRCAktP.lANiDkxxth2sGacT.oILkLJlGNZl7KI1R" } ], diff --git a/src/packs/adversaries/adversary_Gorgon_8mJYMpbLTb8qIOrr.json b/src/packs/adversaries/adversary_Gorgon_8mJYMpbLTb8qIOrr.json index deeafa37..2753d958 100644 --- a/src/packs/adversaries/adversary_Gorgon_8mJYMpbLTb8qIOrr.json +++ b/src/packs/adversaries/adversary_Gorgon_8mJYMpbLTb8qIOrr.json @@ -335,25 +335,25 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.rules.conditionImmunities.hidden", + "value": 1, + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "scene" } }, - "changes": [ - { - "key": "system.rules.conditionImmunities.hidden", - "mode": 5, - "value": "1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You Glow until the end of the scene and can’t become Hidden. Attack rolls made against you have advantage.

      ", "tint": "#ffffff", @@ -363,6 +363,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!8mJYMpbLTb8qIOrr.NepVGKOo1lHYjA1F.bYBrgiSwHwYfQyjn" } ], diff --git a/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json b/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json index a20d80e6..fe968ff3 100644 --- a/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json +++ b/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json @@ -507,18 +507,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until your next roll with Hope.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until your next roll with Hope.

      ", "tint": "#ffffff", @@ -530,6 +531,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!dsfB3YhoL5SudvS2.q45DiEFlXqcXZ5hv.38MUzfbH64EMLVse" } ], diff --git a/src/packs/adversaries/adversary_Greater_Water_Elemental_xIICT6tEdnA7dKDV.json b/src/packs/adversaries/adversary_Greater_Water_Elemental_xIICT6tEdnA7dKDV.json index be037b10..5d17f025 100644 --- a/src/packs/adversaries/adversary_Greater_Water_Elemental_xIICT6tEdnA7dKDV.json +++ b/src/packs/adversaries/adversary_Greater_Water_Elemental_xIICT6tEdnA7dKDV.json @@ -459,30 +459,41 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      You can break free with a successful Strength or Instinct Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Restrained and Vulnerable as you are drowning. You can break free, ending both conditions, with a successful Strength or Instinct Roll.

      ", "tint": "#ffffff", "statuses": [ - "restrained", - "vulnerable" + "vulnerable", + "restrained" ], "sort": 0, "flags": {}, "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!xIICT6tEdnA7dKDV.bcwFQeuU6ZfIGjau.X8NF2OB23mSpDvlx" } ], diff --git a/src/packs/adversaries/adversary_Green_Ooze_SHXedd9zZPVfUgUa.json b/src/packs/adversaries/adversary_Green_Ooze_SHXedd9zZPVfUgUa.json index b03b5495..5f3f52c2 100644 --- a/src/packs/adversaries/adversary_Green_Ooze_SHXedd9zZPVfUgUa.json +++ b/src/packs/adversaries/adversary_Green_Ooze_SHXedd9zZPVfUgUa.json @@ -465,18 +465,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      If the Green Ooze takes Severe damage, the target is freed.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You must mark an additional Stress when you make an action roll. If the Ooze takes Severe damage, you are freed.

      ", "tint": "#ffffff", @@ -486,6 +487,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!SHXedd9zZPVfUgUa.Sm9Sk4mSvcq6PkmR.yk5kR5OVLCgDWfgY" } ], diff --git a/src/packs/adversaries/adversary_High_Seraph_r1mbfSSwKWdcFdAU.json b/src/packs/adversaries/adversary_High_Seraph_r1mbfSSwKWdcFdAU.json index 0a952540..60fe1917 100644 --- a/src/packs/adversaries/adversary_High_Seraph_r1mbfSSwKWdcFdAU.json +++ b/src/packs/adversaries/adversary_High_Seraph_r1mbfSSwKWdcFdAU.json @@ -391,20 +391,21 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until the High Seraph is defeated. The High Seraph can only mark one target at a time.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      While Guilty, you don’t gain Hope on a result with Hope.

      ", + "description": "

      While Guilty, you don’t gain Hope on a result with Hope.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -412,6 +413,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!r1mbfSSwKWdcFdAU.FilEB21L5q9XxKE1.O8G0oOf9f3qzNOAT" } ], diff --git a/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json b/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json index 3bb8ae96..db00c5e6 100644 --- a/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json +++ b/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json @@ -436,18 +436,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      When the Huge Green Ooze takes Severe damage, all Enveloped targets are freed and the condition is cleared.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      While Enveloped, you must mark an additional Stress every time you make an action roll. When the Ooze takes Severe damage, all Enveloped targets are freed and the condition is cleared.

      ", "tint": "#ffffff", @@ -457,6 +458,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!6hbqmxDXFOzZJDk4.pfXYuH7rtsyVjSXh.EwZ8owroJxFpg81e" } ], diff --git a/src/packs/adversaries/adversary_Hydra_MI126iMOOobQ1Obn.json b/src/packs/adversaries/adversary_Hydra_MI126iMOOobQ1Obn.json index 4c6fd61f..a12aaad8 100644 --- a/src/packs/adversaries/adversary_Hydra_MI126iMOOobQ1Obn.json +++ b/src/packs/adversaries/adversary_Hydra_MI126iMOOobQ1Obn.json @@ -507,25 +507,26 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.resistance.magical.immunity", + "value": 1, + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "temporary", + "description": "

      Until the next roll with Fear.

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

      While Dazed, they can’t use their Regeneration action but are immune to magic damage.

      ", "tint": "#ffffff", @@ -535,6 +536,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!MI126iMOOobQ1Obn.sJzjcRBgYRp5f53E.iBJ3YhEkVsGKEIVk" } ], diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json b/src/packs/adversaries/adversary_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json index 6ca9749c..e56f7af5 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json @@ -278,18 +278,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Whenever you roll with Hope, the hexer can mark a stress to make the roll be with Fear instead.

      ", "tint": "#ffffff", @@ -299,6 +299,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!MbBPIOxaxXYNApXz.Bl8L0RCGOgVUzuXo.ihy3kvEGSOEKdNfT" } ], diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Kneebreaker_CBKixLH3yhivZZuL.json b/src/packs/adversaries/adversary_Jagged_Knife_Kneebreaker_CBKixLH3yhivZZuL.json index c38260e9..d8115fd9 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Kneebreaker_CBKixLH3yhivZZuL.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Kneebreaker_CBKixLH3yhivZZuL.json @@ -334,25 +334,26 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.rules.attack.damage.hpDamageTakenMultiplier", + "value": 2, + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "temporary", + "description": "

      The target can break free, clearing both conditions, with a successful Strength Roll or is freed automatically if the Jagged Knife Kneebreaker takes Major or greater damage.

      " } }, - "changes": [ - { - "key": "system.rules.attack.damage.hpDamageTakenMultiplier", - "mode": 5, - "value": "2", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -365,6 +366,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!CBKixLH3yhivZZuL.Sa4Nt0eoDjirBKGf.d7sB1Qa1kJMnglqu" } ], diff --git a/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json b/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json index 3b84774e..6f6f6edc 100644 --- a/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json +++ b/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json @@ -385,30 +385,41 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you break free with a successful Strength Roll or the Kraken takes Major or greater damage.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Restrained and Vulnerable until you break free with a successful Strength Roll or the Kraken takes Major or greater damage. While Restrained and Vulnerable in this way, you must mark a Stress when you make an action roll.

      ", "tint": "#ffffff", "statuses": [ - "restrained", - "vulnerable" + "vulnerable", + "restrained" ], "sort": 0, "flags": {}, "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!4nqv3ZwJGjnmic8j.vz2BWhispgR7mSWF.Xes6ZIE01CCN67KF" } ], diff --git a/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json b/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json index 528df6a9..3143fbe3 100644 --- a/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json +++ b/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json @@ -400,30 +400,41 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you break free with a successful Finesse or Strength Roll (13).

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Restrained and Vulnerable until you break free, ending both conditions, with a successful Finesse or Strength Roll (13).

      [[/dr trait=finesse difficulty=13]]
      [[/dr trait=strength difficulty=13]]

      ", "tint": "#ffffff", "statuses": [ - "restrained", - "vulnerable" + "vulnerable", + "restrained" ], "sort": 0, "flags": {}, "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!niBpVU7yeo5ccskE.tP2DD751nOLxFVps.zMut1PRphlwE0xOa" } ], diff --git a/src/packs/adversaries/adversary_Minor_Chaos_Elemental_sRn4bqerfARvhgSV.json b/src/packs/adversaries/adversary_Minor_Chaos_Elemental_sRn4bqerfARvhgSV.json index b1732c71..57dc2980 100644 --- a/src/packs/adversaries/adversary_Minor_Chaos_Elemental_sRn4bqerfARvhgSV.json +++ b/src/packs/adversaries/adversary_Minor_Chaos_Elemental_sRn4bqerfARvhgSV.json @@ -380,18 +380,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until your next rest or you clear a HP.

      ", "tint": "#ffffff", @@ -403,6 +403,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!sRn4bqerfARvhgSV.oAxhAawgcK7DAdpa.KIyV2eXDmmymXY5y" } ], diff --git a/src/packs/adversaries/adversary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json b/src/packs/adversaries/adversary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json index 8bc7fe10..01218718 100644 --- a/src/packs/adversaries/adversary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json +++ b/src/packs/adversaries/adversary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json @@ -444,18 +444,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "scene" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Attacks made by the Hunter against a Deathlocked target deal direct damage.

      ", "tint": "#ffffff", @@ -465,6 +465,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!mVV7a7KQAORoPMgZ.r1T70u9n3bRfUTX5.YznseQP43jNrk07h" } ], diff --git a/src/packs/adversaries/adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json b/src/packs/adversaries/adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json index c0999e70..82321fb6 100644 --- a/src/packs/adversaries/adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json +++ b/src/packs/adversaries/adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json @@ -399,25 +399,26 @@ "type": "withinRange", "target": "any", "range": "self" + }, + "changes": [ + { + "key": "system.resistance.physical.resistance", + "value": 1, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "temporary", + "description": "

      Can end this effect instead of moving while they are spotlighted.

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

      Mark a Stress to Root the Treant in place. The Treant is Restrained while Rooted and can end this effect instead of moving while they are spotlighted. While Rooted the Treant has resistance to physical damage.

      ", "tint": "#ffffff", @@ -427,6 +428,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!XK78QUfY8c8Go8Uv.sqkgw26P2KiQVtXT.3PY5KIG6d3dr3dty" } ], diff --git a/src/packs/adversaries/adversary_Outer_Realms_Abomination_A0SeeDzwjvqOsyof.json b/src/packs/adversaries/adversary_Outer_Realms_Abomination_A0SeeDzwjvqOsyof.json index 5b565b8c..83edda8a 100644 --- a/src/packs/adversaries/adversary_Outer_Realms_Abomination_A0SeeDzwjvqOsyof.json +++ b/src/packs/adversaries/adversary_Outer_Realms_Abomination_A0SeeDzwjvqOsyof.json @@ -442,20 +442,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "scene" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Unstuck from reality until the end of the scene. When you spend Hope or mark Armor Slots, HP, or Stress, you must double the amount spent or marked.

      ", + "description": "

      Unstuck from reality until the end of the scene. When you spend Hope or mark Armor Slots, HP, or Stress, you must double the amount spent or marked.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -463,6 +463,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!A0SeeDzwjvqOsyof.K3MQO1I42nmfM2F2.edEZER9ImWicMwRb" } ], diff --git a/src/packs/adversaries/adversary_Perfected_Zombie_CP6iRfHdyFWniTHY.json b/src/packs/adversaries/adversary_Perfected_Zombie_CP6iRfHdyFWniTHY.json index e3da56b6..8174e9fd 100644 --- a/src/packs/adversaries/adversary_Perfected_Zombie_CP6iRfHdyFWniTHY.json +++ b/src/packs/adversaries/adversary_Perfected_Zombie_CP6iRfHdyFWniTHY.json @@ -433,18 +433,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest", + "description": "" } }, - "changes": [], "disabled": true, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until your next rest.

      ", "tint": "#ffffff", @@ -456,6 +457,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!CP6iRfHdyFWniTHY.CKy2r6FguyTSO9Fm.Q5eeh0B6qaXFS1Ck" } ], diff --git a/src/packs/adversaries/adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json b/src/packs/adversaries/adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json index 2c10ae3f..cd8a44b6 100644 --- a/src/packs/adversaries/adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json +++ b/src/packs/adversaries/adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json @@ -409,18 +409,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until they’re extinguished with a successful Finesse Roll (14)

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      You are Ignited until you are extinguished with a successful Finesse Roll (14). While Ignited, you take 1d4 magic damage whenever you make an action roll.

      [[/dr trait=finesse difficulty=14]]

      ", "tint": "#ffffff", @@ -430,6 +431,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!9rVlbJVrDNn1x7PS.JU9uVwZSM0ItnZRq.9UBLk9M87VIUziAQ" } ], diff --git a/src/packs/adversaries/adversary_Siren_BK4jwyXSRx7IOQiO.json b/src/packs/adversaries/adversary_Siren_BK4jwyXSRx7IOQiO.json index a72c6d46..05ca67a9 100644 --- a/src/packs/adversaries/adversary_Siren_BK4jwyXSRx7IOQiO.json +++ b/src/packs/adversaries/adversary_Siren_BK4jwyXSRx7IOQiO.json @@ -408,20 +408,21 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you mark 2 Stress.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      While Entranced, you can’t act and are Vulnerable.

      ", + "description": "

      While Entranced, you can’t act and are Vulnerable.

      ", "tint": "#ffffff", "statuses": [ "vulnerable" @@ -431,6 +432,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!BK4jwyXSRx7IOQiO.Ks3HpB4W1l5FqR7p.xrm5786ckKbMYHjn" } ], diff --git a/src/packs/adversaries/adversary_Stonewraith_3aAS2Qm3R6cgaYfE.json b/src/packs/adversaries/adversary_Stonewraith_3aAS2Qm3R6cgaYfE.json index de3ef9f2..d04f41fd 100644 --- a/src/packs/adversaries/adversary_Stonewraith_3aAS2Qm3R6cgaYfE.json +++ b/src/packs/adversaries/adversary_Stonewraith_3aAS2Qm3R6cgaYfE.json @@ -359,18 +359,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -382,6 +382,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!3aAS2Qm3R6cgaYfE.tQgxiSS48TJ3X1Dl.6UgMuuJ8ZygbCsDh" } ], diff --git a/src/packs/adversaries/adversary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json b/src/packs/adversaries/adversary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json index 6a984b3c..2b3867aa 100644 --- a/src/packs/adversaries/adversary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json +++ b/src/packs/adversaries/adversary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json @@ -128,12 +128,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -184,7 +181,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -210,7 +207,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -219,7 +217,7 @@ "_id": "WpOh5kHHx7lcTvEY", "img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp", "system": { - "description": "

      When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.

      ", + "description": "

      When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.

      ", "resource": null, "actions": { "HfK0u0c7NRppuF1Q": { diff --git a/src/packs/adversaries/adversary_Vault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json b/src/packs/adversaries/adversary_Vault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json index d1cca592..24db9c55 100644 --- a/src/packs/adversaries/adversary_Vault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json +++ b/src/packs/adversaries/adversary_Vault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json @@ -328,20 +328,21 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until freed with a successful Strength Roll (18).

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      You are Restrained within the Gaoler until freed with a successful Strength Roll (18). While Restrained, you can only attack the Gaoler.

      ", + "description": "

      You are Restrained within the Gaoler until freed with a successful Strength Roll (18). While Restrained, you can only attack the Gaoler.

      ", "tint": "#ffffff", "statuses": [ "restrained" @@ -351,6 +352,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!JqYraOqNmmhHk4Yy.VlHp8RjHy7MK8rqC.6TZlstmWJPbeoL7i" } ], diff --git a/src/packs/adversaries/adversary_Vault_Guardian_Sentinel_FVgYb28fhxlVcGwA.json b/src/packs/adversaries/adversary_Vault_Guardian_Sentinel_FVgYb28fhxlVcGwA.json index 67139669..e4098e93 100644 --- a/src/packs/adversaries/adversary_Vault_Guardian_Sentinel_FVgYb28fhxlVcGwA.json +++ b/src/packs/adversaries/adversary_Vault_Guardian_Sentinel_FVgYb28fhxlVcGwA.json @@ -132,12 +132,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -188,7 +185,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -214,7 +211,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -246,7 +244,7 @@ "name": "Box In", "type": "feature", "system": { - "description": "

      Mark a Stress to choose a target within Very Close range to focus on. That target has disadvantage on attack rolls when they’re within Very Close range of the @Lookup[@name]. The @Lookup[@name]Sentinel can only focus on one target at a time.

      ", + "description": "

      Mark a Stress to choose a target within Very Close range to focus on. That target has disadvantage on attack rolls when they’re within Very Close range of the @Lookup[@name]. The @Lookup[@name] can only focus on one target at a time.

      ", "resource": null, "actions": { "4RQnBu4kcUs3PcPH": { diff --git a/src/packs/adversaries/adversary_Volcanic_Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json b/src/packs/adversaries/adversary_Volcanic_Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json index 82bdd810..baf84c14 100644 --- a/src/packs/adversaries/adversary_Volcanic_Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json +++ b/src/packs/adversaries/adversary_Volcanic_Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json @@ -841,18 +841,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you break free with a successful Strength Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Restrained by the rubble until you break free with a successful Strength Roll.

      ", "tint": "#ffffff", @@ -864,6 +865,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!pMuXGCSOQaxpi5tb.uWiyaJPXcoW06pOM.YUjdwrEZ4zn7WR9X" } ], diff --git a/src/packs/adversaries/adversary_Volcanic_Dragon__Molten_Scourge_eArAPuB38CNR0ZIM.json b/src/packs/adversaries/adversary_Volcanic_Dragon__Molten_Scourge_eArAPuB38CNR0ZIM.json index b23da064..c73b0dda 100644 --- a/src/packs/adversaries/adversary_Volcanic_Dragon__Molten_Scourge_eArAPuB38CNR0ZIM.json +++ b/src/packs/adversaries/adversary_Volcanic_Dragon__Molten_Scourge_eArAPuB38CNR0ZIM.json @@ -721,18 +721,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you clear a Stress.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until you clear a Stress.

      ", "tint": "#ffffff", @@ -744,6 +745,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!eArAPuB38CNR0ZIM.2mK8kxfp2WBUeBri.xmzA6NC9zrulhzQs" } ], diff --git a/src/packs/adversaries/adversary_Weaponmaster_ZNbQ2jg35LG4t9eH.json b/src/packs/adversaries/adversary_Weaponmaster_ZNbQ2jg35LG4t9eH.json index 2989468b..55c7cb23 100644 --- a/src/packs/adversaries/adversary_Weaponmaster_ZNbQ2jg35LG4t9eH.json +++ b/src/packs/adversaries/adversary_Weaponmaster_ZNbQ2jg35LG4t9eH.json @@ -324,18 +324,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until your next successful attack

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      The next time the Taunted target attacks, they have disadvantage against targets other than the Weaponmaster.

      ", "tint": "#ffffff", @@ -345,6 +346,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!ZNbQ2jg35LG4t9eH.tyGgOqQzDSIypoMz.j2jYmYbtWXvq32yX" } ], diff --git a/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json b/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json index 446a4af3..d097f765 100644 --- a/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json +++ b/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json @@ -367,20 +367,21 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until they’re freed with a successful Strength Roll.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      You are Restrained until you're freed with a successful Strength Roll. When a creature makes an action roll against the cage, they must mark a Stress.

      ", + "description": "

      You are Restrained until you're freed with a successful Strength Roll. When a creature makes an action roll against the cage, they must mark a Stress.

      ", "tint": "#ffffff", "statuses": [ "restrained" @@ -390,6 +391,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!8yUj2Mzvnifhxegm.i8NoUGUTNY2C5NhC.k8LzBWRZo6VPqvpH" } ], diff --git a/src/packs/adversaries/adversary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json b/src/packs/adversaries/adversary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json index b0a3bded..a40f57f0 100644 --- a/src/packs/adversaries/adversary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json +++ b/src/packs/adversaries/adversary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json @@ -621,18 +621,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until they dig themselves out from the debris.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until you dig yourself out from the debris.

      ", "tint": "#ffffff", @@ -644,6 +645,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!UGPiPLJsPvMTSKEF.CcRTxCDCJskiu3fI.40cFHuNdEvbUZ9rs" } ], @@ -744,25 +755,26 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.disadvantageSources", + "value": "On attack rolls.", + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "temporary", + "description": "

      Until your next rest or you clear a Stress.

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

      Chilled until your next rest or you clear a Stress. While you are Chilled, you have disadvantage on attack rolls.

      ", "tint": "#ffffff", @@ -772,6 +784,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!UGPiPLJsPvMTSKEF.nXZHOfcYvjg3YMNU.1JlRxa07i8T1a9x6" } ], diff --git a/src/packs/ancestries/feature_Retracting_Claws_Zj69cAeb3NjIa8Hn.json b/src/packs/ancestries/feature_Retracting_Claws_Zj69cAeb3NjIa8Hn.json index 8e408ec6..6338548e 100644 --- a/src/packs/ancestries/feature_Retracting_Claws_Zj69cAeb3NjIa8Hn.json +++ b/src/packs/ancestries/feature_Retracting_Claws_Zj69cAeb3NjIa8Hn.json @@ -83,18 +83,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -106,6 +106,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!Zj69cAeb3NjIa8Hn.pO76svFkmWmZ6LjC" } ], diff --git a/src/packs/beastforms/feature_Demolish_DfBXO8jTchwFG8dZ.json b/src/packs/beastforms/feature_Demolish_DfBXO8jTchwFG8dZ.json index b7d85ef1..acc7df36 100644 --- a/src/packs/beastforms/feature_Demolish_DfBXO8jTchwFG8dZ.json +++ b/src/packs/beastforms/feature_Demolish_DfBXO8jTchwFG8dZ.json @@ -107,17 +107,18 @@ "transfer": false, "_id": "FXdFgEgqVl5gIWJS", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -129,6 +130,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!DfBXO8jTchwFG8dZ.FXdFgEgqVl5gIWJS" } ], diff --git a/src/packs/beastforms/feature_Hobbling_Strike_8u0HkK3WgtU9lWYs.json b/src/packs/beastforms/feature_Hobbling_Strike_8u0HkK3WgtU9lWYs.json index 6a16f864..a1d80e5d 100644 --- a/src/packs/beastforms/feature_Hobbling_Strike_8u0HkK3WgtU9lWYs.json +++ b/src/packs/beastforms/feature_Hobbling_Strike_8u0HkK3WgtU9lWYs.json @@ -58,17 +58,18 @@ "transfer": false, "_id": "2kKkV9zhfvqA2vlt", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -80,6 +81,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!8u0HkK3WgtU9lWYs.2kKkV9zhfvqA2vlt" } ], diff --git a/src/packs/beastforms/feature_Ocean_Master_tGDdEH40wyOCsFmH.json b/src/packs/beastforms/feature_Ocean_Master_tGDdEH40wyOCsFmH.json index a4431417..aa0baa31 100644 --- a/src/packs/beastforms/feature_Ocean_Master_tGDdEH40wyOCsFmH.json +++ b/src/packs/beastforms/feature_Ocean_Master_tGDdEH40wyOCsFmH.json @@ -51,17 +51,18 @@ "transfer": false, "_id": "6GBczj8REkDmgX2Q", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -73,6 +74,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!tGDdEH40wyOCsFmH.6GBczj8REkDmgX2Q" } ], diff --git a/src/packs/beastforms/feature_Snapping_Strike_Ky3rZD3sJMXYZOBC.json b/src/packs/beastforms/feature_Snapping_Strike_Ky3rZD3sJMXYZOBC.json index d79c9018..581bdcf5 100644 --- a/src/packs/beastforms/feature_Snapping_Strike_Ky3rZD3sJMXYZOBC.json +++ b/src/packs/beastforms/feature_Snapping_Strike_Ky3rZD3sJMXYZOBC.json @@ -58,17 +58,18 @@ "transfer": false, "_id": "y3EsJuInxE7juNXT", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -81,6 +82,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!Ky3rZD3sJMXYZOBC.y3EsJuInxE7juNXT" } ], diff --git a/src/packs/beastforms/feature_Trample_A0lgd6eVEfX6oqSB.json b/src/packs/beastforms/feature_Trample_A0lgd6eVEfX6oqSB.json index e9878f02..230f6470 100644 --- a/src/packs/beastforms/feature_Trample_A0lgd6eVEfX6oqSB.json +++ b/src/packs/beastforms/feature_Trample_A0lgd6eVEfX6oqSB.json @@ -107,17 +107,18 @@ "transfer": false, "_id": "LkekG4IngVW9rFjI", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -129,6 +130,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!A0lgd6eVEfX6oqSB.LkekG4IngVW9rFjI" } ], diff --git a/src/packs/beastforms/feature_Venomous_Bite_2KlTnfzO03vneVS8.json b/src/packs/beastforms/feature_Venomous_Bite_2KlTnfzO03vneVS8.json index 00870086..30ace68f 100644 --- a/src/packs/beastforms/feature_Venomous_Bite_2KlTnfzO03vneVS8.json +++ b/src/packs/beastforms/feature_Venomous_Bite_2KlTnfzO03vneVS8.json @@ -51,17 +51,18 @@ "transfer": false, "_id": "TTyAKKoUCoYXSMs4", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      A Poisoned creature takes 1d10 direct physical damage each time they act.

      ", "tint": "#ffffff", @@ -71,6 +72,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!2KlTnfzO03vneVS8.TTyAKKoUCoYXSMs4" } ], diff --git a/src/packs/beastforms/feature_Venomous_Strike_uW3853pViM9VAfHb.json b/src/packs/beastforms/feature_Venomous_Strike_uW3853pViM9VAfHb.json index 57d5bb56..3b39707d 100644 --- a/src/packs/beastforms/feature_Venomous_Strike_uW3853pViM9VAfHb.json +++ b/src/packs/beastforms/feature_Venomous_Strike_uW3853pViM9VAfHb.json @@ -75,17 +75,18 @@ "transfer": false, "_id": "1iQPj96LqUNkRaxE", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      A Poisoned creature takes 1d10 physical direct damage each time they act.

      ", "tint": "#ffffff", @@ -95,6 +96,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!uW3853pViM9VAfHb.1iQPj96LqUNkRaxE" } ], diff --git a/src/packs/beastforms/feature_Vicious_Maul_jYUBi7yLHap5ljpa.json b/src/packs/beastforms/feature_Vicious_Maul_jYUBi7yLHap5ljpa.json index 2bdad760..28095ea9 100644 --- a/src/packs/beastforms/feature_Vicious_Maul_jYUBi7yLHap5ljpa.json +++ b/src/packs/beastforms/feature_Vicious_Maul_jYUBi7yLHap5ljpa.json @@ -88,17 +88,18 @@ "transfer": false, "_id": "MIAh9XNwDXGDktCm", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -110,6 +111,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!jYUBi7yLHap5ljpa.MIAh9XNwDXGDktCm" } ], diff --git a/src/packs/beastforms/feature_Webslinger_D73fS1iM4SZPFimu.json b/src/packs/beastforms/feature_Webslinger_D73fS1iM4SZPFimu.json index 40adb28b..9e3a3d93 100644 --- a/src/packs/beastforms/feature_Webslinger_D73fS1iM4SZPFimu.json +++ b/src/packs/beastforms/feature_Webslinger_D73fS1iM4SZPFimu.json @@ -75,17 +75,18 @@ "transfer": false, "_id": "cBJueH89gNvvDKfQ", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary" + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -97,6 +98,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!D73fS1iM4SZPFimu.cBJueH89gNvvDKfQ" } ], diff --git a/src/packs/classes/feature_Make_a_Scene_N9E5skDDK2VgvohR.json b/src/packs/classes/feature_Make_a_Scene_N9E5skDDK2VgvohR.json index 1a444728..5f28c048 100644 --- a/src/packs/classes/feature_Make_a_Scene_N9E5skDDK2VgvohR.json +++ b/src/packs/classes/feature_Make_a_Scene_N9E5skDDK2VgvohR.json @@ -53,7 +53,7 @@ "effects": [ { "name": "Make a Scene", - "img": "icons/svg/daze.svg", + "img": "icons/magic/sonic/scream-wail-shout-teal.webp", "origin": "Compendium.daggerheart.classes.Item.OxmucTHHfuBSv2dn", "transfer": false, "_id": "8G9zDv1gac6dEHmS", @@ -64,27 +64,27 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.difficulty", + "value": -2, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "temporary" } }, - "changes": [ - { - "key": "system.difficulty", - "mode": 2, - "value": "-2", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "", + "description": "

      Giving them a -2 penalty to their Difficulty.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -92,6 +92,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!N9E5skDDK2VgvohR.8G9zDv1gac6dEHmS" } ], diff --git a/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json b/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json index 4d10c3b9..b5239242 100644 --- a/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json +++ b/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json @@ -67,25 +67,25 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.bonuses.roll.attack.bonus", + "value": 1, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "shortRest" } }, - "changes": [ - { - "key": "system.bonuses.roll.attack.bonus", - "mode": 2, - "value": "1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Gain a +1 bonus to your attack rolls until your next rest.

      ", "tint": "#ffffff", @@ -95,6 +95,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!njj2C3tMDeCHHOoh.XK4cCcz9sRGDJr0q" } ], diff --git a/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json b/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json index 231295fc..0f31f491 100644 --- a/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json +++ b/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json @@ -65,25 +65,26 @@ "type": "withinRange", "target": "any", "range": "self" + }, + "changes": [ + { + "key": "system.evasion", + "value": 2, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "temporary", + "description": "

      Until the next time an attack succeeds against you.

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

      Spend 3 Hope to gain a +2 bonus to your Evasion until the next time an attack succeeds against you. Otherwise, this bonus lasts until your next rest.

      ", "tint": "#ffffff", @@ -93,6 +94,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!hVaaPIjxoextIgSL.hhVjBro2osGDTT5g" } ], diff --git a/src/packs/domains/domainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json b/src/packs/domains/domainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json index eb053b27..c1964896 100644 --- a/src/packs/domains/domainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json +++ b/src/packs/domains/domainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json @@ -119,20 +119,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Temporarily Vulnerable and glows brightly until this condition is cleared.

      ", + "description": "

      Temporarily Vulnerable and glows brightly until this condition is cleared.

      ", "tint": "#ffffff", "statuses": [ "vulnerable" @@ -142,6 +142,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!BNevJyGk7hmN7XOY.veZpnhnF8NRRhKG4" } ], diff --git a/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json b/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json index 4ed5bd63..8c531bcd 100644 --- a/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json +++ b/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json @@ -260,25 +260,25 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.armorScore", + "value": 1, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "shortRest" } }, - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      +1 bonus to your Armor Score until your next rest, or the caster cast's Tava’s Armor again.

      ", "tint": "#ffffff", @@ -288,6 +288,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!YtZzYBtR0yLPPA93.LdcT1nrkd5ORCU4n" } ], diff --git a/src/packs/domains/domainCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json b/src/packs/domains/domainCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json index 5acec2fd..71ce49f6 100644 --- a/src/packs/domains/domainCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json +++ b/src/packs/domains/domainCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json @@ -177,18 +177,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Lasts until your next rest or the caster casts Telepathy again.

      ", "tint": "#ffffff", @@ -198,6 +198,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!df4iRqQzRntrF6Qw.zAEaETYSOE2fmcyB" }, { @@ -213,18 +223,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until they take damage or the GM spends a Fear on their turn to clear this condition.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Asleep until they take damage or the GM spends a Fear on their turn to clear this condition.

      ", "tint": "#ffffff", @@ -234,6 +245,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!df4iRqQzRntrF6Qw.gfZTHSgwYSDKsePW" } ], diff --git a/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json b/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json index 6581cd52..05d0a219 100644 --- a/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json +++ b/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json @@ -184,18 +184,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -207,6 +207,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!WtwSWXTRZa7QVvmo.iPnT02apql16Zhjf" } ], diff --git a/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json b/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json index 2a48b31e..4e08fb0e 100644 --- a/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json +++ b/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json @@ -114,25 +114,25 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.resistance.magical.immunity", + "value": 1, + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "shortRest" } }, - "changes": [ - { - "key": "system.resistance.magical.immunity", - "mode": 5, - "value": "1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Immune to magic damage until your next rest.

      ", "tint": "#ffffff", @@ -142,6 +142,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!J1ovx2FpNDvPq1o6.HWJYhSegVLeAa3dE" } ], diff --git a/src/packs/domains/domainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json b/src/packs/domains/domainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json index 8ea51d7f..5ffab1e2 100644 --- a/src/packs/domains/domainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json +++ b/src/packs/domains/domainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json @@ -167,18 +167,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      When a creature acts while On Fire, they must take an extra 2d6 magic damage if they are still On Fire at the end of their action.

      ", "tint": "#ffffff", @@ -190,6 +190,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!5EP2Lgf7ojfrc0Is.HNKkaWi507whJuYN" } ], diff --git a/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json b/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json index 67817fc1..a2a06889 100644 --- a/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json +++ b/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json @@ -70,18 +70,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      When you move into or within an adversary’s line of sight or make an attack, you are no longer Cloaked.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      While Cloaked, you remain unseen if you are stationary when an adversary moves to where they would normally see you. When you move into or within an adversary’s line of sight or make an attack, you are no longer Cloaked.

      ", "tint": "#ffffff", @@ -91,6 +92,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!Zhw7PtK8nMPlsOqD.twCBqXytmRkMz0kV" } ], diff --git a/src/packs/domains/domainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json b/src/packs/domains/domainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json index 57fc72db..614f7e32 100644 --- a/src/packs/domains/domainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json +++ b/src/packs/domains/domainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json @@ -123,20 +123,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Temporarily Vulnerable.

      ", + "description": "

      Temporarily Vulnerable.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -144,6 +144,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!C0qLOwSSvZ6PG3Ws.Z31XqmGUKWYcZdMY" } ], diff --git a/src/packs/domains/domainCard_Enrapture_a8lFiKX1o8T924ze.json b/src/packs/domains/domainCard_Enrapture_a8lFiKX1o8T924ze.json index fd27c8ce..dc574dec 100644 --- a/src/packs/domains/domainCard_Enrapture_a8lFiKX1o8T924ze.json +++ b/src/packs/domains/domainCard_Enrapture_a8lFiKX1o8T924ze.json @@ -144,18 +144,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      While Enraptured, a target’s attention is fixed on you, narrowing their field of view and drowning out any sound but your voice.

      ", "tint": "#ffffff", @@ -165,6 +165,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!a8lFiKX1o8T924ze.EYG5dLImk6GkmfRd" } ], diff --git a/src/packs/domains/domainCard_Forceful_Push_z8FFPhDh2SdFkFfS.json b/src/packs/domains/domainCard_Forceful_Push_z8FFPhDh2SdFkFfS.json index d77c8777..3d17ab5d 100644 --- a/src/packs/domains/domainCard_Forceful_Push_z8FFPhDh2SdFkFfS.json +++ b/src/packs/domains/domainCard_Forceful_Push_z8FFPhDh2SdFkFfS.json @@ -69,18 +69,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -92,6 +92,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!z8FFPhDh2SdFkFfS.95oX6QYPySdyyh2v" } ], diff --git a/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json b/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json index 2de4be7e..6666bc28 100644 --- a/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json +++ b/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json @@ -62,43 +62,44 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.bonuses.damage.physical.bonus", + "value": 10, + "priority": null, + "type": "add" + }, + { + "key": "system.bonuses.damage.magical.bonus", + "value": 10, + "priority": null, + "type": "add" + }, + { + "key": "system.damageThresholds.severe", + "value": 8, + "priority": null, + "type": "add" + }, + { + "key": "system.rules.damageReduction.disabledArmor", + "value": 1, + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "temporary", + "description": "

      Until there are no more adversaries within sight.

      " } }, - "changes": [ - { - "key": "system.bonuses.damage.physical.bonus", - "mode": 2, - "value": "10", - "priority": null - }, - { - "key": "system.bonuses.damage.magical.bonus", - "mode": 2, - "value": "10", - "priority": null - }, - { - "key": "system.damageThresholds.severe", - "mode": 2, - "value": "8", - "priority": null - }, - { - "key": "system.rules.damageReduction.disabledArmor", - "mode": 5, - "value": "1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Once per long rest, you can go into a Frenzy until there are no more adversaries within sight.

      While Frenzied, you can’t use Armor Slots, and you gain a +10 bonus to your damage rolls and a +8 bonus to your Severe damage threshold.

      ", "tint": "#ffffff", @@ -108,6 +109,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!MMl7abdGRLl7TJLO.1POoAgObPOWDpUco" } ], diff --git a/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json b/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json index 43b4baf4..fb0bd16d 100644 --- a/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json +++ b/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json @@ -68,57 +68,57 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.traits.strength.value", + "value": 2, + "priority": null, + "type": "add" + }, + { + "key": "system.traits.agility.value", + "value": 2, + "priority": null, + "type": "add" + }, + { + "key": "system.traits.finesse.value", + "value": 2, + "priority": null, + "type": "add" + }, + { + "key": "system.traits.instinct.value", + "value": 2, + "priority": null, + "type": "add" + }, + { + "key": "system.traits.presence.value", + "value": 2, + "priority": null, + "type": "add" + }, + { + "key": "system.traits.knowledge.value", + "value": 2, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "shortRest" } }, - "changes": [ - { - "key": "system.traits.strength.value", - "mode": 2, - "value": "2", - "priority": null - }, - { - "key": "system.traits.agility.value", - "mode": 2, - "value": "2", - "priority": null - }, - { - "key": "system.traits.finesse.value", - "mode": 2, - "value": "2", - "priority": null - }, - { - "key": "system.traits.instinct.value", - "mode": 2, - "value": "2", - "priority": null - }, - { - "key": "system.traits.presence.value", - "mode": 2, - "value": "2", - "priority": null - }, - { - "key": "system.traits.knowledge.value", - "mode": 2, - "value": "2", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Gain a +2 bonus to all of your character traits until your next rest.

      ", + "description": "

      Gain a +2 bonus to all of your character traits until your next rest.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -126,6 +126,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!SgvjJfMyubZowPxS.H5q5iYImr69TfZcp" } ], diff --git a/src/packs/domains/domainCard_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json b/src/packs/domains/domainCard_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json index ef2b6df9..3c17d4ee 100644 --- a/src/packs/domains/domainCard_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json +++ b/src/packs/domains/domainCard_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json @@ -41,7 +41,7 @@ }, "effects": [ { - "_id": "X2w3kRHaETs8YWLO", + "_id": "9avDhppIdTqAR56f", "onSave": false } ], @@ -82,12 +82,25 @@ "effects": [ { "name": "Glyph of Nightfall", - "img": "icons/magic/symbols/runes-triangle-magenta.webp", + "img": "systems/daggerheart/assets/icons/domains/domain-card/midnight.png", "origin": "Compendium.daggerheart.domains.Item.B5HXqYRJiL3xMNKT", "transfer": false, - "_id": "st8Ji9ZNexvw64xM", + "_id": "9avDhppIdTqAR56f", "type": "base", "system": { + "changes": [ + { + "key": "system.difficulty", + "type": "add", + "value": "-max(ORIGIN.@system.traits.knowledge.value,1)", + "priority": null, + "phase": "initial" + } + ], + "duration": { + "description": "", + "type": "temporary" + }, "rangeDependence": { "enabled": false, "type": "withinRange", @@ -95,76 +108,32 @@ "range": "melee" } }, - "changes": [ - { - "key": "system.difficulty", - "mode": 2, - "value": "-max(ORIGIN.@system.traits.knowledge.value,1)", - "priority": null - } - ], "disabled": false, - "duration": { - "startTime": null, + "start": { + "time": 0, "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "combatant": null, + "initiative": null, + "round": null, + "turn": null }, - "description": "

      Conjure a dark glyph upon their body that exposes their weak points, temporarily reducing the target’s Difficulty by a value equal to your Knowledge (minimum 1).

      ", + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "description": "

      Conjure a dark glyph upon their body that exposes their weak points, temporarily reducing the target’s Difficulty by a value equal to your Knowledge (minimum 1).

      ", "tint": "#ffffff", "statuses": [], + "showIcon": 1, + "folder": null, "sort": 0, "flags": {}, "_stats": { "compendiumSource": null }, - "_key": "!items.effects!B5HXqYRJiL3xMNKT.st8Ji9ZNexvw64xM" - }, - { - "name": "Glyph of Nightfall", - "img": "icons/magic/symbols/runes-triangle-magenta.webp", - "origin": "Compendium.daggerheart.domains.Item.B5HXqYRJiL3xMNKT", - "transfer": false, - "_id": "X2w3kRHaETs8YWLO", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "changes": [ - { - "key": "system.difficulty", - "mode": 2, - "value": "-max(ORIGIN.@system.traits.knowledge.value,1)", - "priority": null - } - ], - "disabled": false, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      Conjure a dark glyph upon their body that exposes their weak points, temporarily reducing the target’s Difficulty by a value equal to your Knowledge (minimum 1).

      ", - "tint": "#ffffff", - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!B5HXqYRJiL3xMNKT.X2w3kRHaETs8YWLO" + "_key": "!items.effects!B5HXqYRJiL3xMNKT.9avDhppIdTqAR56f" } ], "ownership": { diff --git a/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json b/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json index 5b3c95d3..263220e4 100644 --- a/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json +++ b/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json @@ -132,18 +132,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      If an adversary moves within Very Close range, they’re pulled into Melee range and Restrained.

      ", "tint": "#ffffff", @@ -155,6 +155,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!kdFoLo3KXwn4LqTG.WTYg0b8nE1XbnMiA" } ], diff --git a/src/packs/domains/domainCard_Hush_gwmYasmfgXZ7tFS6.json b/src/packs/domains/domainCard_Hush_gwmYasmfgXZ7tFS6.json index f49c4a83..23b9588d 100644 --- a/src/packs/domains/domainCard_Hush_gwmYasmfgXZ7tFS6.json +++ b/src/packs/domains/domainCard_Hush_gwmYasmfgXZ7tFS6.json @@ -82,45 +82,7 @@ "effects": [ { "name": "Silenced", - "img": "icons/svg/sound-off.svg", - "origin": "Compendium.daggerheart.domains.Item.gwmYasmfgXZ7tFS6", - "transfer": false, - "_id": "5hzzPTFccUSSNHgp", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "changes": [], - "disabled": false, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      Auppressive magic around the target that encompasses everything within Very Close range of them and follows them as they move. The target and anything within the area is Silenced until the GM spends a Fear on their turn to clear this condition, you cast Hush again, or you take Major damage. While Silenced, they can’t make noise and can’t cast spells.

      ", - "tint": "#ffffff", - "statuses": [ - "silence" - ], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!gwmYasmfgXZ7tFS6.5hzzPTFccUSSNHgp" - }, - { - "name": "Silenced", - "img": "icons/svg/sound-off.svg", + "img": "systems/daggerheart/assets/icons/domains/domain-card/midnight.png", "origin": "Compendium.daggerheart.domains.Item.gwmYasmfgXZ7tFS6", "transfer": false, "_id": "pZ5YpjKidaj48IYF", @@ -131,20 +93,21 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until the GM spends a Fear on their turn to clear this condition, you cast Hush again, or you take Major damage.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Suppressive magic around the target that encompasses everything within Very Close range of them and follows them as they move. The target and anything within the area is Silenced until the GM spends a Fear on their turn to clear this condition, you cast Hush again, or you take Major damage. While Silenced, they can’t make noise and can’t cast spells.

      ", + "description": "

      Suppressive magic around the target that encompasses everything within Very Close range of them and follows them as they move. The target and anything within the area is Silenced until the GM spends a Fear on their turn to clear this condition, you cast Hush again, or you take Major damage. While Silenced, they can’t make noise and can’t cast spells.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -152,6 +115,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!gwmYasmfgXZ7tFS6.pZ5YpjKidaj48IYF" } ], diff --git a/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json b/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json index 67310781..ffa0226d 100644 --- a/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json +++ b/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json @@ -112,20 +112,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      An illusion of flashing colors and lights that temporarily Stuns targets. While Stunned, they can’t use reactions and can’t take any other actions until they clear this condition.

      ", + "description": "

      An illusion of flashing colors and lights that temporarily Stuns targets. While Stunned, they can’t use reactions and can’t take any other actions until they clear this condition.

      ", "tint": "#ffffff", "statuses": [ "stun" @@ -135,6 +135,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!2ZeuCGVatQdPOVC6.xAG75UWUz3aDZH3m" } ], diff --git a/src/packs/domains/domainCard_Life_Ward_OszbCj0jTqq2ADx9.json b/src/packs/domains/domainCard_Life_Ward_OszbCj0jTqq2ADx9.json index 19a6fda6..de841ffe 100644 --- a/src/packs/domains/domainCard_Life_Ward_OszbCj0jTqq2ADx9.json +++ b/src/packs/domains/domainCard_Life_Ward_OszbCj0jTqq2ADx9.json @@ -70,18 +70,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "longRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Marked with a glowing sigil of protection. When this ally would make a death move, they clear a Hit Point instead.

      This effect ends when it saves the target from a death move, you cast Life Ward on another target, or you take a long rest.

      ", "tint": "#ffffff", @@ -91,6 +91,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!OszbCj0jTqq2ADx9.E7Ou4OMEy3TeK1Gf" } ], diff --git a/src/packs/domains/domainCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json b/src/packs/domains/domainCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json index da64f16e..d4df379e 100644 --- a/src/packs/domains/domainCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json +++ b/src/packs/domains/domainCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json @@ -144,20 +144,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      While Enraptured, a target’s attention is fixed on you, narrowing their field of view and drowning out any sound but your voice.

      ", + "description": "

      While Enraptured, a target’s attention is fixed on you, narrowing their field of view and drowning out any sound but your voice.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -165,6 +165,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!ubpixIgZrJXKyM3b.QNbnelRylVB0yCm0" } ], diff --git a/src/packs/domains/domainCard_Night_Terror_zcldCuqOg3dphUVI.json b/src/packs/domains/domainCard_Night_Terror_zcldCuqOg3dphUVI.json index 6ffddbe7..59a0c924 100644 --- a/src/packs/domains/domainCard_Night_Terror_zcldCuqOg3dphUVI.json +++ b/src/packs/domains/domainCard_Night_Terror_zcldCuqOg3dphUVI.json @@ -146,20 +146,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      While Horrified, they’re Vulnerable.

      ", + "description": "

      While Horrified, they’re Vulnerable.

      ", "tint": "#ffffff", "statuses": [ "vulnerable" @@ -169,6 +169,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!zcldCuqOg3dphUVI.32j3ZeNMMCk1QLlM" } ], diff --git a/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json b/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json index cc04c9c9..1b0173d7 100644 --- a/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json +++ b/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json @@ -94,25 +94,25 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.traits.presence.value", + "value": "@cast", + "priority": 51, + "type": "override" + } + ], + "duration": { + "type": "longRest" } }, - "changes": [ - { - "key": "system.traits.presence.value", - "mode": 5, - "value": "@cast", - "priority": 51 - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Your Presence is equal to your Spellcast trait until your next long rest.
      While this spell is active, an adversary must mark a Stress when they target you with an attack.

      ", "tint": "#ffffff", @@ -122,6 +122,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!iEBLySZD9z8CLdz7.ba9GO4NtQHYkaRR9" } ], diff --git a/src/packs/domains/domainCard_Redirect_faU0XkJCbar69PiN.json b/src/packs/domains/domainCard_Redirect_faU0XkJCbar69PiN.json index 4adb8240..aeb20f1d 100644 --- a/src/packs/domains/domainCard_Redirect_faU0XkJCbar69PiN.json +++ b/src/packs/domains/domainCard_Redirect_faU0XkJCbar69PiN.json @@ -50,7 +50,7 @@ }, "flags": {}, "_id": "faU0XkJCbar69PiN", - "sort": 3400000, + "sort": 3500000, "effects": [], "ownership": { "default": 0 diff --git a/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json b/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json index cd906eaa..39139dfa 100644 --- a/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json +++ b/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json @@ -85,18 +85,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -108,6 +108,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!kguhWlidhxe2GbT0.RFB4V0V4bDJ6vCL2" } ], diff --git a/src/packs/domains/domainCard_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json b/src/packs/domains/domainCard_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json index 6fde5d18..f9b86bd0 100644 --- a/src/packs/domains/domainCard_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json +++ b/src/packs/domains/domainCard_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json @@ -175,20 +175,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Temporarily Stunned. While Stunned, they can’t use reactions and can’t take any other actions until they clear this condition.

      ", + "description": "

      Temporarily Stunned. While Stunned, they can’t use reactions and can’t take any other actions until they clear this condition.

      ", "tint": "#ffffff", "statuses": [ "stun" @@ -198,6 +198,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!lRHo6ZkK1zybeEoG.kSLuGSI6FLhOJaGp" } ], diff --git a/src/packs/domains/domainCard_Tempest_X7YaZgFieBlqaPdZ.json b/src/packs/domains/domainCard_Tempest_X7YaZgFieBlqaPdZ.json index d82dd9fa..7d587b02 100644 --- a/src/packs/domains/domainCard_Tempest_X7YaZgFieBlqaPdZ.json +++ b/src/packs/domains/domainCard_Tempest_X7YaZgFieBlqaPdZ.json @@ -268,18 +268,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -291,6 +291,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!X7YaZgFieBlqaPdZ.oqPY3I9oO9J6l5Aj" }, { diff --git a/src/packs/domains/domainCard_Through_Your_Eyes_7b0mzV5QMPjVPT4o.json b/src/packs/domains/domainCard_Through_Your_Eyes_7b0mzV5QMPjVPT4o.json index ed01392d..529950b5 100644 --- a/src/packs/domains/domainCard_Through_Your_Eyes_7b0mzV5QMPjVPT4o.json +++ b/src/packs/domains/domainCard_Through_Your_Eyes_7b0mzV5QMPjVPT4o.json @@ -61,20 +61,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Can see through their eyes and hear through their ears.

      ", + "description": "

      Can see through their eyes and hear through their ears.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -82,6 +82,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!7b0mzV5QMPjVPT4o.TCOHV7tWpunCZDxn" } ], diff --git a/src/packs/domains/domainCard_Transcendent_Union_kVkoCLBXLAIifqpz.json b/src/packs/domains/domainCard_Transcendent_Union_kVkoCLBXLAIifqpz.json index 7d8d9bbe..4460a5db 100644 --- a/src/packs/domains/domainCard_Transcendent_Union_kVkoCLBXLAIifqpz.json +++ b/src/packs/domains/domainCard_Transcendent_Union_kVkoCLBXLAIifqpz.json @@ -70,18 +70,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Until your next rest, when a creature connected by this union would mark Stress or Hit Points, the connected creatures can choose who marks it.

      ", "tint": "#ffffff", @@ -91,6 +91,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!kVkoCLBXLAIifqpz.kMcvp2QKmBP4uinB" } ], diff --git a/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json b/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json index dae448e9..85935876 100644 --- a/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json +++ b/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json @@ -146,18 +146,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -169,6 +169,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!qvpvTnkAoRn9vYO4.Xh0wrgRUuYpwChBU" }, { @@ -184,18 +194,18 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -207,6 +217,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!qvpvTnkAoRn9vYO4.2xzOqTaPJQzGqFJv" } ], diff --git a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json index c1006da4..0fa0a09e 100644 --- a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json +++ b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json @@ -268,18 +268,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you’re freed with a successful Finesse or Strength roll or by dealing at least 6 damage to the vines.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Restrained lasts until you’re freed with a successful Finesse or Strength roll or by dealing at least 6 damage to the vines.

      ", "tint": "#ffffff", @@ -291,6 +292,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!pGEdzdLkqYtBhxnG.maK5OyfrOxcjCoPt.LSeftEwgBbXXkLw3" } ], diff --git a/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json b/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json index ea4f1951..a48d9b83 100644 --- a/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json +++ b/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json @@ -271,30 +271,41 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you break free with a successful Finesse or Strength Roll or by dealing 10 damage to the vines.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Restrained and Vulnerable until you break free, clearing both conditions, with a successful Finesse or Strength Roll or by dealing 10 damage to the vines. When the target makes a roll to escape, they take 1d8+4 physical damage and lose a Hope.

      What painful memories do the vines bring to the surface as they pierce flesh?

      ", + "description": "

      Restrained and Vulnerable until you break free, clearing both conditions, with a successful Finesse or Strength Roll or by dealing 10 damage to the vines. When the target makes a roll to escape, they take 1d8+4 physical damage and lose a Hope.

      What painful memories do the vines bring to the surface as they pierce flesh?

      ", "tint": "#ffffff", "statuses": [ - "restrained", - "vulnerable" + "vulnerable", + "restrained" ], "sort": 0, "flags": {}, "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!oY69NN4rYxoRE4hl.1aOeMMX0XuDtZbbB.gCkqvBUljsOsYacB" } ], diff --git a/src/packs/environments/environment_Bustling_Marketplace_HZKA7hkej7JJY503.json b/src/packs/environments/environment_Bustling_Marketplace_HZKA7hkej7JJY503.json index ad96108b..af0b3a70 100644 --- a/src/packs/environments/environment_Bustling_Marketplace_HZKA7hkej7JJY503.json +++ b/src/packs/environments/environment_Bustling_Marketplace_HZKA7hkej7JJY503.json @@ -50,12 +50,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/forest.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -106,7 +103,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -132,14 +129,15 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { "name": "Tip the Scales", "type": "feature", "system": { - "description": "

      PCs can gain advantage on a Presence Roll by off ering a handful of gold as part of the interaction.

      Will any coin be accepted or only local currency? How overt are the PCs in offering this bribe?

      ", + "description": "

      PCs can gain advantage on a Presence Roll by offering a handful of gold as part of the interaction.

      Will any coin be accepted or only local currency? How overt are the PCs in offering this bribe?

      ", "resource": null, "actions": {}, "originItemType": null, diff --git a/src/packs/environments/environment_Raging_River_t4cdqTfzcqP3H1vJ.json b/src/packs/environments/environment_Raging_River_t4cdqTfzcqP3H1vJ.json index 6c34c296..03dd72e3 100644 --- a/src/packs/environments/environment_Raging_River_t4cdqTfzcqP3H1vJ.json +++ b/src/packs/environments/environment_Raging_River_t4cdqTfzcqP3H1vJ.json @@ -315,18 +315,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until you get out of the river.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Vulnerable until you get out of the river.

      ", "tint": "#ffffff", @@ -338,6 +339,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!t4cdqTfzcqP3H1vJ.WsNoSwwtv0r80BMj.T0ouSQyR8cVpAn79" } ], diff --git a/src/packs/items/consumables/consumable_Blinding_Orb_eAXHdzA5qNPldOpn.json b/src/packs/items/consumables/consumable_Blinding_Orb_eAXHdzA5qNPldOpn.json index 94a6671b..9c6403e1 100644 --- a/src/packs/items/consumables/consumable_Blinding_Orb_eAXHdzA5qNPldOpn.json +++ b/src/packs/items/consumables/consumable_Blinding_Orb_eAXHdzA5qNPldOpn.json @@ -60,19 +60,21 @@ "transfer": false, "_id": "nryJhrF26hyFQUxH", "type": "base", - "system": {}, - "changes": [], + "system": { + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until they mark HP.

      " + } + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      You are Vulnerable until you mark a Hit Point.

      ", + "description": "

      You are Vulnerable until you mark a Hit Point.

      ", "tint": "#ffffff", "statuses": [ "vulnerable" @@ -82,6 +84,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!eAXHdzA5qNPldOpn.nryJhrF26hyFQUxH" } ], diff --git a/src/packs/items/consumables/consumable_Growing_Potion_fl2f3ees8RFMze9t.json b/src/packs/items/consumables/consumable_Growing_Potion_fl2f3ees8RFMze9t.json index e4493348..44114455 100644 --- a/src/packs/items/consumables/consumable_Growing_Potion_fl2f3ees8RFMze9t.json +++ b/src/packs/items/consumables/consumable_Growing_Potion_fl2f3ees8RFMze9t.json @@ -60,30 +60,31 @@ "transfer": false, "_id": "YEGd74Lssj7rCmpF", "type": "base", - "system": {}, - "changes": [ - { - "key": "system.traits.strength.value", - "mode": 2, - "value": "2", - "priority": null - }, - { - "key": "system.proficiency", - "mode": 2, - "value": "1", - "priority": null + "system": { + "changes": [ + { + "key": "system.traits.strength.value", + "value": 2, + "priority": null, + "type": "add" + }, + { + "key": "system.proficiency", + "value": 1, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "shortRest" } - ], + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -93,6 +94,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!fl2f3ees8RFMze9t.YEGd74Lssj7rCmpF" } ], diff --git a/src/packs/items/consumables/consumable_Major_Stride_Potion_yK6eEDUrsPbZA8G0.json b/src/packs/items/consumables/consumable_Major_Stride_Potion_yK6eEDUrsPbZA8G0.json index 320a77a3..cfcee96b 100644 --- a/src/packs/items/consumables/consumable_Major_Stride_Potion_yK6eEDUrsPbZA8G0.json +++ b/src/packs/items/consumables/consumable_Major_Stride_Potion_yK6eEDUrsPbZA8G0.json @@ -60,24 +60,25 @@ "transfer": false, "_id": "L9dAw8pws1o02XkE", "type": "base", - "system": {}, - "changes": [ - { - "key": "system.traits.agility.value", - "mode": 2, - "value": "1", - "priority": null + "system": { + "changes": [ + { + "key": "system.traits.agility.value", + "value": 1, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "shortRest" } - ], + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -87,6 +88,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!yK6eEDUrsPbZA8G0.L9dAw8pws1o02XkE" } ], diff --git a/src/packs/items/consumables/consumable_Morphing_Clay_f1NHVSIHJJCIOaBl.json b/src/packs/items/consumables/consumable_Morphing_Clay_f1NHVSIHJJCIOaBl.json index 970cabd4..68965de6 100644 --- a/src/packs/items/consumables/consumable_Morphing_Clay_f1NHVSIHJJCIOaBl.json +++ b/src/packs/items/consumables/consumable_Morphing_Clay_f1NHVSIHJJCIOaBl.json @@ -66,20 +66,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      Your face is unrecognizable until your next rest.

      ", + "description": "

      Your face is unrecognizable until your next rest.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -87,6 +87,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!f1NHVSIHJJCIOaBl.rMno0zO5Cbwlu4zn" } ], diff --git a/src/packs/items/consumables/consumable_Ogre_Musk_qr1bosjFcUfuwq4B.json b/src/packs/items/consumables/consumable_Ogre_Musk_qr1bosjFcUfuwq4B.json index 874072cc..fdd98249 100644 --- a/src/packs/items/consumables/consumable_Ogre_Musk_qr1bosjFcUfuwq4B.json +++ b/src/packs/items/consumables/consumable_Ogre_Musk_qr1bosjFcUfuwq4B.json @@ -66,20 +66,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      You cannot be tracked by mundane or magical means until your next rest.

      ", + "description": "

      You cannot be tracked by mundane or magical means until your next rest.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -87,6 +87,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!qr1bosjFcUfuwq4B.n73d0J4oMCBIPWHN" } ], diff --git a/src/packs/items/consumables/consumable_Shrinking_Potion_HGixKenQwhyRAYNk.json b/src/packs/items/consumables/consumable_Shrinking_Potion_HGixKenQwhyRAYNk.json index 7bf8c686..ed0e233e 100644 --- a/src/packs/items/consumables/consumable_Shrinking_Potion_HGixKenQwhyRAYNk.json +++ b/src/packs/items/consumables/consumable_Shrinking_Potion_HGixKenQwhyRAYNk.json @@ -60,30 +60,31 @@ "transfer": false, "_id": "yaRLd7eHWYm2MHRM", "type": "base", - "system": {}, - "changes": [ - { - "key": "system.traits.agility.value", - "mode": 2, - "value": "2", - "priority": null - }, - { - "key": "system.proficiency", - "mode": 2, - "value": "-1", - "priority": null + "system": { + "changes": [ + { + "key": "system.traits.agility.value", + "value": 2, + "priority": null, + "type": "add" + }, + { + "key": "system.proficiency", + "value": -1, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "shortRest" } - ], + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -93,6 +94,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!HGixKenQwhyRAYNk.yaRLd7eHWYm2MHRM" } ], diff --git a/src/packs/items/consumables/consumable_Vial_of_Moondrip_VqEX5YwK5oL3r1t6.json b/src/packs/items/consumables/consumable_Vial_of_Moondrip_VqEX5YwK5oL3r1t6.json index 42359fe8..195a46de 100644 --- a/src/packs/items/consumables/consumable_Vial_of_Moondrip_VqEX5YwK5oL3r1t6.json +++ b/src/packs/items/consumables/consumable_Vial_of_Moondrip_VqEX5YwK5oL3r1t6.json @@ -66,20 +66,20 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "shortRest" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      You can see in total darkness until your next rest.

      ", + "description": "

      You can see in total darkness until your next rest.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -87,6 +87,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!VqEX5YwK5oL3r1t6.548KAUPcSbQLsivh" } ], diff --git a/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json b/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json index 304bd29c..5dee0c1c 100644 --- a/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json +++ b/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json @@ -59,13 +59,19 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [], + "duration": { + "type": "temporary", + "description": "

      Until the next successful attack you make against that adversary.

      " } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -75,6 +81,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!k7vvMJtEcxMWUUrW.9Uo0yOYGn3vandPp" } ], diff --git a/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json b/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json index 106b0057..578be0a3 100644 --- a/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json +++ b/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json @@ -26,27 +26,28 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.evasion", + "value": 2, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "", + "description": "

      Against the attack.

      " } }, "_id": "IZhakv6EuG8DO4a3", "img": "icons/creatures/mammals/humanoid-wolf-dog-blue.webp", - "changes": [ - { - "key": "system.evasion", - "mode": 2, - "value": "2", - "priority": null - } - ], "disabled": true, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      When an adversary attacks you while they’re within your companion’s Melee range, you gain a +2 bonus to your Evasion against the attack.

      ", "origin": null, @@ -58,6 +59,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!hWsKyed1vfILg0I8.IZhakv6EuG8DO4a3" } ], diff --git a/src/packs/subclasses/feature_Elemental_Dominion_EFUJHrkTuyv8uA9l.json b/src/packs/subclasses/feature_Elemental_Dominion_EFUJHrkTuyv8uA9l.json index 4297173f..2476046c 100644 --- a/src/packs/subclasses/feature_Elemental_Dominion_EFUJHrkTuyv8uA9l.json +++ b/src/packs/subclasses/feature_Elemental_Dominion_EFUJHrkTuyv8uA9l.json @@ -189,18 +189,18 @@ "type": "withinRange", "target": "hostile", "range": "veryFar" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      When an attack against you succeeds, you can mark a Stress to make the attacker temporarily Vulnerable.

      ", "tint": "#ffffff", @@ -212,6 +212,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!EFUJHrkTuyv8uA9l.bGwFZZT4juuaIRmZ" }, { diff --git a/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json b/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json index 18f80cdb..82cd55a0 100644 --- a/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json +++ b/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json @@ -26,27 +26,28 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.evasion", + "value": 2, + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "", + "description": "

      Against the attack.

      " } }, "_id": "X4llFOcAcdJLbear", "img": "icons/creatures/mammals/beast-horned-scaled-glowing-orange.webp", - "changes": [ - { - "key": "system.evasion", - "mode": 2, - "value": "2", - "priority": null - } - ], "disabled": true, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      When your Focus makes an attack against you, you gain a +2 bonus to your Evasion against the attack.

      ", "origin": null, @@ -58,6 +59,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!Cjtc43V3IzAmfIFG.X4llFOcAcdJLbear" } ], diff --git a/src/packs/subclasses/feature_Gifted_Performer_99U7YWNCxFZHCiT0.json b/src/packs/subclasses/feature_Gifted_Performer_99U7YWNCxFZHCiT0.json index f53f7c31..33d5af92 100644 --- a/src/packs/subclasses/feature_Gifted_Performer_99U7YWNCxFZHCiT0.json +++ b/src/packs/subclasses/feature_Gifted_Performer_99U7YWNCxFZHCiT0.json @@ -180,7 +180,7 @@ "effects": [ { "name": "Epic Song", - "img": "icons/svg/ice-aura.svg", + "img": "icons/tools/instruments/harp-yellow-teal.webp", "origin": "Compendium.daggerheart.subclasses.Item.6j1RP4fz3BwSfoli", "transfer": false, "_id": "FK4IdbxluRErfYor", @@ -191,18 +191,18 @@ "type": "withinRange", "target": "hostile", "range": "close" + }, + "changes": [], + "duration": { + "type": "temporary" } }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

      Make a target within Close range temporarily Vulnerable.

      ", "tint": "#ffffff", @@ -214,6 +214,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!99U7YWNCxFZHCiT0.FK4IdbxluRErfYor" } ], diff --git a/src/packs/subclasses/feature_Transcendence_th6HZwEFnVBjUtqm.json b/src/packs/subclasses/feature_Transcendence_th6HZwEFnVBjUtqm.json index 5f2df7cb..c03c10b5 100644 --- a/src/packs/subclasses/feature_Transcendence_th6HZwEFnVBjUtqm.json +++ b/src/packs/subclasses/feature_Transcendence_th6HZwEFnVBjUtqm.json @@ -58,29 +58,29 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.damageThresholds.severe", + "value": "+4", + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "" } }, "_id": "zFOpzO3tBJPcZcRc", "img": "icons/magic/fire/elemental-fire-flying.webp", - "changes": [ - { - "key": "system.damageThresholds.severe", - "mode": 2, - "value": "+4", - "priority": null - } - ], "disabled": true, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      +4 bonus to your Severe threshold

      ", + "description": "

      +4 bonus to your Severe threshold

      ", "origin": null, "tint": "#ffffff", "transfer": true, @@ -90,6 +90,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!th6HZwEFnVBjUtqm.zFOpzO3tBJPcZcRc" }, { diff --git a/styles/less/sheets/activeEffects/activeEffects.less b/styles/less/sheets/activeEffects/activeEffects.less index f1879463..ba3ff43f 100644 --- a/styles/less/sheets/activeEffects/activeEffects.less +++ b/styles/less/sheets/activeEffects/activeEffects.less @@ -1,4 +1,28 @@ .application.sheet.daggerheart.dh-style.active-effect-config { + .custom-duration-section { + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; + overflow: hidden; + height: 0; + transition: height ease-in-out 0.3s; + + &.visible { + height: auto; + } + } + + .duration-description { + height: 0; + overflow: hidden; + transition: height ease-in-out 0.3s; + + &.visible { + height: 100px; + } + } + .tab.changes { gap: 0; diff --git a/styles/less/ux/tooltip/bordered-tooltip.less b/styles/less/ux/tooltip/bordered-tooltip.less index 78622377..b3a5ed29 100644 --- a/styles/less/ux/tooltip/bordered-tooltip.less +++ b/styles/less/ux/tooltip/bordered-tooltip.less @@ -42,5 +42,37 @@ color: @golden; font-size: 12px; } + + .duration-container { + display: flex; + flex-direction: column; + text-align: center; + margin-top: 0.5rem; + width: 100%; + + &::before, + &::after { + content: ''; + background: var(--golden, #f3c267); + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + } + + &::before { + margin-bottom: 8px; + } + + &::after { + margin-top: 8px; + } + + .duration-inner-container { + display: flex; + justify-content: center; + gap: 2px; + width: 100%; + } + } } } diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less index 8e6f638d..ac09afc0 100644 --- a/styles/less/ux/tooltip/tooltip.less +++ b/styles/less/ux/tooltip/tooltip.less @@ -53,6 +53,33 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip.card-style) { } } + .tooltip-duration { + font-style: italic; + text-align: start; + position: relative; + width: 100%; + padding: 5px 10px; + margin: 5px 0px; + + &::before { + content: ''; + background: @golden; + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + position: absolute; + top: -5px; + font-size: 14px; + } + + .duration-inner-container { + display: flex; + justify-content: center; + gap: 2px; + width: 100%; + } + } + .tooltip-tags { display: flex; flex-direction: column; diff --git a/templates/settings/automation-settings/general.hbs b/templates/settings/automation-settings/general.hbs index c5f4d871..a10e50c6 100644 --- a/templates/settings/automation-settings/general.hbs +++ b/templates/settings/automation-settings/general.hbs @@ -14,6 +14,7 @@ {{formGroup settingFields.schema.fields.summaryMessages.fields.effects value=settingFields._source.summaryMessages.effects localize=true}} + {{formGroup settingFields.schema.fields.autoExpireActiveEffects value=settingFields._source.autoExpireActiveEffects localize=true}} {{formGroup settingFields.schema.fields.countdownAutomation value=settingFields._source.countdownAutomation localize=true}} {{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}} {{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}} diff --git a/templates/sheets/activeEffect/settings.hbs b/templates/sheets/activeEffect/settings.hbs index 6ccd9ee9..9443edfb 100644 --- a/templates/sheets/activeEffect/settings.hbs +++ b/templates/sheets/activeEffect/settings.hbs @@ -7,47 +7,31 @@ {{formGroup systemFields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }} {{formGroup systemFields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }} -{{!-- - {{#if start}} -
      - {{localize "EFFECT.START.Header"}} -
      - {{localize "EFFECT.FIELDS.start.time.label"}} -
      {{start.time}}
      -
      - {{#if start.combat}} -
      - {{localize "DOCUMENT.Combat"}} -
      - {{localize "EFFECT.START.Combat" round=start.round turn=start.turn}} -
      -
      - {{#if start.combatant}} -
      - {{localize "DOCUMENT.Combatant"}} -
      - {{localize "EFFECT.START.Combatant" combatant=start.combatantName initiative=start.combatantInitiative}} -
      -
      - {{/if}} - {{/if}} -
      - {{/if}} --}} +
      + {{localize "EFFECT.DURATION.Label"}} - {{!--
      - {{localize "EFFECT.DURATION.Header"}} -
      - -
      - {{formInput fields.duration.fields.value value=source.duration.value id=(concat rootId "-duration.value") - aria=(object label=(localize "EFFECT.FIELDS.duration.value.label"))}} - {{formInput fields.duration.fields.units value=source.duration.units id=(concat rootId "-duration.units") - options=durationUnits aria=(object label=(localize "EFFECT.FIELDS.duration.units.label"))}} + {{formGroup systemFields.duration.fields.type value=source.system.duration.type localize=true }} + +
      +
      + {{formInput systemFields.duration.fields.description value=source.system.duration.description localize=true }} +
      +
      + +
      + {{formGroup fields.start.fields.time value=source.start.time localize=true }} +
      + +
      + {{formInput fields.duration.fields.value value=source.duration.value id=(concat rootId "-duration.value") + aria=(object label=(localize "EFFECT.FIELDS.duration.value.label"))}} + {{formInput fields.duration.fields.units value=source.duration.units id=(concat rootId "-duration.units") + options=durationUnits aria=(object label=(localize "EFFECT.FIELDS.duration.units.label")) localize=true }} +
      - {{formGroup fields.duration.fields.expiry choices=expiryEvents value=source.duration.expiry rootId=rootId}} -
      --}} +
      \ No newline at end of file diff --git a/templates/ui/tooltip/effect-display.hbs b/templates/ui/tooltip/effect-display.hbs index 5ca4fec2..d37b5147 100644 --- a/templates/ui/tooltip/effect-display.hbs +++ b/templates/ui/tooltip/effect-display.hbs @@ -16,6 +16,16 @@ {{/if}} + {{#if effect.system.duration.type}} + +
      +
      + {{localize "EFFECT.DURATION.Label"}}: + {{localize (concat "DAGGERHEART.CONFIG.ActiveEffectDuration." effect.system.duration.type )}} +
      +
      + {{/if}} + {{#unless effect.isLockedCondition}}

      {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} diff --git a/templates/ui/tooltip/effect.hbs b/templates/ui/tooltip/effect.hbs index 4430b91d..c5004a86 100644 --- a/templates/ui/tooltip/effect.hbs +++ b/templates/ui/tooltip/effect.hbs @@ -4,4 +4,12 @@ {{#if description}}

      {{{description}}}
      {{/if}} + {{#if item.system.duration.type}} +
      +
      + {{localize "EFFECT.DURATION.Label"}}: + {{localize (concat "DAGGERHEART.CONFIG.ActiveEffectDuration." item.system.duration.type )}} +
      +
      + {{/if}} \ No newline at end of file From bcb30a6ff7b300c7d3dad1c69015aba1e6bb8c20 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 25 Feb 2026 21:57:28 +0100 Subject: [PATCH 016/304] Fixed environment potentialAdversaries --- .../sheets-configs/environment-settings.mjs | 10 +++++++--- module/data/actor/environment.mjs | 2 +- module/data/fields/foreignDocumentUUIDArrayField.mjs | 2 +- system.json | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/module/applications/sheets-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index 5c039f85..bc0efef2 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -68,9 +68,9 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { */ static async #addCategory() { await this.actor.update({ - [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( - 'DAGGERHEART.ACTORS.Environment.newAdversary' - ) + [`system.potentialAdversaries.${foundry.utils.randomID()}`]: { + label: game.i18n.localize('DAGGERHEART.ACTORS.Environment.newAdversary') + } }); } @@ -138,4 +138,8 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { this.render(); } } + + async _onDropItem(event, item) { + console.log(item); + } } diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs index 0aaf8eb0..c4a3f747 100644 --- a/module/data/actor/environment.mjs +++ b/module/data/actor/environment.mjs @@ -37,7 +37,7 @@ export default class DhEnvironment extends BaseDataActor { potentialAdversaries: new fields.TypedObjectField( new fields.SchemaField({ label: new fields.StringField(), - adversaries: new ForeignDocumentUUIDArrayField({ type: 'Actor' }) + adversaries: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { required: false, initial: [] }) }) ), notes: new fields.HTMLField() diff --git a/module/data/fields/foreignDocumentUUIDArrayField.mjs b/module/data/fields/foreignDocumentUUIDArrayField.mjs index 0ddd126c..456c0593 100644 --- a/module/data/fields/foreignDocumentUUIDArrayField.mjs +++ b/module/data/fields/foreignDocumentUUIDArrayField.mjs @@ -14,7 +14,7 @@ export default class ForeignDocumentUUIDArrayField extends foundry.data.fields.A /** @inheritdoc */ initialize(value, model, options = {}) { - const v = super.initialize(value, model, options); + const v = super.initialize(value ?? [], model, options); return () => { const data = v.map(entry => (typeof entry === 'function' ? entry() : entry)); return this.options.prune ? data.filter(d => !!d) : data; diff --git a/system.json b/system.json index 48a2319a..14d61851 100644 --- a/system.json +++ b/system.json @@ -4,8 +4,8 @@ "description": "An unofficial implementation of the Daggerheart system", "version": "2.0.0", "compatibility": { - "minimum": "14.354", - "verified": "14.354", + "minimum": "14.355", + "verified": "14.355", "maximum": "14" }, "authors": [ From 37c53ad74e2ae7cdc746fc35148308cb07f05b5d Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 25 Feb 2026 22:45:55 +0100 Subject: [PATCH 017/304] Fixed Template range texts --- daggerheart.mjs | 2 ++ lang/en.json | 4 ++-- module/canvas/placeables/_module.mjs | 1 + module/canvas/placeables/region.mjs | 12 ++++++++++ module/config/generalConfig.mjs | 4 ++-- module/enrichers/TemplateEnricher.mjs | 34 +++++++++++++++++++-------- 6 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 module/canvas/placeables/region.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index b5da2c3e..89c74e00 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -59,6 +59,8 @@ CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer; CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate; +CONFIG.Region.objectClass = placeables.DhRegion; + CONFIG.RollTable.documentClass = documents.DhRollTable; CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs'; diff --git a/lang/en.json b/lang/en.json index 24d6168a..cc5c778d 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1222,8 +1222,8 @@ "cone": "Cone", "emanation": "Emanation", "inFront": "In Front", - "rect": "Rectangle", - "ray": "Ray" + "rectangle": "Rectangle", + "line": "Line" }, "TokenSize": { "tiny": "Tiny", diff --git a/module/canvas/placeables/_module.mjs b/module/canvas/placeables/_module.mjs index 6ba047fb..8746aabd 100644 --- a/module/canvas/placeables/_module.mjs +++ b/module/canvas/placeables/_module.mjs @@ -1,5 +1,6 @@ export { default as DhMeasuredTemplate } from './measuredTemplate.mjs'; export { default as DhRuler } from './ruler.mjs'; +export { default as DhRegion } from './region.mjs'; export { default as DhRegionLayer } from './regionLayer.mjs'; export { default as DhTokenPlaceable } from './token.mjs'; export { default as DhTokenRuler } from './tokenRuler.mjs'; diff --git a/module/canvas/placeables/region.mjs b/module/canvas/placeables/region.mjs new file mode 100644 index 00000000..7ad94b2f --- /dev/null +++ b/module/canvas/placeables/region.mjs @@ -0,0 +1,12 @@ +import DhMeasuredTemplate from './measuredTemplate.mjs'; + +export default class DhRegion extends foundry.canvas.placeables.Region { + /**@inheritdoc */ + _formatMeasuredDistance(distance) { + const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement; + if (!range.enabled) return super._formatMeasuredDistance(distance); + + const { distance: resultDistance, units } = DhMeasuredTemplate.getRangeLabels(distance, range); + return `${resultDistance} ${units}`; + } +} diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 165342b4..685b725f 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -74,8 +74,8 @@ export const range = { export const templateTypes = { CIRCLE: 'circle', CONE: 'cone', - RECTANGLE: 'rect', - RAY: 'ray', + RECTANGLE: 'rectangle', + LINE: 'line', EMANATION: 'emanation', INFRONT: 'inFront' }; diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index ab3d406f..fd0e78eb 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -49,6 +49,8 @@ export default function DhTemplateEnricher(match, _options) { } export const renderMeasuredTemplate = async event => { + const { LINE, RECTANGLE, INFRONT, CONE } = CONFIG.DH.GENERAL.templateTypes; + const button = event.currentTarget, type = button.dataset.type, range = button.dataset.range, @@ -57,13 +59,9 @@ export const renderMeasuredTemplate = async event => { if (!type || !range || !game.canvas.scene) return; - const usedType = type === 'inFront' ? 'cone' : type === 'emanation' ? 'circle' : type; + const usedType = type === 'inFront' ? 'cone' : type; const usedAngle = - type === CONFIG.DH.GENERAL.templateTypes.CONE - ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) - : type === CONFIG.DH.GENERAL.templateTypes.INFRONT - ? '180' - : undefined; + type === CONE ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) : type === INFRONT ? '180' : undefined; let baseDistance = range; if (Number.isNaN(Number(range))) { @@ -71,16 +69,32 @@ export const renderMeasuredTemplate = async event => { range ]; } - const distance = type === CONFIG.DH.GENERAL.templateTypes.EMANATION ? baseDistance + 2.5 : baseDistance; - const radius = (distance / game.scenes.active.grid.distance) * game.scenes.active.grid.size; + + const dimensionConstant = game.scenes.active.grid.size / game.scenes.active.grid.distance; + + baseDistance *= dimensionConstant; + + const length = baseDistance; + const radius = length; + + const shapeWidth = type === LINE ? 5 * dimensionConstant : type === RECTANGLE ? length : undefined; const { width, height } = game.canvas.scene.dimensions; const shapeData = { x: width / 2, y: height / 2, + base: { + type: 'token', + x: 0, + y: 0, + width: 1, + height: 1, + shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1 + }, t: usedType, - distance: distance, - width: type === CONFIG.DH.GENERAL.templateTypes.RAY ? 5 : undefined, + length: length, + width: shapeWidth, + height: length, angle: usedAngle, radius: radius, direction: direction, From c2807e0676027bc13fe6aa4fc89cff8b8083d880 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 7 Mar 2026 18:02:01 +0100 Subject: [PATCH 018/304] Added in the new v14 subtract changeMode type for ActiveEffects --- module/config/generalConfig.mjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 8ec7034a..3b87c15d 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -870,6 +870,11 @@ export const activeEffectModes = { priority: 20, label: 'EFFECT.CHANGES.TYPES.add' }, + subtract: { + id: 'subtract', + priority: 20, + label: 'EFFECT.CHANGES.TYPES.subtract' + }, downgrade: { id: 'downgrade', priority: 30, From d3ebd30e59b9678469f05f6c31ed28b0c8835977 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 7 Mar 2026 22:26:54 +0100 Subject: [PATCH 019/304] . --- module/applications/ux/contextMenu.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/ux/contextMenu.mjs b/module/applications/ux/contextMenu.mjs index 081e6ba0..6e70da0c 100644 --- a/module/applications/ux/contextMenu.mjs +++ b/module/applications/ux/contextMenu.mjs @@ -86,7 +86,7 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu { const element = event.target.closest('.context-item'); if (!element) return; const item = this.menuItems.find(i => i.element === element); - item?.callback(this.#jQuery ? $(this.target) : this.target, event); + item?.onClick(event, this.#jQuery ? $(this.target) : this.target); this.close(); } From 5a4bbc91f554c0aac57110ebf95e2300fc2c035f Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:58:24 +0100 Subject: [PATCH 020/304] [Feature] Damage Iterrable Rework (#1685) * Initial * More * Fixed current actionConfig damage * Reworked ActionConfig damage ui * . * Updated all Adversary compendium damage entries * more * The rest * Fixed misses * . * . * Also migrate sub fields of MappingField * Removed MappingField * Fix regression with re-tiering adversaries when dealing non-hp damage * Allow iterable object to be detected as an object by foundry --------- Co-authored-by: Carlos Fernandez --- .../applications/levelup/companionLevelup.mjs | 2 +- .../sheets-configs/action-base-config.mjs | 63 +++++++++++++++--- .../sheets/api/application-mixin.mjs | 5 +- module/config/generalConfig.mjs | 60 ++++++++--------- module/config/itemConfig.mjs | 18 ++--- module/data/action/attackAction.mjs | 10 +-- module/data/action/baseAction.mjs | 13 +++- module/data/actor/adversary.mjs | 20 +++--- module/data/actor/character.mjs | 8 +-- module/data/actor/companion.mjs | 10 +-- module/data/fields/_module.mjs | 1 + module/data/fields/action/damageField.mjs | 3 +- .../data/fields/iterableTypedObjectField.mjs | 32 +++++++++ module/data/item/weapon.mjs | 6 +- module/helpers/handlebarsHelper.mjs | 4 +- module/helpers/utils.mjs | 13 ++++ ...ersary_Acid_Burrower_89yAh30vaNQOALlz.json | 28 ++++---- ...ary_Adult_Flickerfly_G7jiltRjgvVhZewm.json | 22 +++---- ..._Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json | 6 +- ...ary_Arch_Necromancer_WPEOIGfclNJxWb87.json | 28 ++++---- ...versary_Archer_Guard_JRhrrEg5UroURiAD.json | 12 ++-- ...sary_Archer_Squadron_0ts6CGd93lLqGZI5.json | 18 ++--- ...ry_Assassin_Poisoner_h5RuhzGL17dW5FBT.json | 6 +- ...adversary_Battle_Box_dgH3fW9FTYLaIDvS.json | 52 +++++++-------- .../adversary_Bear_71qKDLKO3CsrNkdy.json | 18 ++--- ...versary_Bladed_Guard_B4LZcGuBAHzyVdzy.json | 10 +-- ...ersary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json | 18 ++--- .../adversary_Cave_Ogre_8Zkqk1jU09nKL2fy.json | 24 +++---- ...dversary_Chaos_Skull_jDmHqGvzg5wjgmxE.json | 18 ++--- .../adversary_Conscript_99TqczuQipBmaB8i.json | 6 +- .../adversary_Construct_uOP5oT9QzXPlnf3p.json | 18 ++--- .../adversary_Courtesan_ZxWaWPdzFIUPNC62.json | 12 ++-- .../adversary_Courtier_CBBuEXAlLKFMJdjg.json | 12 ++-- ...adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json | 20 +++--- .../adversary_Cult_Fang_tyBOpLfigAhI9bU3.json | 12 ++-- ...ersary_Cult_Initiate_zx99sOGTXicP4SSD.json | 6 +- ...ry_Deeproot_Defender_9x2xY9zwc3xzbXo5.json | 18 ++--- ...ary_Demon_of_Avarice_pnyjIGxxvurcWmTv.json | 6 +- ...ary_Demon_of_Despair_kE4dfhqmIQpNd44e.json | 18 ++--- ...sary_Demon_of_Hubris_2VN3BftageoTTIzu.json | 30 ++++----- ...ry_Demon_of_Jealousy_SxSOkM4bcVOFyjbo.json | 8 +-- ...rsary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json | 12 ++-- ...y_Demonic_Hound_Pack_NoRZ1PqB8N5wcIw0.json | 18 ++--- .../adversary_Dire_Bat_tBWHW00epmMnkawe.json | 18 ++--- .../adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json | 18 ++--- .../adversary_Dryad_wR7cFKrHvRzbzhBT.json | 22 +++---- ...ersary_Electric_Eels_TLzY1nDw0Bu9Ud40.json | 12 ++-- ...sary_Elemental_Spark_P7h54ZePFPHpYwvB.json | 6 +- ...ersary_Elite_Soldier_bfhVWMBUh61b9J6n.json | 12 ++-- ...ry_Failed_Experiment_ChwwVqowFw8hJQwT.json | 6 +- ...y_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json | 12 ++-- ...sary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json | 18 ++--- ...rlord__Realm_Breaker_hxZ0sgoFJubh5aj6.json | 26 ++++---- ..._Undefeated_Champion_RXkZTwBRi4dJ3JE5.json | 26 ++++---- ...ry_Giant_Beastmaster_8VZIgU12cB3cvlyH.json | 12 ++-- ...ersary_Giant_Brawler_YnObCleGjPT7yqEc.json | 24 +++---- ...dversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json | 24 +++---- ...ary_Giant_Mosquitoes_IIWV4ysJPFPnTP7W.json | 6 +- .../adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json | 8 +-- ...ersary_Giant_Recruit_5s8wSvpyC5rxY5aD.json | 6 +- ...rsary_Giant_Scorpion_fmfntuJ8mHRCAktP.json | 24 +++---- ...dversary_Glass_Snake_8KWVLWXFhlY2kYx0.json | 26 ++++---- .../adversary_Gorgon_8mJYMpbLTb8qIOrr.json | 22 +++---- ...ater_Earth_Elemental_dsfB3YhoL5SudvS2.json | 24 +++---- ...ater_Water_Elemental_xIICT6tEdnA7dKDV.json | 14 ++-- ...adversary_Green_Ooze_SHXedd9zZPVfUgUa.json | 20 +++--- ...sary_Hallowed_Archer_kabueAo6BALApWqp.json | 12 ++-- ...ary_Hallowed_Soldier_VENwg7xEFcYObjmT.json | 6 +- .../adversary_Harrier_uRtghKE9mHlII4rs.json | 12 ++-- ...adversary_Head_Guard_mK3A5FTx6k8iPU3F.json | 14 ++-- ...versary_Head_Vampire_i2UNbRvgyoSs07M6.json | 26 ++++---- ...dversary_High_Seraph_r1mbfSSwKWdcFdAU.json | 12 ++-- ...sary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json | 18 ++--- .../adversary_Hydra_MI126iMOOobQ1Obn.json | 18 ++--- ..._Jagged_Knife_Bandit_5Lh1T0zaT8Pkr2U2.json | 12 ++-- ...y_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json | 12 ++-- ...ed_Knife_Kneebreaker_CBKixLH3yhivZZuL.json | 8 +-- ..._Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json | 6 +- ...ged_Knife_Lieutenant_aTljstqteGoLpCBq.json | 20 +++--- ..._Jagged_Knife_Shadow_XF4tYTq9nPJAy2ox.json | 12 ++-- ..._Jagged_Knife_Sniper_1zuyof1XuIfi3aMG.json | 12 ++-- ..._Juvenile_Flickerfly_MYXmTx2FHcIjdfYZ.json | 18 ++--- ..._Knight_of_the_Realm_7ai2opemrclQe3VF.json | 14 ++-- .../adversary_Kraken_4nqv3ZwJGjnmic8j.json | 20 +++--- ...versary_Masked_Thief_niBpVU7yeo5ccskE.json | 14 ++-- ...sary_Master_Assassin_dNta0cUzr96xcFhf.json | 18 ++--- .../adversary_Merchant_Al3w2CgjfdT3p9ma.json | 12 ++-- ...rsary_Merchant_Baron_Vy02IhGhkJLuezu4.json | 12 ++-- ...inor_Chaos_Elemental_sRn4bqerfARvhgSV.json | 24 +++---- ...dversary_Minor_Demon_3tqCjDwJAQ7JKqMb.json | 24 +++---- ...Minor_Fire_Elemental_DscWkNVoHak6P4hh.json | 36 +++++----- ...versary_Minor_Treant_G62k4oSkhkoXEs2D.json | 6 +- ...ary_Minotaur_Wrecker_rM9qCIYeWg9I0B4l.json | 24 +++---- .../adversary_Monarch_yx0vK2yfNVZKWUUi.json | 12 ++-- ...ersary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json | 26 ++++---- ...adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json | 12 ++-- ...rsary_Oracle_of_Doom_befIqd5IYKg6eUz2.json | 32 ++++----- ...r_Realms_Abomination_A0SeeDzwjvqOsyof.json | 12 ++-- ...ter_Realms_Corrupter_ms6nuOl3NFkhPj1k.json | 18 ++--- ..._Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json | 6 +- ...atchwork_Zombie_Hulk_EQTOAOUrkIvS2z88.json | 26 ++++---- ...ary_Perfected_Zombie_CP6iRfHdyFWniTHY.json | 18 ++--- ...dversary_Petty_Noble_wycLpvebWdUqRhpP.json | 6 +- ...rsary_Pirate_Captain_OROJbjsqagVh7ECV.json | 24 +++---- ...rsary_Pirate_Raiders_5YgEajn0wa4i85kC.json | 12 ++-- ...versary_Pirate_Tough_mhcVkVFrzIJ18FDm.json | 18 ++--- .../adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json | 18 ++--- ...ersary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json | 6 +- ...ersary_Royal_Advisor_EtLJiTsilPPZvLUX.json | 12 ++-- ...ersary_Secret_Keeper_sLAccjvCWfeedbpI.json | 14 ++-- .../adversary_Sellsword_bgreCaQ6ap2DVpCr.json | 6 +- ...ary_Shambling_Zombie_2nXz4ilAY4xuhKLm.json | 12 ++-- .../adversary_Shark_YmVAkdNsyuXWTtYp.json | 20 +++--- .../adversary_Siren_BK4jwyXSRx7IOQiO.json | 14 ++-- ...sary_Skeleton_Archer_7X5q7a6ueeHs5oA9.json | 12 ++-- ...sary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json | 6 +- ...sary_Skeleton_Knight_Q9LaVTyXF9NF12C7.json | 26 ++++---- ...ary_Skeleton_Warrior_10YIQl0lvCJXZLfX.json | 8 +-- ...sary_Spectral_Archer_5tCkhnBByUIN5UdG.json | 12 ++-- ...ary_Spectral_Captain_65cSO3EQEh6ZH6Xk.json | 12 ++-- ...ry_Spectral_Guardian_UFVGl1osOsJTneLf.json | 14 ++-- ...adversary_Spellblade_ldbWEL7uZs84vyrR.json | 18 ++--- .../adversary_Spy_8zlynOhnVA59KpKT.json | 12 ++-- ...dversary_Stag_Knight_KGVwnLq85ywP9xvB.json | 20 +++--- ...dversary_Stonewraith_3aAS2Qm3R6cgaYfE.json | 24 +++---- ...ersary_Swarm_of_Rats_qNgs3AbLyJrY19nt.json | 6 +- ...rsary_Sylvan_Soldier_VtFBt9XBE0WrGGxP.json | 18 ++--- ...Tangle_Bramble_Swarm_PKSXFuaIHUCoH63A.json | 12 ++-- ...rsary_Tangle_Bramble_XcAGOSmtCFLT1unN.json | 6 +- ...sary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json | 12 ++-- ...ersary_Tiny_Red_Ooze_1fkLQXVtmILqfJ44.json | 12 ++-- ...rsary_Treant_Sapling_o63nS0k3wHu6EgKP.json | 6 +- .../adversary_Vampire_WWyUp6Mxl1S3KYUG.json | 16 ++--- ...ault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json | 8 +-- ...lt_Guardian_Sentinel_FVgYb28fhxlVcGwA.json | 18 ++--- ...ault_Guardian_Turret_c5hGdvY5UnSjlHws.json | 12 ++-- ...Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json | 26 ++++---- ...agon__Molten_Scourge_eArAPuB38CNR0ZIM.json | 34 +++++----- ...n__Obsidian_Predator_ladm7wykhZczYzrQ.json | 22 +++---- ...adversary_War_Wizard_noDdT0tsN6FXSmC8.json | 24 +++---- ...versary_Weaponmaster_ZNbQ2jg35LG4t9eH.json | 26 ++++---- ...dversary_Young_Dryad_8yUj2Mzvnifhxegm.json | 14 ++-- ...ary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json | 32 ++++----- ...ersary_Zombie_Legion_YhJrP7rTBiRdX5Fp.json | 6 +- ...dversary_Zombie_Pack_Nf0v43rtflV56V2T.json | 6 +- .../feature_Charge_AA2CZlJSWW8GPhrR.json | 6 +- ...ure_Elemental_Breath_sRaE3CgkgjBF1UpV.json | 6 +- ...ture_Fungril_Network_9tmeXm623hl4Qnws.json | 2 +- .../feature_Kick_gpW19TfJk0WWFh1S.json | 6 +- .../feature_Long_Tongue_oWbdlh51ajn1Q5kL.json | 6 +- .../feature_Luckbringer_8O6SQQMxKWr430QA.json | 6 +- ...ure_Retracting_Claws_Zj69cAeb3NjIa8Hn.json | 2 +- .../feature_Tusks_YhxD1ujZpftPu19w.json | 6 +- .../feature_Unshakeable_G5pE8FW94V1W9jJx.json | 2 +- .../feature_Demolish_DfBXO8jTchwFG8dZ.json | 6 +- ...feature_Elusive_Prey_a7Qvmm14nx9BCysA.json | 2 +- .../feature_Takedown_0ey4kM9ssj2otHvb.json | 6 +- .../feature_Trample_A0lgd6eVEfX6oqSB.json | 6 +- .../feature_Unyielding_vEAQ4cfsoPmOv2Gg.json | 2 +- ...ture_Venomous_Strike_uW3853pViM9VAfHb.json | 2 +- ...feature_Vicious_Maul_jYUBi7yLHap5ljpa.json | 6 +- .../feature_Webslinger_D73fS1iM4SZPFimu.json | 2 +- ...ature_Frontline_Tank_YS1g7YdWwOaS629x.json | 6 +- ...feature_Life_Support_lSlvSUHbOoX36q2j.json | 6 +- ...ature_Minor_Illusion_cshTYdtz9yoXYYB3.json | 2 +- ...ure_Strange_Patterns_6YsfFjmCGuFYVhT4.json | 6 +- ...ard_A_Soldier_s_Bond_Y08dLFuPXsgeRrHi.json | 6 +- ...rd_Arcane_Reflection_JzSvxy9Mu3RJp1jV.json | 2 +- .../domainCard_Armorer_cy8GjBPGc9w9RaGO.json | 6 +- .../domainCard_Banish_AIbHfryMA2Rvs1ut.json | 2 +- ...omainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json | 8 +-- ...Card_Battle_Hardened_NeEOghgfyDUBTwBG.json | 6 +- ...domainCard_Blink_Out_Qu0iA4s3Xov10Erd.json | 2 +- ...mainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json | 6 +- ...mainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json | 14 ++-- ...inCard_Book_of_Exota_oVs2MSC6Uf5GbgEG.json | 8 +-- ...inCard_Book_of_Grynn_R0LNheiZycZlZzV3.json | 6 +- ...inCard_Book_of_Homet_gFMx08ogQ8hS2Obi.json | 4 +- ...nCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json | 8 +-- ...nCard_Book_of_Korvax_cWRFHJdxEZ0M1dAg.json | 10 +-- ...inCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json | 12 ++-- ...inCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json | 4 +- ...inCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json | 2 +- ...inCard_Book_of_Tyfar_1VXzwRbvbBj5bd5V.json | 10 +-- ...nCard_Book_of_Vagras_aknDDYtN7EObv94t.json | 6 +- ...inCard_Book_of_Vyola_VOIgm2j2Ijszwc5m.json | 2 +- ...nCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json | 2 +- ...Card_Chain_Lightning_0kAVO6rordCfZqYP.json | 12 ++-- ...Card_Champion_s_Edge_rnejRbUQsNGX1GMC.json | 18 ++--- ...domainCard_Chokehold_R5GYUalYXLLFRlNl.json | 2 +- ...ainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json | 12 ++-- ...nCard_Confusing_Aura_R8NDiJXJWmC48WSr.json | 4 +- ...inCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json | 8 +-- ...Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json | 6 +- ...ainCard_Counterspell_6dhqo1kzGxejCjHa.json | 2 +- ...Critical_Inspiration_ABp9pUfBS69NomTD.json | 2 +- ...inCard_Dark_Whispers_yL2qrSWmTwXVOySH.json | 2 +- ...omainCard_Death_Grip_x0FVGE1YbfXalJiw.json | 14 ++-- ..._Disintegration_Wave_kja5qvh4rdeDBB96.json | 2 +- ...omainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json | 6 +- .../domainCard_Eclipse_62Sj67PdPFzwWVe3.json | 8 +-- .../domainCard_Encore_klahWDFwihqqEhXP.json | 2 +- ...domainCard_Enrapture_a8lFiKX1o8T924ze.json | 8 +-- ...mainCard_Falling_Sky_hZJp9mdkMnqKDROe.json | 6 +- ...mainCard_Final_Words_Nbw6Jnh1vRZzwHQI.json | 2 +- .../domainCard_Flight_54GUjNuBEy7xdzMz.json | 2 +- .../domainCard_Forager_06UapZuaA5S6fAKl.json | 18 ++--- ...nCard_Forest_Sprites_JrkUMTzaFmQNBHVm.json | 2 +- ...inCard_Glancing_Blow_nCNCqSH7UgW4O3To.json | 2 +- ...d_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json | 2 +- ...ainCard_Goad_Them_On_HufF5KzuNfEb9RTi.json | 6 +- ...nCard_Gore_and_Glory_3zvjgZ5Od343wHzx.json | 12 ++-- ...inCard_Grace_Touched_KAuNb51AwhD8KEXk.json | 6 +- ...ainCard_Ground_Pound_WnGldYhJPDhx8v9X.json | 6 +- ...inCard_Healing_Field_GlRm1Dxlc0Z1b04o.json | 12 ++-- ...inCard_Healing_Hands_WTlhnQMajc1r8i50.json | 26 ++++---- ...nCard_Healing_Strike_XtSc0jIJLOoMTMYS.json | 6 +- .../domainCard_Hush_gwmYasmfgXZ7tFS6.json | 2 +- ...ard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json | 6 +- ...Card_I_See_It_Coming_Kp6RejHGimnuoBom.json | 2 +- ..._Inspirational_Words_cWu1o82ZF7GvnbXc.json | 18 ++--- ...ainCard_Invigoration_X8OfkEoI5gLTRf1B.json | 2 +- ...ainCard_Invisibility_KHkzA4Zrw8EWN1CH.json | 2 +- ...nCard_Know_Thy_Enemy_O38MQMhJWdZnXi6b.json | 2 +- ...omainCard_Lean_on_Me_BdePs1ZWpZTZvY1Z.json | 6 +- ...inCard_Manifest_Wall_TtGOtWkbr23VhHfH.json | 2 +- ...nCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json | 8 +-- ...inCard_Mending_Touch_TGjR4vJVNbQRV8zr.json | 24 +++---- ...Card_Midnight_Spirit_FXLsB3QbQvTtqX5B.json | 6 +- ...ard_Midnight_Touched_uSyGKVxOJcnp28po.json | 6 +- ...ard_Natural_Familiar_Tag303LoRNC5zGgl.json | 2 +- ...Card_Nature_s_Tongue_atWLorlCOxcrq8WB.json | 2 +- ...ainCard_Night_Terror_zcldCuqOg3dphUVI.json | 8 +-- ...domainCard_Onslaught_I7pNsQ9Yx6mRJX4V.json | 2 +- ...rd_Overwhelming_Aura_iEBLySZD9z8CLdz7.json | 2 +- ...nCard_Plant_Dominion_9a6xP5pxhVvdugk9.json | 2 +- ...d_Preservation_Blast_1p1cOmbnRd5CoKBp.json | 6 +- ...nCard_Rain_of_Blades_Ucenef6JpjQxwXni.json | 12 ++-- ...Card_Reaper_s_Strike_MCgNRlh0s5XUPCfl.json | 2 +- ...Rejuvenation_Barrier_HtWx5IIemCoorMj2.json | 6 +- ...mainCard_Restoration_wUQFsRtww18naYaq.json | 12 ++-- ...ainCard_Resurrection_z30ciOwQI7g3tHla.json | 4 +- ...mainCard_Rift_Walker_vd5STqX29RpYbGxa.json | 2 +- .../domainCard_Rise_Up_oDIZoC4l19Nli0Fj.json | 6 +- ...domainCard_Rune_Ward_GEhBUmv9Bj7oJfHk.json | 2 +- ...nCard_Salvation_Beam_4uAFGp3LxiC07woC.json | 6 +- ...mainCard_Second_Wind_ffPbSEvLuFrFsMxl.json | 12 ++-- ...d_Sensory_Projection_gZOMzskSOfeiXn54.json | 2 +- ...omainCard_Shadowbind_kguhWlidhxe2GbT0.json | 2 +- ...ainCard_Shrug_It_Off_JwfhtgmmuRxg4zhI.json | 2 +- ...Card_Soothing_Speech_QED2PDYePOSTbLtC.json | 12 ++-- ...d_Splintering_Strike_TYKfM3H9vBXyWiH4.json | 2 +- ...rd_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json | 12 ++-- ...omainCard_Swift_Step_H6TqCJBaa1eWEQ1z.json | 12 ++-- ...mainCard_Telekinesis_FgzBppvLjXr0UbUI.json | 8 +-- .../domainCard_Teleport_HnPwVrWblYa9hwSt.json | 2 +- ...ainCard_Tell_No_Lies_HTv9QEPS466WsstP.json | 6 +- .../domainCard_Tempest_X7YaZgFieBlqaPdZ.json | 18 ++--- ...nCard_Thought_Delver_B4choj481tqajWb9.json | 2 +- ...nCard_Towering_Stalk_n0P3VS1WfxvmXbB6.json | 6 +- ...ainCard_Troublemaker_JrdZedm1BFKeV7Yb.json | 6 +- ...inCard_Twilight_Toll_SDjjV61TC1NceV1m.json | 6 +- ...mainCard_Unbreakable_CUIQmrPjf9VCHmwJ.json | 2 +- ...ard_Uncanny_Disguise_TV56wSysbU5xAlOa.json | 2 +- ...inCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json | 6 +- ...inCard_Valor_Touched_k1AtYd3lSchIymBr.json | 6 +- ...inCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json | 2 +- ...ard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json | 6 +- ...inCard_Wild_Fortress_9dFvcM1i3bxG3BSA.json | 2 +- ...ard_Words_of_Discord_ZjAdi1FSNCDDHI3X.json | 6 +- .../domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json | 2 +- ...d_Zone_of_Protection_lOZaRb4fCVgQsWB5.json | 2 +- ...ment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json | 6 +- ...environment_Ambushed_uGEdNYERCTJBEjc5.json | 6 +- ...g_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json | 14 ++-- ...Bustling_Marketplace_HZKA7hkej7JJY503.json | 2 +- ...ronment_Castle_Siege_1eZ32Esq7rfZOjlu.json | 12 ++-- ...ironment_Chaos_Realm_2Z1mKc65LxNk2PqR.json | 20 +++--- ...ent_Cliffside_Ascent_LPpfdlNKqiZIl04w.json | 24 +++---- ...nt_Divine_Usurpation_4DLYez7VbMCFDAuZ.json | 12 ++-- ...ment_Hallowed_Temple_dsA6j69AnaJhUyqH.json | 12 ++-- ...nment_Imperial_Court_jr1xAoXzVwVblzxI.json | 10 +-- ...ronment_Local_Tavern_cM4X81DOyvxNIi52.json | 6 +- ...onment_Mountain_Pass_acMu9wJrMZZzLSTJ.json | 12 ++-- ...ecromancer_s_Ossuary_h3KyRL7AshhLAmcH.json | 14 ++-- ...nment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json | 6 +- ...ronment_Raging_River_t4cdqTfzcqP3H1vJ.json | 6 +- ...or_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json | 2 +- ...mor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json | 6 +- ...mor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json | 2 +- ...able_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json | 6 +- ...able_Dripfang_Poison_eU8VpbWB2NHIL47n.json | 6 +- ...mable_Feast_of_Xuria_aX6NyxkNzu0LcJpt.json | 10 +-- ...umable_Health_Potion_Aruc2NLutWuVIjP1.json | 6 +- ...mproved_Arcane_Shard_nQTo6mNoPTEVBtkm.json | 6 +- ...e_Jar_of_Lost_Voices_yUol6M5b8jsbk9za.json | 6 +- ...e_Major_Arcane_Shard_AA7bmiwv00lshPrC.json | 6 +- ..._Major_Health_Potion_cM7pHe8bBAxSZ2xR.json | 6 +- ...Major_Stamina_Potion_I4cQ03xbxnc81EGa.json | 6 +- ..._Minor_Health_Potion_tPfKtKRRjv8qdSqy.json | 6 +- ...Minor_Stamina_Potion_b6vGSPFWOlzZZDLO.json | 6 +- ...sumable_Sleeping_Sap_XZavUVlHEvE2srEt.json | 6 +- ...nsumable_Snap_Powder_cg6VtQ0eVZjDdcK0.json | 6 +- ...mable_Stamina_Potion_hf3k1POoVSooJyN2.json | 6 +- .../consumable_Stardrop_y4c1jrlHrf0wBWOq.json | 6 +- ...sumable_Sun_Tree_Sap_kwexUzdM9wm1Qums.json | 2 +- ...onsumable_Sweet_Moss_GrDrRqWgv7gvl9vn.json | 12 ++-- ...nstable_Arcane_Shard_mUepnLbkvFk0ha4Z.json | 6 +- ...sumable_Varik_Leaves_hvy5BkG3F6iOIXTx.json | 6 +- ...ot_Bag_of_Ficklesand_v758j4FwNVAurhYK.json | 2 +- ...ot_Box_of_Many_Goods_bZyT7Qw7iafswlTY.json | 2 +- ...loot_Calming_Pendant_tgFFMxpuRSiRrrEB.json | 2 +- .../loot_Skeleton_Key_edkNgwy4xghZreBa.json | 2 +- .../loot/loot_Woven_Net_ARuv48PWUGJGBC4n.json | 2 +- .../weapon_Aantari_Bow_ijodu5yNBoMxpkHV.json | 6 +- ...ane_Frame_Wheelchair_la3sAWgnvadc4NvP.json | 6 +- ...ced_Arcane_Gauntlets_hXR56fTKwZ6s1obs.json | 6 +- ...n_Advanced_Battleaxe_FcbvY1ydbNVMjKvk.json | 6 +- ..._Advanced_Broadsword_WtQAGz0TUgz8Xg70.json | 6 +- ...on_Advanced_Crossbow_3HGs0AgVrdIBTaKG.json | 6 +- ...pon_Advanced_Cutlass_bw9WO9lxkM9bWZxQ.json | 6 +- ...apon_Advanced_Dagger_mrioysDjNQEIE8hN.json | 6 +- ...n_Advanced_Dualstaff_X5x3sC7v2f3L9sjL.json | 6 +- ...vanced_Glowing_Rings_InQoh8mZPnwarQkX.json | 6 +- ...on_Advanced_Grappler_7vvhVl4TDJHtjpFK.json | 6 +- ..._Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json | 6 +- ..._Advanced_Greatsword_MAC6YWTo4lzSotQc.json | 6 +- ...pon_Advanced_Halberd_C8gQn7onAc9wsrCs.json | 6 +- ...dvanced_Hallowed_Axe_BiyXKX2Mo1TQbKgk.json | 6 +- ...vanced_Hand_Crossbow_Lsvocst8aGqkBj7g.json | 6 +- ..._Advanced_Hand_Runes_PQACczSghZIVTdgZ.json | 6 +- ...avy_Frame_Wheelchair_eT2Qwb0RdrLX2hH1.json | 6 +- ...ght_Frame_Wheelchair_BuMfupnCzHbziQ8o.json | 6 +- ...pon_Advanced_Longbow_M5CywMAyPKGgebsJ.json | 6 +- ...n_Advanced_Longsword_9xkB3MWXahrsVP4N.json | 6 +- ...weapon_Advanced_Mace_WreMYiH5uhVDaoVw.json | 6 +- ...dvanced_Quarterstaff_zJtm2f9ZFKZRtCRg.json | 6 +- ...apon_Advanced_Rapier_KxFne76d7cak15dO.json | 6 +- ...nced_Returning_Blade_sIGXA4KMeYBUjcEO.json | 6 +- ...dvanced_Round_Shield_hiEOGF2reabGLUoi.json | 6 +- ...pon_Advanced_Scepter_2Khzuj768yoWN9QK.json | 12 ++-- ...on_Advanced_Shortbow_JpSlJvDR0X8VFDns.json | 6 +- ..._Advanced_Shortstaff_T5exRCqOXhrjSYnI.json | 6 +- ..._Advanced_Shortsword_p3nz5CaGUoyuGVg0.json | 6 +- ...dvanced_Small_Dagger_0thN0BpN05KT8Avx.json | 6 +- ...eapon_Advanced_Spear_pK6dsNABKKp1CIGN.json | 6 +- ...dvanced_Tower_Shield_OfOzQbs4hg6QbfTG.json | 6 +- ...weapon_Advanced_Wand_jU9jWIardjtdAQcs.json | 6 +- ...n_Advanced_Warhammer_8Lipw3RRKDgBVP0p.json | 6 +- ...weapon_Advanced_Whip_01izMUSJcAUo79IX.json | 6 +- ...ane_Frame_Wheelchair_XRChepscgr75Uug7.json | 6 +- ...pon_Arcane_Gauntlets_PC5EyEIq7NWBV0n5.json | 6 +- ...apon_Axe_of_Fortunis_YcS1rHgfnSlla8Xf.json | 6 +- .../weapon_Battleaxe_fbDYUja3ll9vCtrB.json | 6 +- ...lack_Powder_Revolver_AokqTusPzn0hghkE.json | 6 +- .../weapon_Bladed_Whip_5faflfNz20cFW1EM.json | 6 +- ...eapon_Blessed_Anlace_n1oPTk5czTIGTkVj.json | 12 ++-- .../weapon_Bloodstaff_IoMVDz92WVvfGGdc.json | 6 +- .../weapon_Blunderbuss_SLFrK0WmldPo0shz.json | 6 +- .../weapon_Braveshield_QEvgVoz9xKBSKsGi.json | 6 +- .../weapon_Bravesword_QZrWAkprA2tL2MOI.json | 6 +- .../weapon_Broadsword_1cwWNt4sqlgA8gCT.json | 6 +- .../weapon_Buckler_EmFTp9wzT6MHSaNz.json | 6 +- ...weapon_Casting_Sword_2Fbf2cxLfbdGkU4I.json | 12 ++-- .../weapon_Crossbow_cw7HG1Z7hp7OOLD0.json | 6 +- ...weapon_Curved_Dagger_Fk69R40svV0kanZD.json | 6 +- .../weapon_Cutlass_CWrbnethuILXrEpA.json | 6 +- .../weapon_Dagger_iStO0BbeMTTR0rQi.json | 6 +- ...pon_Devouring_Dagger_C5wSGglR8e0euQnY.json | 6 +- .../weapon_Double_Flail_xm1yU7k58fMgXxRR.json | 6 +- ...pon_Dual_Ended_Sword_nXjuBa215H1sTUK3.json | 6 +- .../weapon_Dualstaff_j8cdNeIUYxxzFVji.json | 6 +- .../weapon_Ego_Blade_G7rH31KQ5eEZXcv0.json | 6 +- .../weapon_Elder_Bow_JdWcn9W1edhAEInL.json | 6 +- ...pon_Extended_Polearm_fJHKMxZokVP34MCi.json | 6 +- .../weapon_Finehair_Bow_ykF3jouxHZ6YR8Bg.json | 6 +- .../weapon_Firestaff_BtCm2RhWEfs00g38.json | 6 +- ...pon_Flickerfly_Blade_xLJ5RRpUoTRmAC3G.json | 6 +- ...Floating_Bladeshards_3vti3xfo0wJND7ew.json | 6 +- ...weapon_Fusion_Gloves_uK1RhtYAsDeoPNGx.json | 6 +- .../weapon_Ghostblade_6gFvOFTE97QZ74Zr.json | 6 +- .../weapon_Gilded_Bow_ctTgFfMbM3YtmsYU.json | 6 +- ...apon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json | 6 +- ...weapon_Glowing_Rings_wG9f5NpCwSbaLy8t.json | 6 +- .../weapon_Grappler_iEzPscUc18GuFoB6.json | 6 +- .../weapon_Greatbow_MXBpbqQsZFln4rZk.json | 6 +- .../weapon_Greatstaff_Yk8pTEmyLLi4095S.json | 6 +- .../weapon_Greatsword_70ysaFJDREwTgvZa.json | 6 +- .../weapon_Halberd_qT7FfmauAumOjJoq.json | 6 +- .../weapon_Hallowed_Axe_Vayg7CnRTFBrunjM.json | 6 +- ...apon_Hammer_of_Exota_0lAkBEUvbM9Osmqb.json | 6 +- ...apon_Hammer_of_Wrath_1R4uzOpWD8bkYRUm.json | 6 +- .../weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json | 6 +- ...weapon_Hand_Crossbow_zxKt6qXe7uZB6ljm.json | 6 +- .../weapon_Hand_Runes_3whiedn0jBMNRdIb.json | 6 +- .../weapon_Hand_Sling_RAIaoMi6iO1PKIlK.json | 12 ++-- ...avy_Frame_Wheelchair_XjPQjhRCH08VUIbr.json | 6 +- ...eapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json | 6 +- ...apon_Impact_Gauntlet_Zg6IutksQVOqAg8K.json | 6 +- ...ane_Frame_Wheelchair_N9P695V5KKlJbAY5.json | 6 +- ...ved_Arcane_Gauntlets_kENTDpa1hr5LDhIT.json | 6 +- ...n_Improved_Battleaxe_nxGUpuHLmuKdKsDC.json | 6 +- ..._Improved_Broadsword_OcKeLJxvmdT81VBc.json | 6 +- ...on_Improved_Crossbow_55NwHIIZHUeKSE3M.json | 6 +- ...pon_Improved_Cutlass_ddRjXnp2vbohu7rJ.json | 6 +- ...apon_Improved_Dagger_ScjTkb9qrndhlk9S.json | 6 +- ...n_Improved_Dualstaff_f7hhHlZ5nL3AhYEM.json | 6 +- ...proved_Glowing_Rings_N5amhkxR1xn3B7r2.json | 6 +- ...on_Improved_Grappler_3T3o9zfe61t22L1H.json | 6 +- ..._Improved_Greatstaff_LCuTrYXi4lhg6LqW.json | 6 +- ..._Improved_Greatsword_FPX4ouDrxXiQ5MDf.json | 6 +- ...pon_Improved_Halberd_F9PETfCQGwczBPif.json | 6 +- ...mproved_Hallowed_Axe_wFOXMN2uiX4j6Gd9.json | 6 +- ...proved_Hand_Crossbow_XEDRkuw3BhMoVBn9.json | 6 +- ..._Improved_Hand_Runes_jMEukC3VpNDz5AOD.json | 6 +- ...avy_Frame_Wheelchair_L5KeCtrs768PmYWW.json | 6 +- ...ght_Frame_Wheelchair_ZJsetdHKV77ygtCE.json | 6 +- ...pon_Improved_Longbow_NacNonjbzyoVMNhI.json | 6 +- ...n_Improved_Longsword_QyBZ5NxM8F9nCL9s.json | 6 +- ...weapon_Improved_Mace_zSLx52U4Yltqx8F1.json | 6 +- ...mproved_Quarterstaff_BEmAR60PM3ZaiNXa.json | 6 +- ...apon_Improved_Rapier_LFPH8nD2f4Blv3AM.json | 6 +- ...oved_Returning_Blade_SKNwkW23eVQjN4Zy.json | 6 +- ...mproved_Round_Shield_DlinEBGZfIlvreO3.json | 6 +- ...pon_Improved_Scepter_tj26lbNkwy8bORF4.json | 12 ++-- ...on_Improved_Shortbow_6ZWl6ARfvYBaAMwY.json | 6 +- ..._Improved_Shortstaff_Mn8ja5Oi1sXvvPSk.json | 6 +- ..._Improved_Shortsword_rSyBNRwemBVuTo3H.json | 6 +- ...mproved_Small_Dagger_nMuF8ZDZ2aXZVTg6.json | 6 +- ...eapon_Improved_Spear_j5Pt1thLfcvopBij.json | 6 +- ...mproved_Tower_Shield_bxt3NsbMqTSdI5ab.json | 6 +- ...weapon_Improved_Wand_6d9B2b5X2d2U56jt.json | 6 +- ...n_Improved_Warhammer_pxaN4ZK4eqKrjtWj.json | 6 +- ...weapon_Improved_Whip_ftTp8VlsBQ1r4LFD.json | 6 +- ...eapon_Keeper_s_Staff_q382JqMkqLaaFLIr.json | 6 +- ...eapon_Knuckle_Blades_U8gfyvxoHm024inM.json | 6 +- ...weapon_Knuckle_Claws_SFqganS8Du4aEKjQ.json | 6 +- .../weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json | 6 +- ...ane_Frame_Wheelchair_gA2tiET9VHGhwMoO.json | 6 +- ...ary_Arcane_Gauntlets_umADDPYCaykXDc1v.json | 6 +- ..._Legendary_Battleaxe_1nztpLzoHGfbKf5x.json | 6 +- ...Legendary_Broadsword_y3hfTPfZhMognyaJ.json | 6 +- ...n_Legendary_Crossbow_1G6xX2QL9O0Rsgz7.json | 6 +- ...on_Legendary_Cutlass_Rpyz0jbFJ1SwqfyD.json | 6 +- ...pon_Legendary_Dagger_GmTg3Fdne1UPNs8t.json | 6 +- ..._Legendary_Dualstaff_o3rsLvImcLAx5TvD.json | 6 +- ...endary_Glowing_Rings_PReWrfuPjoNQuieo.json | 6 +- ...n_Legendary_Grappler_IrtUj0UntBMNn49G.json | 6 +- ...Legendary_Greatstaff_jDtvEabkHY1GFgfc.json | 6 +- ...Legendary_Greatsword_zMZ46F9VR7zdTxb9.json | 6 +- ...on_Legendary_Halberd_1AuMNiJz96Ez9fur.json | 6 +- ...gendary_Hallowed_Axe_0HmhnZnv1I6uX69c.json | 6 +- ...endary_Hand_Crossbow_32nYyMaeDWaakSxz.json | 6 +- ...Legendary_Hand_Runes_DWLkswhluXuMy3bB.json | 6 +- ...avy_Frame_Wheelchair_S6nB0CNlzdU05o5U.json | 6 +- ...ght_Frame_Wheelchair_Xt8tVSn5Fu6ly6LF.json | 6 +- ...on_Legendary_Longbow_Utt1GpoH1fhaTOtN.json | 6 +- ..._Legendary_Longsword_14abPqQcROJfDChR.json | 6 +- ...eapon_Legendary_Mace_RsDsy7tIhrhaAQQc.json | 6 +- ...gendary_Quarterstaff_1ZciqG7vIKLYpKsP.json | 6 +- ...pon_Legendary_Rapier_BakN97v4jTePcXiZ.json | 6 +- ...dary_Returning_Blade_mcj3CPkcSSDdAcBB.json | 6 +- ...gendary_Round_Shield_A28WL9E2lJ3iLZHW.json | 6 +- ...on_Legendary_Scepter_IZ4CWNxfuM46JeCN.json | 12 ++-- ...n_Legendary_Shortbow_j7kp36jaetfn5jb3.json | 6 +- ...Legendary_Shortstaff_D3SbNvNJZAFuzfhg.json | 6 +- ...Legendary_Shortsword_dEumq3BIZBk5xYTk.json | 6 +- ...gendary_Small_Dagger_Px3Rh3kIvAqyISxJ.json | 6 +- ...apon_Legendary_Spear_4e5pWxi2qohuGsWh.json | 6 +- ...gendary_Tower_Shield_MaJIROht7A9LxIZx.json | 6 +- ...eapon_Legendary_Wand_wPjg0LufJH9vUfVM.json | 6 +- ..._Legendary_Warhammer_W9ymfEDck2icfvla.json | 6 +- ...eapon_Legendary_Whip_Wcdbf6yS3LEt7nsg.json | 6 +- ...ght_Frame_Wheelchair_iaGnlUkShBgdeMo0.json | 6 +- .../weapon_Longbow_YfVs6Se903az4Yet.json | 6 +- .../weapon_Longsword_Iv8BZM1R24QMT72M.json | 6 +- .../weapons/weapon_Mace_cKQCDyM2UopDL9zF.json | 6 +- .../weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json | 6 +- ...eapon_Magus_Revolver_jGykNGQiKm63tCiE.json | 6 +- ...pon_Meridian_Cutlass_Gi26Zk9VqlAAgx3E.json | 6 +- .../weapon_Midas_Scythe_BdLfy5i488VZgkjP.json | 6 +- ...apon_Parrying_Dagger_taAZDkDCpeNgxhnn.json | 6 +- ...pon_Powered_Gauntlet_bW3xw5S9DbaLCN3E.json | 6 +- .../weapon_Primer_Shard_SxcblanBvqaest3A.json | 6 +- .../weapon_Quarterstaff_mlIj88p1wcQNjEDG.json | 6 +- .../weapon_Rapier_zkAgEW6zMkRZalEm.json | 6 +- ...on_Retractable_Saber_i8CqVTzqoRoCewNe.json | 6 +- ...weapon_Returning_Axe_FtsQGwOg3r8uUCST.json | 6 +- ...apon_Returning_Blade_4fQpVfQ3NVwTHStA.json | 6 +- ...weapon_Ricochet_Axes_E9QDh5o9eQ1Qx0kH.json | 6 +- .../weapon_Round_Shield_mxwWKDujgsRcZWPT.json | 6 +- ...n_Runes_of_Ruination_EG6mZhr3ib56r974.json | 6 +- .../weapon_Scepter_GZh345N8fmuS4Jeh.json | 12 ++-- ...pon_Scepter_of_Elias_acPGwIaUhx3R0mTq.json | 6 +- .../weapon_Shortbow_p9tdjQr2AZP19RYm.json | 6 +- .../weapon_Shortstaff_vHDHG3STcxTEfYAM.json | 6 +- .../weapon_Shortsword_cjGZpXCoshEqi1FI.json | 6 +- ..._Siphoning_Gauntlets_1N1jggda5DfdzdMj.json | 6 +- .../weapon_Sledge_Axe_OxsEmffWriiQmqJK.json | 12 ++-- .../weapon_Small_Dagger_wKklDxs5nkzILNp4.json | 6 +- .../weapon_Spear_TF85tKJetUjLwh54.json | 6 +- .../weapon_Spiked_Bow_O1w8KPYH85ZS8X64.json | 12 ++-- ...weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json | 6 +- ..._Steelforged_Halberd_6bkbw4Ap644KZGvJ.json | 6 +- ...n_Swinging_Ropeblade_1jOJHHKdtk3s2jaY.json | 6 +- ...ord_of_Light___Flame_TVPCWnSELOVBv6G1.json | 6 +- .../weapon_Talon_Blades_jlLtgK468rO5IssR.json | 6 +- .../weapon_Thistlebow_I1nDGpulg29GpWOW.json | 6 +- .../weapon_Tower_Shield_C9aWpK1shVMWP4m5.json | 6 +- ...apon_Urok_Broadsword_zGm6Wa1fGF6cECY5.json | 6 +- .../weapons/weapon_Wand_ItWisJFNGMNWeaCV.json | 6 +- ...Wand_of_Enthrallment_tP6vmnrmTq2h5sj7.json | 6 +- ...weapon_Wand_of_Essek_ZrRGNjGCgZTTfgDG.json | 6 +- .../weapon_War_Scythe_z6yEdFYQJ5IzgTX3.json | 6 +- .../weapon_Warhammer_ZXh1GQahBiODfSTC.json | 6 +- .../weapons/weapon_Whip_CmtWqw6DwoePnX7W.json | 6 +- ...pon_Widogast_Pendant_8Z5QrThfwkYPXNco.json | 6 +- ...apon_Yutari_Bloodbow_0XpSBYXxtywvBFQi.json | 6 +- ...eature_Battle_Ritual_qqb5acyUSl1sCpWW.json | 8 +-- ...re_Clarity_of_Nature_etaQ01yGJhBLDUqZ.json | 6 +- .../feature_Dark_Cloud_frBTtNMX9Y2gkuPz.json | 2 +- ...ature_Elemental_Aura_2JH9NaOh69yN80Gw.json | 8 +-- ...lemental_Incarnation_f37TTgCc0Q3Ih1A1.json | 12 ++-- ...ure_Gifted_Performer_99U7YWNCxFZHCiT0.json | 12 ++-- ...ture_Honed_Expertise_w1BwNKxbQOSizLmZ.json | 2 +- ...ture_Natural_Evasion_TnuLBtHQGbqyzn82.json | 2 +- ...feature_Regeneration_KRyrbSLVGreIOTZe.json | 6 +- .../feature_Revenge_oNfA5F9cKwNR7joq.json | 6 +- ...ature_Rousing_Speech_PCmYTX02JLzBpgml.json | 6 +- ...eature_Sparing_Touch_GfOSgVJW8bS1OjNq.json | 12 ++-- ...ture_Thrive_in_Chaos_1nmFmkNXY6OYyyju.json | 2 +- ..._Warden_s_Protection_2F1bUFY80oce97C9.json | 6 +- styles/less/global/elements.less | 9 +++ templates/actionTypes/damage.hbs | 65 +++++++++---------- templates/sheets/items/weapon/header.hbs | 6 +- templates/sheets/items/weapon/settings.hbs | 14 ++-- 536 files changed, 2476 insertions(+), 2368 deletions(-) create mode 100644 module/data/fields/iterableTypedObjectField.mjs diff --git a/module/applications/levelup/companionLevelup.mjs b/module/applications/levelup/companionLevelup.mjs index 7f11ccff..d6bf2d78 100644 --- a/module/applications/levelup/companionLevelup.mjs +++ b/module/applications/levelup/companionLevelup.mjs @@ -67,7 +67,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp { break; case 'summary': const levelKeys = Object.keys(this.levelup.levels); - const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice; + const actorDamageDice = this.actor.system.attack.damage.parts.hitPoints.value.dice; const actorRange = this.actor.system.attack.range; let achievementExperiences = []; diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 34543086..c7e8e758 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -1,3 +1,4 @@ +import { getUnusedDamageTypes } from '../../helpers/utils.mjs'; import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs'; const { ApplicationV2 } = foundry.applications.api; @@ -103,7 +104,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) } }; - static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects', 'summon']; + static CLEAN_ARRAYS = ['cost', 'effects', 'summon']; _getTabs(tabs) { for (const v of Object.values(tabs)) { @@ -155,6 +156,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) revealed: this.openTrigger === index }; }); + context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length; const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers; context.tierOptions = [ @@ -268,18 +270,61 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) static addDamage(_event) { if (!this.action.damage.parts) return; - const data = this.action.toObject(), - part = {}; - if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' }; - data.damage.parts.push(part); - this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + + const choices = getUnusedDamageTypes(this.action.damage.parts); + const content = new foundry.data.fields.StringField({ + label: game.i18n.localize('Damage Type'), + choices, + required: true + }).toFormGroup( + {}, + { + name: 'type', + localize: true, + nameAttr: 'value', + labelAttr: 'label' + } + ).outerHTML; + + const callback = (_, button) => { + const data = this.action.toObject(); + const type = choices[button.form.elements.type.value].value; + const part = { applyTo: type }; + if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' }; + data.damage.parts[type] = part; + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + }; + + const typeDialog = new foundry.applications.api.DialogV2({ + buttons: [ + foundry.utils.mergeObject( + { + action: 'ok', + label: 'Confirm', + icon: 'fas fa-check', + default: true + }, + { callback: callback } + ) + ], + content: content, + rejectClose: false, + modal: false, + window: { + title: game.i18n.localize('Add Damage') + }, + position: { width: 300 } + }); + + typeDialog.render(true); } static removeDamage(_event, button) { if (!this.action.damage.parts) return; - const data = this.action.toObject(), - index = button.dataset.index; - data.damage.parts.splice(index, 1); + const data = this.action.toObject(); + const key = button.dataset.key; + delete data.damage.parts[key]; + data.damage.parts[`-=${key}`] = null; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index eb53f073..8c96e0bd 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -472,7 +472,10 @@ export default function DHApplicationMixin(Base) { icon: 'fa-solid fa-explosion', condition: target => { const doc = getDocFromElementSync(target); - return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length; + return ( + !foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) || + !foundry.utils.isEmpty(doc?.damage?.parts) + ); }, callback: async (target, event) => { const doc = await getDocFromElement(target), diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 3b87c15d..d0d4c69f 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -245,8 +245,8 @@ export const defaultRestOptions = { type: 'friendly' }, damage: { - parts: [ - { + parts: { + hitPoints: { applyTo: healingTypes.hitPoints.id, value: { custom: { @@ -255,7 +255,7 @@ export const defaultRestOptions = { } } } - ] + } } } }, @@ -279,8 +279,8 @@ export const defaultRestOptions = { type: 'self' }, damage: { - parts: [ - { + parts: { + stress: { applyTo: healingTypes.stress.id, value: { custom: { @@ -289,7 +289,7 @@ export const defaultRestOptions = { } } } - ] + } } } }, @@ -314,8 +314,8 @@ export const defaultRestOptions = { type: 'friendly' }, damage: { - parts: [ - { + parts: { + armor: { applyTo: healingTypes.armor.id, value: { custom: { @@ -324,7 +324,7 @@ export const defaultRestOptions = { } } } - ] + } } } }, @@ -348,8 +348,8 @@ export const defaultRestOptions = { type: 'self' }, damage: { - parts: [ - { + parts: { + hope: { applyTo: healingTypes.hope.id, value: { custom: { @@ -358,7 +358,7 @@ export const defaultRestOptions = { } } } - ] + } } }, prepareWithFriends: { @@ -372,8 +372,8 @@ export const defaultRestOptions = { type: 'self' }, damage: { - parts: [ - { + parts: { + hope: { applyTo: healingTypes.hope.id, value: { custom: { @@ -382,7 +382,7 @@ export const defaultRestOptions = { } } } - ] + } } } }, @@ -409,8 +409,8 @@ export const defaultRestOptions = { type: 'friendly' }, damage: { - parts: [ - { + parts: { + hitPoints: { applyTo: healingTypes.hitPoints.id, value: { custom: { @@ -419,7 +419,7 @@ export const defaultRestOptions = { } } } - ] + } } } }, @@ -443,8 +443,8 @@ export const defaultRestOptions = { type: 'self' }, damage: { - parts: [ - { + parts: { + stress: { applyTo: healingTypes.stress.id, value: { custom: { @@ -453,7 +453,7 @@ export const defaultRestOptions = { } } } - ] + } } } }, @@ -478,8 +478,8 @@ export const defaultRestOptions = { type: 'friendly' }, damage: { - parts: [ - { + parts: { + armor: { applyTo: healingTypes.armor.id, value: { custom: { @@ -488,7 +488,7 @@ export const defaultRestOptions = { } } } - ] + } } } }, @@ -512,8 +512,8 @@ export const defaultRestOptions = { type: 'self' }, damage: { - parts: [ - { + parts: { + hope: { applyTo: healingTypes.hope.id, value: { custom: { @@ -522,7 +522,7 @@ export const defaultRestOptions = { } } } - ] + } } }, prepareWithFriends: { @@ -536,8 +536,8 @@ export const defaultRestOptions = { type: 'self' }, damage: { - parts: [ - { + parts: { + hope: { applyTo: healingTypes.hope.id, value: { custom: { @@ -546,7 +546,7 @@ export const defaultRestOptions = { } } } - ] + } } } }, diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index 77328987..b424c707 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -14,8 +14,8 @@ export const armorFeatures = { type: 'hostile' }, damage: { - parts: [ - { + parts: { + stress: { applyTo: 'stress', value: { custom: { @@ -24,7 +24,7 @@ export const armorFeatures = { } } } - ] + } } } ] @@ -732,8 +732,8 @@ export const weaponFeatures = { type: 'hostile' }, damage: { - parts: [ - { + parts: { + stress: { applyTo: 'stress', value: { custom: { @@ -742,7 +742,7 @@ export const weaponFeatures = { } } } - ] + } } } ], @@ -914,8 +914,8 @@ export const weaponFeatures = { type: 'self' }, damage: { - parts: [ - { + parts: { + hitPoints: { applyTo: 'hitPoints', value: { custom: { @@ -924,7 +924,7 @@ export const weaponFeatures = { } } } - ] + } } } ] diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index 60112c40..3671613d 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -26,23 +26,23 @@ export default class DHAttackAction extends DHDamageAction { return { value: { multiplier: 'prof', - dice: this.item?.system?.attack.damage.parts[0].value.dice, - bonus: this.item?.system?.attack.damage.parts[0].value.bonus ?? 0 + dice: this.item?.system?.attack.damage.parts.hitPoints.value.dice, + bonus: this.item?.system?.attack.damage.parts.hitPoints.value.bonus ?? 0 }, - type: this.item?.system?.attack.damage.parts[0].type, + type: this.item?.system?.attack.damage.parts.hitPoints.type, base: true }; } get damageFormula() { - const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); + const hitPointsPart = this.damage.parts.hitPoints; if (!hitPointsPart) return '0'; return hitPointsPart.value.getFormula(); } get altDamageFormula() { - const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); + const hitPointsPart = this.damage.parts.hitPoints; if (!hitPointsPart) return '0'; return hitPointsPart.valueAlt.getFormula(); diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f6ffe75f..5cc57ad7 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -352,11 +352,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } get hasDamage() { - return this.damage?.parts?.length && this.type !== 'healing'; + return !foundry.utils.isEmpty(this.damage.parts) && this.type !== 'healing'; } get hasHealing() { - return this.damage?.parts?.length && this.type === 'healing'; + return !foundry.utils.isEmpty(this.damage.parts) && this.type === 'healing'; } get hasSave() { @@ -376,6 +376,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return tags; } + + static migrateData(source) { + if (source.damage?.parts && Array.isArray(source.damage.parts)) { + source.damage.parts = source.damage.parts.reduce((acc, part) => { + acc[part.applyTo] = part; + return acc; + }, {}); + } + } } export class ResourceUpdateMap extends Map { diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 0a446c15..2288c548 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -89,14 +89,14 @@ export default class DhpAdversary extends DhCreature { type: 'attack' }, damage: { - parts: [ - { + parts: { + hitPoints: { type: ['physical'], value: { multiplier: 'flat' } } - ] + } } } }), @@ -268,12 +268,12 @@ export default class DhpAdversary extends DhCreature { } // Update damage in item actions + // Parse damage, and convert all formula matches in the descriptions to the new damage for (const action of Object.values(item.system.actions)) { - if (!action.damage) continue; - - // Parse damage, and convert all formula matches in the descriptions to the new damage try { const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' }); + if (!result) continue; + for (const { previousFormula, formula } of Object.values(result)) { const oldFormulaRegexp = new RegExp( previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?') @@ -375,16 +375,14 @@ export default class DhpAdversary extends DhCreature { /** * Updates damage to reflect a specific value. * @throws if damage structure is invalid for conversion - * @returns the converted formula and value as a simplified term + * @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage */ #adjustActionDamage(action, damageMeta) { - // The current algorithm only returns a value if there is a single damage part - const hpDamageParts = action.damage.parts.filter(d => d.applyTo === 'hitPoints'); - if (hpDamageParts.length !== 1) throw new Error('incorrect number of hp parts'); + if (!action.damage?.parts.hitPoints) return null; const result = {}; for (const property of ['value', 'valueAlt']) { - const data = hpDamageParts[0][property]; + const data = action.damage.parts.hitPoints[property]; const previousFormula = data.custom.enabled ? data.custom.formula : [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0] diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 10fba63c..414a9299 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -118,8 +118,8 @@ export default class DhCharacter extends DhCreature { trait: 'strength' }, damage: { - parts: [ - { + parts: { + hitPoints: { type: ['physical'], value: { custom: { @@ -128,7 +128,7 @@ export default class DhCharacter extends DhCreature { } } } - ] + } } } }), @@ -704,7 +704,7 @@ export default class DhCharacter extends DhCreature { isReversed: true }; - this.attack.damage.parts[0].value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`; + this.attack.damage.parts.hitPoints.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`; } getRollData() { diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 6f51b593..32ae661d 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -85,15 +85,15 @@ export default class DhCompanion extends DhCreature { bonus: 0 }, damage: { - parts: [ - { + parts: { + hitPoints: { type: ['physical'], value: { dice: 'd6', multiplier: 'prof' } } - ] + } } } }), @@ -138,7 +138,9 @@ export default class DhCompanion extends DhCreature { break; case 'vicious': if (selection.data[0] === 'damage') { - this.attack.damage.parts[0].value.dice = adjustDice(this.attack.damage.parts[0].value.dice); + this.attack.damage.parts.hitPoints.value.dice = adjustDice( + this.attack.damage.parts.hitPoints.value.dice + ); } else { this.attack.range = adjustRange(this.attack.range).id; } diff --git a/module/data/fields/_module.mjs b/module/data/fields/_module.mjs index 58423979..930814e2 100644 --- a/module/data/fields/_module.mjs +++ b/module/data/fields/_module.mjs @@ -1,4 +1,5 @@ export { ActionCollection } from './actionField.mjs'; +export { default as IterableTypedObjectField } from './iterableTypedObjectField.mjs'; export { default as FormulaField } from './formulaField.mjs'; export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs'; export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs'; diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 6439344b..e79a91a2 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -1,5 +1,6 @@ import FormulaField from '../formulaField.mjs'; import { setsEqual } from '../../../helpers/utils.mjs'; +import IterableTypedObjectField from '../iterableTypedObjectField.mjs'; const fields = foundry.data.fields; @@ -12,7 +13,7 @@ export default class DamageField extends fields.SchemaField { /** @inheritDoc */ constructor(options, context = {}) { const damageFields = { - parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)), + parts: new IterableTypedObjectField(DHDamageData), includeBase: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label' diff --git a/module/data/fields/iterableTypedObjectField.mjs b/module/data/fields/iterableTypedObjectField.mjs new file mode 100644 index 00000000..d50d7dbf --- /dev/null +++ b/module/data/fields/iterableTypedObjectField.mjs @@ -0,0 +1,32 @@ +export default class IterableTypedObjectField extends foundry.data.fields.TypedObjectField { + constructor(model, options = { collectionClass: foundry.utils.Collection }, context = {}) { + super(new foundry.data.fields.EmbeddedDataField(model), options, context); + this.#elementClass = model; + } + + #elementClass; + + /** Initializes an object with an iterator. This modifies the prototype instead of */ + initialize(values) { + const object = Object.create(IterableObjectPrototype); + for (const [key, value] of Object.entries(values)) { + object[key] = new this.#elementClass(value); + } + return object; + } +} + +/** + * The prototype of an iterable object. + * This allows the functionality of a class but also allows foundry.utils.getType() to return "Object" instead of "Unknown". + */ +const IterableObjectPrototype = { + [Symbol.iterator]: function*() { + for (const value of Object.values(this)) { + yield value; + } + }, + map: function (func) { + return Array.from(this, func); + } +}; \ No newline at end of file diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index bb2e10d5..18660ab5 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -63,15 +63,15 @@ export default class DHWeapon extends AttachableItem { type: 'attack' }, damage: { - parts: [ - { + parts: { + hitPoints: { type: ['physical'], value: { multiplier: 'prof', dice: 'd8' } } - ] + } } } }), diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index 2faea830..1c47f8dc 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -49,9 +49,7 @@ export default class RegisterHandlebarsHelpers { } static damageSymbols(damageParts) { - const symbols = [...new Set(damageParts.reduce((a, c) => a.concat([...c.type]), []))].map( - p => CONFIG.DH.GENERAL.damageTypes[p].icon - ); + const symbols = [...new Set(damageParts.map(x => x.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 7e4d794b..5f9af9c9 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -722,3 +722,16 @@ export async function RefreshFeatures( return refreshedActors; } + +export function getUnusedDamageTypes(parts) { + const usedKeys = Object.keys(parts); + return Object.keys(CONFIG.DH.GENERAL.healingTypes).reduce((acc, key) => { + if (!usedKeys.includes(key)) + acc.push({ + value: key, + label: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label) + }); + + return acc; + }, []); +} diff --git a/src/packs/adversaries/adversary_Acid_Burrower_89yAh30vaNQOALlz.json b/src/packs/adversaries/adversary_Acid_Burrower_89yAh30vaNQOALlz.json index 70d0072b..0dd182fa 100644 --- a/src/packs/adversaries/adversary_Acid_Burrower_89yAh30vaNQOALlz.json +++ b/src/packs/adversaries/adversary_Acid_Burrower_89yAh30vaNQOALlz.json @@ -91,8 +91,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -118,7 +118,7 @@ }, "base": false } - ], + }, "includeBase": false }, "_id": "TCKVaVweyJzhEArX", @@ -343,7 +343,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -471,8 +471,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -499,7 +499,7 @@ } } }, - { + "armor": { "value": { "custom": { "enabled": true, @@ -524,7 +524,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -598,8 +598,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -626,7 +626,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -652,8 +652,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -680,7 +680,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Adult_Flickerfly_G7jiltRjgvVhZewm.json b/src/packs/adversaries/adversary_Adult_Flickerfly_G7jiltRjgvVhZewm.json index 16fb61d8..c4b4eb2a 100644 --- a/src/packs/adversaries/adversary_Adult_Flickerfly_G7jiltRjgvVhZewm.json +++ b/src/packs/adversaries/adversary_Adult_Flickerfly_G7jiltRjgvVhZewm.json @@ -75,8 +75,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -400,8 +400,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -427,7 +427,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, @@ -508,7 +508,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -581,8 +581,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -608,7 +608,7 @@ } } }, - { + "hope": { "value": { "custom": { "enabled": true, @@ -633,7 +633,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json b/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json index 23f1f339..4c63297d 100644 --- a/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json +++ b/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json @@ -72,8 +72,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -100,7 +100,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/daggers/dagger-bone-black.webp", "type": "attack", diff --git a/src/packs/adversaries/adversary_Arch_Necromancer_WPEOIGfclNJxWb87.json b/src/packs/adversaries/adversary_Arch_Necromancer_WPEOIGfclNJxWb87.json index 3b1f3535..dfae0598 100644 --- a/src/packs/adversaries/adversary_Arch_Necromancer_WPEOIGfclNJxWb87.json +++ b/src/packs/adversaries/adversary_Arch_Necromancer_WPEOIGfclNJxWb87.json @@ -85,8 +85,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -112,7 +112,7 @@ }, "base": false } - ] + } }, "img": "icons/magic/unholy/beam-ringed-impact-purple.webp", "type": "attack", @@ -256,7 +256,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -336,8 +336,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -363,7 +363,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -414,8 +414,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -440,7 +440,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -619,7 +619,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -692,8 +692,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -719,7 +719,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Archer_Guard_JRhrrEg5UroURiAD.json b/src/packs/adversaries/adversary_Archer_Guard_JRhrrEg5UroURiAD.json index 5a13b3d9..965c5168 100644 --- a/src/packs/adversaries/adversary_Archer_Guard_JRhrrEg5UroURiAD.json +++ b/src/packs/adversaries/adversary_Archer_Guard_JRhrrEg5UroURiAD.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/bows/longbow-recurve-leather-brown.webp", "type": "attack", @@ -246,8 +246,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -273,7 +273,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Archer_Squadron_0ts6CGd93lLqGZI5.json b/src/packs/adversaries/adversary_Archer_Squadron_0ts6CGd93lLqGZI5.json index 5b15bc09..67e10c53 100644 --- a/src/packs/adversaries/adversary_Archer_Squadron_0ts6CGd93lLqGZI5.json +++ b/src/packs/adversaries/adversary_Archer_Squadron_0ts6CGd93lLqGZI5.json @@ -68,8 +68,8 @@ "description": "

      A group of trained archers bearing massive bows.

      ", "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -95,7 +95,7 @@ "resultBased": false, "base": false } - ] + } }, "name": "Longbow", "img": "icons/weapons/bows/longbow-recurve-leather-brown.webp", @@ -270,8 +270,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -295,7 +295,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -368,8 +368,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -393,7 +393,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Assassin_Poisoner_h5RuhzGL17dW5FBT.json b/src/packs/adversaries/adversary_Assassin_Poisoner_h5RuhzGL17dW5FBT.json index 324fc25e..4b3a872a 100644 --- a/src/packs/adversaries/adversary_Assassin_Poisoner_h5RuhzGL17dW5FBT.json +++ b/src/packs/adversaries/adversary_Assassin_Poisoner_h5RuhzGL17dW5FBT.json @@ -81,8 +81,8 @@ }, "range": "close", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false diff --git a/src/packs/adversaries/adversary_Battle_Box_dgH3fW9FTYLaIDvS.json b/src/packs/adversaries/adversary_Battle_Box_dgH3fW9FTYLaIDvS.json index 02553014..74ed8dfd 100644 --- a/src/packs/adversaries/adversary_Battle_Box_dgH3fW9FTYLaIDvS.json +++ b/src/packs/adversaries/adversary_Battle_Box_dgH3fW9FTYLaIDvS.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "range": "melee", "type": "attack", @@ -309,7 +309,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -382,8 +382,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -409,7 +409,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -482,8 +482,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -509,7 +509,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -582,8 +582,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -609,7 +609,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -737,8 +737,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -763,7 +763,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -836,7 +836,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -964,8 +964,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -991,7 +991,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -1071,8 +1071,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -1097,7 +1097,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -1165,8 +1165,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -1192,7 +1192,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json b/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json index 959ef9dd..cfc71120 100644 --- a/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json +++ b/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json @@ -84,8 +84,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -111,7 +111,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/claws/claw-straight-brown.webp", "type": "attack", @@ -284,8 +284,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -311,7 +311,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -441,8 +441,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -468,7 +468,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Bladed_Guard_B4LZcGuBAHzyVdzy.json b/src/packs/adversaries/adversary_Bladed_Guard_B4LZcGuBAHzyVdzy.json index 0a332575..a315f91a 100644 --- a/src/packs/adversaries/adversary_Bladed_Guard_B4LZcGuBAHzyVdzy.json +++ b/src/packs/adversaries/adversary_Bladed_Guard_B4LZcGuBAHzyVdzy.json @@ -80,8 +80,8 @@ "name": "Longsword", "img": "icons/weapons/swords/sword-guard.webp", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -246,7 +246,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -319,7 +319,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json b/src/packs/adversaries/adversary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json index a169bf61..8863641d 100644 --- a/src/packs/adversaries/adversary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json +++ b/src/packs/adversaries/adversary_Brawny_Zombie_2UeZ0tEe7AzgSJNd.json @@ -83,8 +83,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -110,7 +110,7 @@ }, "base": false } - ] + } }, "img": "icons/skills/melee/unarmed-punch-fist-yellow-red.webp", "type": "attack", @@ -280,8 +280,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -307,7 +307,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, @@ -389,8 +389,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -415,7 +415,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Cave_Ogre_8Zkqk1jU09nKL2fy.json b/src/packs/adversaries/adversary_Cave_Ogre_8Zkqk1jU09nKL2fy.json index 5b2d2e41..cb081441 100644 --- a/src/packs/adversaries/adversary_Cave_Ogre_8Zkqk1jU09nKL2fy.json +++ b/src/packs/adversaries/adversary_Cave_Ogre_8Zkqk1jU09nKL2fy.json @@ -79,8 +79,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ], + }, "direct": true }, "name": "Club", @@ -336,8 +336,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false, @@ -365,7 +365,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, @@ -412,8 +412,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -438,7 +438,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -507,8 +507,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -534,7 +534,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, diff --git a/src/packs/adversaries/adversary_Chaos_Skull_jDmHqGvzg5wjgmxE.json b/src/packs/adversaries/adversary_Chaos_Skull_jDmHqGvzg5wjgmxE.json index f548870a..a95db1b7 100644 --- a/src/packs/adversaries/adversary_Chaos_Skull_jDmHqGvzg5wjgmxE.json +++ b/src/packs/adversaries/adversary_Chaos_Skull_jDmHqGvzg5wjgmxE.json @@ -74,8 +74,8 @@ }, "range": "close", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "img": "icons/magic/light/beam-rays-magenta.webp", "type": "attack", @@ -383,8 +383,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -410,7 +410,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -483,8 +483,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -508,7 +508,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json b/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json index 35c43a3b..5cbc1f82 100644 --- a/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json +++ b/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json @@ -67,8 +67,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -95,7 +95,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false diff --git a/src/packs/adversaries/adversary_Construct_uOP5oT9QzXPlnf3p.json b/src/packs/adversaries/adversary_Construct_uOP5oT9QzXPlnf3p.json index 310eefce..067248c9 100644 --- a/src/packs/adversaries/adversary_Construct_uOP5oT9QzXPlnf3p.json +++ b/src/packs/adversaries/adversary_Construct_uOP5oT9QzXPlnf3p.json @@ -72,8 +72,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -99,7 +99,7 @@ }, "base": false } - ] + } }, "name": "Fist Slam", "img": "icons/skills/melee/unarmed-punch-fist-yellow-red.webp", @@ -332,8 +332,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -359,7 +359,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -534,8 +534,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -561,7 +561,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Courtesan_ZxWaWPdzFIUPNC62.json b/src/packs/adversaries/adversary_Courtesan_ZxWaWPdzFIUPNC62.json index 24572103..aba9ea46 100644 --- a/src/packs/adversaries/adversary_Courtesan_ZxWaWPdzFIUPNC62.json +++ b/src/packs/adversaries/adversary_Courtesan_ZxWaWPdzFIUPNC62.json @@ -84,8 +84,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -111,7 +111,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/daggers/dagger-straight-cracked.webp", "type": "attack", @@ -256,8 +256,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -282,7 +282,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Courtier_CBBuEXAlLKFMJdjg.json b/src/packs/adversaries/adversary_Courtier_CBBuEXAlLKFMJdjg.json index 1ffc7ece..8777ee06 100644 --- a/src/packs/adversaries/adversary_Courtier_CBBuEXAlLKFMJdjg.json +++ b/src/packs/adversaries/adversary_Courtier_CBBuEXAlLKFMJdjg.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/daggers/dagger-twin-green.webp", "type": "attack", @@ -253,8 +253,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -279,7 +279,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json b/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json index e428d05d..cbb48681 100644 --- a/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json +++ b/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json @@ -84,8 +84,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -111,7 +111,7 @@ }, "base": false } - ] + } }, "range": "far", "img": "icons/weapons/staves/staff-ornate-purple.webp", @@ -256,8 +256,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -283,7 +283,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -308,7 +308,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -614,8 +614,8 @@ "recovery": "scene" }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -640,7 +640,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Cult_Fang_tyBOpLfigAhI9bU3.json b/src/packs/adversaries/adversary_Cult_Fang_tyBOpLfigAhI9bU3.json index d3b341f0..e65f3202 100644 --- a/src/packs/adversaries/adversary_Cult_Fang_tyBOpLfigAhI9bU3.json +++ b/src/packs/adversaries/adversary_Cult_Fang_tyBOpLfigAhI9bU3.json @@ -74,8 +74,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "range": "melee", "type": "attack", @@ -300,8 +300,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -326,7 +326,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json b/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json index a0c0713d..4f04a85a 100644 --- a/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json +++ b/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json @@ -66,8 +66,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -94,7 +94,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", diff --git a/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json b/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json index f66ce7f0..aed2e08a 100644 --- a/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json +++ b/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/magic/nature/root-vines-grow-brown.webp", "type": "attack", @@ -245,8 +245,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -271,7 +271,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -325,8 +325,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -350,7 +350,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Demon_of_Avarice_pnyjIGxxvurcWmTv.json b/src/packs/adversaries/adversary_Demon_of_Avarice_pnyjIGxxvurcWmTv.json index e4ba41fb..1a3538da 100644 --- a/src/packs/adversaries/adversary_Demon_of_Avarice_pnyjIGxxvurcWmTv.json +++ b/src/packs/adversaries/adversary_Demon_of_Avarice_pnyjIGxxvurcWmTv.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", diff --git a/src/packs/adversaries/adversary_Demon_of_Despair_kE4dfhqmIQpNd44e.json b/src/packs/adversaries/adversary_Demon_of_Despair_kE4dfhqmIQpNd44e.json index 27c6bac2..e2f58709 100644 --- a/src/packs/adversaries/adversary_Demon_of_Despair_kE4dfhqmIQpNd44e.json +++ b/src/packs/adversaries/adversary_Demon_of_Despair_kE4dfhqmIQpNd44e.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "far", @@ -435,8 +435,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -461,7 +461,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -515,8 +515,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -542,7 +542,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Demon_of_Hubris_2VN3BftageoTTIzu.json b/src/packs/adversaries/adversary_Demon_of_Hubris_2VN3BftageoTTIzu.json index 16bc1d1f..9f954437 100644 --- a/src/packs/adversaries/adversary_Demon_of_Hubris_2VN3BftageoTTIzu.json +++ b/src/packs/adversaries/adversary_Demon_of_Hubris_2VN3BftageoTTIzu.json @@ -81,8 +81,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -251,8 +251,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -277,7 +277,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -329,8 +329,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -355,7 +355,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -414,8 +414,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -441,7 +441,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -550,8 +550,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -576,7 +576,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Demon_of_Jealousy_SxSOkM4bcVOFyjbo.json b/src/packs/adversaries/adversary_Demon_of_Jealousy_SxSOkM4bcVOFyjbo.json index 31f9b942..c083b183 100644 --- a/src/packs/adversaries/adversary_Demon_of_Jealousy_SxSOkM4bcVOFyjbo.json +++ b/src/packs/adversaries/adversary_Demon_of_Jealousy_SxSOkM4bcVOFyjbo.json @@ -80,8 +80,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ], + }, "direct": true }, "img": "icons/magic/symbols/rune-sigil-rough-white-teal.webp", @@ -352,7 +352,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json b/src/packs/adversaries/adversary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json index 33ded6b9..201b17fd 100644 --- a/src/packs/adversaries/adversary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json +++ b/src/packs/adversaries/adversary_Demon_of_Wrath_5lphJAgzoqZI3VoG.json @@ -81,8 +81,8 @@ }, "img": "icons/skills/melee/unarmed-punch-fist-yellow-red.webp", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ], + }, "direct": true }, "type": "attack", @@ -398,8 +398,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -425,7 +425,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, diff --git a/src/packs/adversaries/adversary_Demonic_Hound_Pack_NoRZ1PqB8N5wcIw0.json b/src/packs/adversaries/adversary_Demonic_Hound_Pack_NoRZ1PqB8N5wcIw0.json index 7482c734..2043d960 100644 --- a/src/packs/adversaries/adversary_Demonic_Hound_Pack_NoRZ1PqB8N5wcIw0.json +++ b/src/packs/adversaries/adversary_Demonic_Hound_Pack_NoRZ1PqB8N5wcIw0.json @@ -74,8 +74,8 @@ "motivesAndTactics": "Cause fear, consume fl esh, please masters", "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ "resultBased": false, "base": false } - ] + } }, "name": "Claws and Fangs", "img": "icons/creatures/abilities/mouth-teeth-rows-red.webp", @@ -269,8 +269,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -295,7 +295,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -349,8 +349,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -376,7 +376,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Dire_Bat_tBWHW00epmMnkawe.json b/src/packs/adversaries/adversary_Dire_Bat_tBWHW00epmMnkawe.json index 16ec7643..23d5550e 100644 --- a/src/packs/adversaries/adversary_Dire_Bat_tBWHW00epmMnkawe.json +++ b/src/packs/adversaries/adversary_Dire_Bat_tBWHW00epmMnkawe.json @@ -78,8 +78,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -105,7 +105,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/claws/claw-hooked-curved.webp", "type": "attack", @@ -312,8 +312,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -337,7 +337,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -397,8 +397,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -424,7 +424,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json b/src/packs/adversaries/adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json index 69301cb2..939a5307 100644 --- a/src/packs/adversaries/adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json +++ b/src/packs/adversaries/adversary_Dire_Wolf_wNzeuQLfLUMvgHlQ.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/claws/claw-straight-brown.webp", "type": "attack", @@ -247,8 +247,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -272,7 +272,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -352,8 +352,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -377,7 +377,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, diff --git a/src/packs/adversaries/adversary_Dryad_wR7cFKrHvRzbzhBT.json b/src/packs/adversaries/adversary_Dryad_wR7cFKrHvRzbzhBT.json index ca9ce647..c69ee84e 100644 --- a/src/packs/adversaries/adversary_Dryad_wR7cFKrHvRzbzhBT.json +++ b/src/packs/adversaries/adversary_Dryad_wR7cFKrHvRzbzhBT.json @@ -81,8 +81,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -251,7 +251,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -297,8 +297,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -324,7 +324,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -438,8 +438,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -464,7 +464,7 @@ }, "type": [] }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -489,7 +489,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Electric_Eels_TLzY1nDw0Bu9Ud40.json b/src/packs/adversaries/adversary_Electric_Eels_TLzY1nDw0Bu9Ud40.json index 9386944f..1909a74a 100644 --- a/src/packs/adversaries/adversary_Electric_Eels_TLzY1nDw0Bu9Ud40.json +++ b/src/packs/adversaries/adversary_Electric_Eels_TLzY1nDw0Bu9Ud40.json @@ -68,8 +68,8 @@ "motivesAndTactics": "Avoid larger predators, shock prey, tear apart", "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -95,7 +95,7 @@ "resultBased": false, "base": false } - ] + } }, "name": "Shocking Bite", "img": "icons/creatures/abilities/mouth-teeth-sharp.webp", @@ -270,8 +270,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -297,7 +297,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json b/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json index 5c25f63e..2c2633ea 100644 --- a/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json +++ b/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json @@ -67,8 +67,8 @@ }, "range": "close", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -95,7 +95,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false diff --git a/src/packs/adversaries/adversary_Elite_Soldier_bfhVWMBUh61b9J6n.json b/src/packs/adversaries/adversary_Elite_Soldier_bfhVWMBUh61b9J6n.json index de5db0b2..df3e6d12 100644 --- a/src/packs/adversaries/adversary_Elite_Soldier_bfhVWMBUh61b9J6n.json +++ b/src/packs/adversaries/adversary_Elite_Soldier_bfhVWMBUh61b9J6n.json @@ -98,8 +98,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -125,7 +125,7 @@ }, "base": false } - ], + }, "includeBase": false }, "target": { @@ -276,8 +276,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -303,7 +303,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Failed_Experiment_ChwwVqowFw8hJQwT.json b/src/packs/adversaries/adversary_Failed_Experiment_ChwwVqowFw8hJQwT.json index 408d5102..70e56980 100644 --- a/src/packs/adversaries/adversary_Failed_Experiment_ChwwVqowFw8hJQwT.json +++ b/src/packs/adversaries/adversary_Failed_Experiment_ChwwVqowFw8hJQwT.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/claws/claw-hooked-barbed.webp", "range": "melee", diff --git a/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json b/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json index 931e4c0a..8c0d7b95 100644 --- a/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json +++ b/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json @@ -67,8 +67,8 @@ "img": "icons/weapons/axes/axe-battle-skull-black.webp", "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -95,7 +95,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -256,8 +256,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -282,7 +282,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json b/src/packs/adversaries/adversary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json index bfad5cf6..06db1453 100644 --- a/src/packs/adversaries/adversary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json +++ b/src/packs/adversaries/adversary_Fallen_Sorcerer_PELRry1vqjBzSAlr.json @@ -80,8 +80,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/staves/staff-animal-skull-bull.webp", "type": "attack", @@ -251,8 +251,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -278,7 +278,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -482,8 +482,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -508,7 +508,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Fallen_Warlord__Realm_Breaker_hxZ0sgoFJubh5aj6.json b/src/packs/adversaries/adversary_Fallen_Warlord__Realm_Breaker_hxZ0sgoFJubh5aj6.json index b2cdc489..8864e76d 100644 --- a/src/packs/adversaries/adversary_Fallen_Warlord__Realm_Breaker_hxZ0sgoFJubh5aj6.json +++ b/src/packs/adversaries/adversary_Fallen_Warlord__Realm_Breaker_hxZ0sgoFJubh5aj6.json @@ -67,8 +67,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -94,7 +94,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", @@ -337,7 +337,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -416,8 +416,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -443,7 +443,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -516,8 +516,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -543,7 +543,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -653,8 +653,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -679,7 +679,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Fallen_Warlord__Undefeated_Champion_RXkZTwBRi4dJ3JE5.json b/src/packs/adversaries/adversary_Fallen_Warlord__Undefeated_Champion_RXkZTwBRi4dJ3JE5.json index 89d61c1c..5ad77ab0 100644 --- a/src/packs/adversaries/adversary_Fallen_Warlord__Undefeated_Champion_RXkZTwBRi4dJ3JE5.json +++ b/src/packs/adversaries/adversary_Fallen_Warlord__Undefeated_Champion_RXkZTwBRi4dJ3JE5.json @@ -67,8 +67,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -94,7 +94,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", @@ -338,7 +338,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -410,8 +410,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -437,7 +437,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -697,8 +697,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -724,7 +724,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -792,8 +792,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -818,7 +818,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json b/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json index e576e1e0..c6a482dd 100644 --- a/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json +++ b/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json @@ -81,8 +81,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -269,8 +269,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -294,7 +294,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Giant_Brawler_YnObCleGjPT7yqEc.json b/src/packs/adversaries/adversary_Giant_Brawler_YnObCleGjPT7yqEc.json index 4f76b706..8948c8b6 100644 --- a/src/packs/adversaries/adversary_Giant_Brawler_YnObCleGjPT7yqEc.json +++ b/src/packs/adversaries/adversary_Giant_Brawler_YnObCleGjPT7yqEc.json @@ -81,8 +81,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -251,8 +251,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -278,7 +278,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -351,8 +351,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -378,7 +378,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -452,8 +452,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -479,7 +479,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json b/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json index ea578762..a8a33586 100644 --- a/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json +++ b/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json @@ -98,8 +98,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -125,7 +125,7 @@ }, "base": false } - ], + }, "includeBase": false }, "target": { @@ -345,8 +345,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -372,7 +372,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -499,8 +499,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -526,7 +526,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -643,8 +643,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -670,7 +670,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Giant_Mosquitoes_IIWV4ysJPFPnTP7W.json b/src/packs/adversaries/adversary_Giant_Mosquitoes_IIWV4ysJPFPnTP7W.json index fbb30d40..a74cb88d 100644 --- a/src/packs/adversaries/adversary_Giant_Mosquitoes_IIWV4ysJPFPnTP7W.json +++ b/src/packs/adversaries/adversary_Giant_Mosquitoes_IIWV4ysJPFPnTP7W.json @@ -74,8 +74,8 @@ "motivesAndTactics": "Fly away, harass, steal blood", "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ "resultBased": false, "base": false } - ] + } }, "name": "Proboscis", "img": "icons/skills/wounds/blood-cells-vessel-red.webp", diff --git a/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json b/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json index d1df6b57..822ee035 100644 --- a/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json +++ b/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json @@ -72,8 +72,8 @@ "name": "Claws", "img": "icons/creatures/claws/claw-straight-brown.webp", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -100,7 +100,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -272,7 +272,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json b/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json index adcdf015..376ebace 100644 --- a/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json +++ b/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json @@ -62,8 +62,8 @@ "name": "Warhammer", "img": "icons/weapons/hammers/hammer-double-stone-worn.webp", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -90,7 +90,7 @@ }, "base": false } - ] + } }, "range": "veryClose", "roll": { diff --git a/src/packs/adversaries/adversary_Giant_Scorpion_fmfntuJ8mHRCAktP.json b/src/packs/adversaries/adversary_Giant_Scorpion_fmfntuJ8mHRCAktP.json index fda3e656..03a0272d 100644 --- a/src/packs/adversaries/adversary_Giant_Scorpion_fmfntuJ8mHRCAktP.json +++ b/src/packs/adversaries/adversary_Giant_Scorpion_fmfntuJ8mHRCAktP.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -253,8 +253,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -280,7 +280,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -354,8 +354,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -381,7 +381,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -508,8 +508,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -535,7 +535,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Glass_Snake_8KWVLWXFhlY2kYx0.json b/src/packs/adversaries/adversary_Glass_Snake_8KWVLWXFhlY2kYx0.json index f02a1c52..5876f55c 100644 --- a/src/packs/adversaries/adversary_Glass_Snake_8KWVLWXFhlY2kYx0.json +++ b/src/packs/adversaries/adversary_Glass_Snake_8KWVLWXFhlY2kYx0.json @@ -75,8 +75,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -240,8 +240,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -266,7 +266,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -326,8 +326,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -351,7 +351,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -459,8 +459,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -486,7 +486,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -532,7 +532,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Gorgon_8mJYMpbLTb8qIOrr.json b/src/packs/adversaries/adversary_Gorgon_8mJYMpbLTb8qIOrr.json index 2753d958..1fcfcce4 100644 --- a/src/packs/adversaries/adversary_Gorgon_8mJYMpbLTb8qIOrr.json +++ b/src/packs/adversaries/adversary_Gorgon_8mJYMpbLTb8qIOrr.json @@ -80,8 +80,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/bows/shortbow-recurve-yellow.webp", "type": "attack", @@ -409,8 +409,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -436,7 +436,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -461,7 +461,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -541,7 +541,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -652,8 +652,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -679,7 +679,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json b/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json index fe968ff3..077373b2 100644 --- a/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json +++ b/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json @@ -75,8 +75,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -268,8 +268,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -294,7 +294,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -422,8 +422,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -449,7 +449,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -578,8 +578,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -605,7 +605,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Greater_Water_Elemental_xIICT6tEdnA7dKDV.json b/src/packs/adversaries/adversary_Greater_Water_Elemental_xIICT6tEdnA7dKDV.json index 5d17f025..b44bb9cc 100644 --- a/src/packs/adversaries/adversary_Greater_Water_Elemental_xIICT6tEdnA7dKDV.json +++ b/src/packs/adversaries/adversary_Greater_Water_Elemental_xIICT6tEdnA7dKDV.json @@ -75,8 +75,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -245,8 +245,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -272,7 +272,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -401,7 +401,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Green_Ooze_SHXedd9zZPVfUgUa.json b/src/packs/adversaries/adversary_Green_Ooze_SHXedd9zZPVfUgUa.json index 5f3f52c2..a3a76d7a 100644 --- a/src/packs/adversaries/adversary_Green_Ooze_SHXedd9zZPVfUgUa.json +++ b/src/packs/adversaries/adversary_Green_Ooze_SHXedd9zZPVfUgUa.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/slimes/slime-movement-dripping-pseudopods-green.webp", "type": "attack", @@ -277,8 +277,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -303,7 +303,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -356,8 +356,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -383,7 +383,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -408,7 +408,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Hallowed_Archer_kabueAo6BALApWqp.json b/src/packs/adversaries/adversary_Hallowed_Archer_kabueAo6BALApWqp.json index 8cce1b94..eb7eafc1 100644 --- a/src/packs/adversaries/adversary_Hallowed_Archer_kabueAo6BALApWqp.json +++ b/src/packs/adversaries/adversary_Hallowed_Archer_kabueAo6BALApWqp.json @@ -75,8 +75,8 @@ "name": "Sanctified Longbow", "img": "icons/weapons/bows/shortbow-recurve-yellow.webp", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -314,8 +314,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -341,7 +341,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json b/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json index 95a2ecd0..6a131c86 100644 --- a/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json +++ b/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json @@ -65,8 +65,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -93,7 +93,7 @@ }, "base": false } - ] + } }, "img": "icons/skills/melee/sword-shield-stylized-white.webp", "type": "attack", diff --git a/src/packs/adversaries/adversary_Harrier_uRtghKE9mHlII4rs.json b/src/packs/adversaries/adversary_Harrier_uRtghKE9mHlII4rs.json index 89d82a0b..84034a7e 100644 --- a/src/packs/adversaries/adversary_Harrier_uRtghKE9mHlII4rs.json +++ b/src/packs/adversaries/adversary_Harrier_uRtghKE9mHlII4rs.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/polearms/spear-hooked-rounded.webp", "type": "attack", @@ -278,8 +278,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -305,7 +305,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Head_Guard_mK3A5FTx6k8iPU3F.json b/src/packs/adversaries/adversary_Head_Guard_mK3A5FTx6k8iPU3F.json index 75afed49..f0c050a9 100644 --- a/src/packs/adversaries/adversary_Head_Guard_mK3A5FTx6k8iPU3F.json +++ b/src/packs/adversaries/adversary_Head_Guard_mK3A5FTx6k8iPU3F.json @@ -85,8 +85,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -112,7 +112,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -258,7 +258,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -398,8 +398,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -425,7 +425,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Head_Vampire_i2UNbRvgyoSs07M6.json b/src/packs/adversaries/adversary_Head_Vampire_i2UNbRvgyoSs07M6.json index d5891359..322e4658 100644 --- a/src/packs/adversaries/adversary_Head_Vampire_i2UNbRvgyoSs07M6.json +++ b/src/packs/adversaries/adversary_Head_Vampire_i2UNbRvgyoSs07M6.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -244,8 +244,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -270,7 +270,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -322,7 +322,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -394,8 +394,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -420,7 +420,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -542,8 +542,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -568,7 +568,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_High_Seraph_r1mbfSSwKWdcFdAU.json b/src/packs/adversaries/adversary_High_Seraph_r1mbfSSwKWdcFdAU.json index 60fe1917..3dc96fd5 100644 --- a/src/packs/adversaries/adversary_High_Seraph_r1mbfSSwKWdcFdAU.json +++ b/src/packs/adversaries/adversary_High_Seraph_r1mbfSSwKWdcFdAU.json @@ -80,8 +80,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "img": "icons/skills/melee/strike-blade-hooked-orange-blue.webp", "type": "attack", @@ -466,8 +466,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -493,7 +493,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json b/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json index db00c5e6..1615dec8 100644 --- a/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json +++ b/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -274,8 +274,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -300,7 +300,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -352,8 +352,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -378,7 +378,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Hydra_MI126iMOOobQ1Obn.json b/src/packs/adversaries/adversary_Hydra_MI126iMOOobQ1Obn.json index a12aaad8..7e71af98 100644 --- a/src/packs/adversaries/adversary_Hydra_MI126iMOOobQ1Obn.json +++ b/src/packs/adversaries/adversary_Hydra_MI126iMOOobQ1Obn.json @@ -75,8 +75,8 @@ }, "range": "close", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -300,8 +300,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -326,7 +326,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -394,8 +394,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -420,7 +420,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Bandit_5Lh1T0zaT8Pkr2U2.json b/src/packs/adversaries/adversary_Jagged_Knife_Bandit_5Lh1T0zaT8Pkr2U2.json index ae359eaf..bfbff494 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Bandit_5Lh1T0zaT8Pkr2U2.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Bandit_5Lh1T0zaT8Pkr2U2.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/daggers/dagger-twin-green.webp", "type": "attack", @@ -272,8 +272,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -299,7 +299,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json b/src/packs/adversaries/adversary_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json index e56f7af5..c6b2554e 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Hexer_MbBPIOxaxXYNApXz.json @@ -80,8 +80,8 @@ }, "img": "icons/weapons/staves/staff-blue-jewel.webp", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -347,8 +347,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -374,7 +374,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Kneebreaker_CBKixLH3yhivZZuL.json b/src/packs/adversaries/adversary_Jagged_Knife_Kneebreaker_CBKixLH3yhivZZuL.json index d8115fd9..3ce6a165 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Kneebreaker_CBKixLH3yhivZZuL.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Kneebreaker_CBKixLH3yhivZZuL.json @@ -85,8 +85,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -112,7 +112,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -277,7 +277,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json b/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json index a52ec1c9..cfcdea8b 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json @@ -64,8 +64,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -92,7 +92,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Lieutenant_aTljstqteGoLpCBq.json b/src/packs/adversaries/adversary_Jagged_Knife_Lieutenant_aTljstqteGoLpCBq.json index c139d76f..8820f30c 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Lieutenant_aTljstqteGoLpCBq.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Lieutenant_aTljstqteGoLpCBq.json @@ -80,8 +80,8 @@ }, "range": "close", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -364,8 +364,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -391,7 +391,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -416,7 +416,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -491,8 +491,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -518,7 +518,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Shadow_XF4tYTq9nPJAy2ox.json b/src/packs/adversaries/adversary_Jagged_Knife_Shadow_XF4tYTq9nPJAy2ox.json index bca035c1..81b95d8b 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Shadow_XF4tYTq9nPJAy2ox.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Shadow_XF4tYTq9nPJAy2ox.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -246,8 +246,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -273,7 +273,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Sniper_1zuyof1XuIfi3aMG.json b/src/packs/adversaries/adversary_Jagged_Knife_Sniper_1zuyof1XuIfi3aMG.json index 6fd02cb5..62253d69 100644 --- a/src/packs/adversaries/adversary_Jagged_Knife_Sniper_1zuyof1XuIfi3aMG.json +++ b/src/packs/adversaries/adversary_Jagged_Knife_Sniper_1zuyof1XuIfi3aMG.json @@ -81,8 +81,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -246,8 +246,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -273,7 +273,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Juvenile_Flickerfly_MYXmTx2FHcIjdfYZ.json b/src/packs/adversaries/adversary_Juvenile_Flickerfly_MYXmTx2FHcIjdfYZ.json index 86d69c37..562b42b8 100644 --- a/src/packs/adversaries/adversary_Juvenile_Flickerfly_MYXmTx2FHcIjdfYZ.json +++ b/src/packs/adversaries/adversary_Juvenile_Flickerfly_MYXmTx2FHcIjdfYZ.json @@ -75,8 +75,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -296,7 +296,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -417,7 +417,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -490,8 +490,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -516,7 +516,7 @@ }, "type": [] }, - { + "hope": { "value": { "custom": { "enabled": true, @@ -541,7 +541,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Knight_of_the_Realm_7ai2opemrclQe3VF.json b/src/packs/adversaries/adversary_Knight_of_the_Realm_7ai2opemrclQe3VF.json index 71cb7a8d..01435922 100644 --- a/src/packs/adversaries/adversary_Knight_of_the_Realm_7ai2opemrclQe3VF.json +++ b/src/packs/adversaries/adversary_Knight_of_the_Realm_7ai2opemrclQe3VF.json @@ -90,8 +90,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -117,7 +117,7 @@ }, "base": false } - ] + } }, "range": "melee", "type": "attack", @@ -392,8 +392,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -417,7 +417,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -442,7 +442,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json b/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json index 6f6f6edc..4494e0e8 100644 --- a/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json +++ b/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json @@ -80,8 +80,8 @@ }, "range": "close", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/tentacles/tentacles-octopus-black-pink.webp", "type": "attack", @@ -327,7 +327,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -456,8 +456,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -483,7 +483,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -557,8 +557,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -584,7 +584,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json b/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json index 3143fbe3..25740e52 100644 --- a/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json +++ b/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "range": "melee", "type": "attack", @@ -244,8 +244,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -269,7 +269,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -342,7 +342,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Master_Assassin_dNta0cUzr96xcFhf.json b/src/packs/adversaries/adversary_Master_Assassin_dNta0cUzr96xcFhf.json index 3cec6e0b..2131023a 100644 --- a/src/packs/adversaries/adversary_Master_Assassin_dNta0cUzr96xcFhf.json +++ b/src/packs/adversaries/adversary_Master_Assassin_dNta0cUzr96xcFhf.json @@ -86,8 +86,8 @@ }, "range": "close", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -113,7 +113,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -254,8 +254,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false, @@ -283,7 +283,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, @@ -469,8 +469,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -496,7 +496,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Merchant_Al3w2CgjfdT3p9ma.json b/src/packs/adversaries/adversary_Merchant_Al3w2CgjfdT3p9ma.json index 880b1a6e..482ba727 100644 --- a/src/packs/adversaries/adversary_Merchant_Al3w2CgjfdT3p9ma.json +++ b/src/packs/adversaries/adversary_Merchant_Al3w2CgjfdT3p9ma.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/clubs/club-baton-blue.webp", "type": "attack", @@ -272,8 +272,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -298,7 +298,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Merchant_Baron_Vy02IhGhkJLuezu4.json b/src/packs/adversaries/adversary_Merchant_Baron_Vy02IhGhkJLuezu4.json index 15c0aeb9..7ace67ac 100644 --- a/src/packs/adversaries/adversary_Merchant_Baron_Vy02IhGhkJLuezu4.json +++ b/src/packs/adversaries/adversary_Merchant_Baron_Vy02IhGhkJLuezu4.json @@ -85,8 +85,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -112,7 +112,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -249,8 +249,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -275,7 +275,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Minor_Chaos_Elemental_sRn4bqerfARvhgSV.json b/src/packs/adversaries/adversary_Minor_Chaos_Elemental_sRn4bqerfARvhgSV.json index 57dc2980..21564421 100644 --- a/src/packs/adversaries/adversary_Minor_Chaos_Elemental_sRn4bqerfARvhgSV.json +++ b/src/packs/adversaries/adversary_Minor_Chaos_Elemental_sRn4bqerfARvhgSV.json @@ -74,8 +74,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "range": "close", "type": "attack", @@ -317,8 +317,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -343,7 +343,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -458,8 +458,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -485,7 +485,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -567,8 +567,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -594,7 +594,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Minor_Demon_3tqCjDwJAQ7JKqMb.json b/src/packs/adversaries/adversary_Minor_Demon_3tqCjDwJAQ7JKqMb.json index 0fceeba1..8f5f51f3 100644 --- a/src/packs/adversaries/adversary_Minor_Demon_3tqCjDwJAQ7JKqMb.json +++ b/src/packs/adversaries/adversary_Minor_Demon_3tqCjDwJAQ7JKqMb.json @@ -74,8 +74,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -299,8 +299,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -325,7 +325,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -385,8 +385,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -412,7 +412,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -544,8 +544,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -571,7 +571,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Minor_Fire_Elemental_DscWkNVoHak6P4hh.json b/src/packs/adversaries/adversary_Minor_Fire_Elemental_DscWkNVoHak6P4hh.json index 2980a141..0cc9703c 100644 --- a/src/packs/adversaries/adversary_Minor_Fire_Elemental_DscWkNVoHak6P4hh.json +++ b/src/packs/adversaries/adversary_Minor_Fire_Elemental_DscWkNVoHak6P4hh.json @@ -75,8 +75,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -297,8 +297,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -324,7 +324,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -405,8 +405,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -432,7 +432,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -511,8 +511,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -537,7 +537,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -578,8 +578,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -604,7 +604,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -674,8 +674,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -701,7 +701,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json b/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json index f05ba5fc..b2217e66 100644 --- a/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json +++ b/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json @@ -66,8 +66,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -94,7 +94,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", diff --git a/src/packs/adversaries/adversary_Minotaur_Wrecker_rM9qCIYeWg9I0B4l.json b/src/packs/adversaries/adversary_Minotaur_Wrecker_rM9qCIYeWg9I0B4l.json index 85981374..b4734967 100644 --- a/src/packs/adversaries/adversary_Minotaur_Wrecker_rM9qCIYeWg9I0B4l.json +++ b/src/packs/adversaries/adversary_Minotaur_Wrecker_rM9qCIYeWg9I0B4l.json @@ -74,8 +74,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/axes/axe-double.webp", "type": "attack", @@ -300,8 +300,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -327,7 +327,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -373,8 +373,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -401,7 +401,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -454,8 +454,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -479,7 +479,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, diff --git a/src/packs/adversaries/adversary_Monarch_yx0vK2yfNVZKWUUi.json b/src/packs/adversaries/adversary_Monarch_yx0vK2yfNVZKWUUi.json index 5320a0ed..8d5845a9 100644 --- a/src/packs/adversaries/adversary_Monarch_yx0vK2yfNVZKWUUi.json +++ b/src/packs/adversaries/adversary_Monarch_yx0vK2yfNVZKWUUi.json @@ -85,8 +85,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -112,7 +112,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -381,8 +381,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": false, @@ -408,7 +408,7 @@ }, "type": [] } - ], + }, "includeBase": false, "direct": false }, diff --git a/src/packs/adversaries/adversary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json b/src/packs/adversaries/adversary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json index 01218718..2eaa9619 100644 --- a/src/packs/adversaries/adversary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json +++ b/src/packs/adversaries/adversary_Mortal_Hunter_mVV7a7KQAORoPMgZ.json @@ -81,8 +81,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -244,8 +244,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -270,7 +270,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -361,8 +361,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false, @@ -390,7 +390,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, @@ -518,7 +518,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -591,8 +591,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -618,7 +618,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json b/src/packs/adversaries/adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json index 1a109aa4..c5feed37 100644 --- a/src/packs/adversaries/adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json +++ b/src/packs/adversaries/adversary_Oak_Treant_XK78QUfY8c8Go8Uv.json @@ -73,8 +73,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "multiplier": "flat", "flatMultiplier": 3, @@ -100,7 +100,7 @@ }, "base": false } - ] + } }, "img": "icons/skills/melee/blood-slash-foam-red.webp", "type": "attack", @@ -273,8 +273,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false, @@ -302,7 +302,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Oracle_of_Doom_befIqd5IYKg6eUz2.json b/src/packs/adversaries/adversary_Oracle_of_Doom_befIqd5IYKg6eUz2.json index 66fa5ba1..343cfdc1 100644 --- a/src/packs/adversaries/adversary_Oracle_of_Doom_befIqd5IYKg6eUz2.json +++ b/src/packs/adversaries/adversary_Oracle_of_Doom_befIqd5IYKg6eUz2.json @@ -80,8 +80,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "img": "icons/magic/symbols/rune-sigil-rough-white-teal.webp", "type": "attack", @@ -244,8 +244,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -270,7 +270,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -322,8 +322,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -348,7 +348,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -407,8 +407,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -434,7 +434,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -514,7 +514,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -620,8 +620,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -647,7 +647,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Outer_Realms_Abomination_A0SeeDzwjvqOsyof.json b/src/packs/adversaries/adversary_Outer_Realms_Abomination_A0SeeDzwjvqOsyof.json index 83edda8a..0f73354e 100644 --- a/src/packs/adversaries/adversary_Outer_Realms_Abomination_A0SeeDzwjvqOsyof.json +++ b/src/packs/adversaries/adversary_Outer_Realms_Abomination_A0SeeDzwjvqOsyof.json @@ -74,8 +74,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/tentacles/tentacle-earth-green.webp", "type": "attack", @@ -263,7 +263,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -384,7 +384,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -509,7 +509,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Outer_Realms_Corrupter_ms6nuOl3NFkhPj1k.json b/src/packs/adversaries/adversary_Outer_Realms_Corrupter_ms6nuOl3NFkhPj1k.json index 83fbf4fa..6c78dced 100644 --- a/src/packs/adversaries/adversary_Outer_Realms_Corrupter_ms6nuOl3NFkhPj1k.json +++ b/src/packs/adversaries/adversary_Outer_Realms_Corrupter_ms6nuOl3NFkhPj1k.json @@ -75,8 +75,8 @@ }, "img": "icons/creatures/tentacles/tentacles-thing-green.webp", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -238,8 +238,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -264,7 +264,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -316,8 +316,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -342,7 +342,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json b/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json index 5347bf49..5a7a605a 100644 --- a/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json +++ b/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json @@ -60,8 +60,8 @@ }, "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -88,7 +88,7 @@ }, "base": false } - ] + } }, "name": "Claws and Teeth", "roll": { diff --git a/src/packs/adversaries/adversary_Patchwork_Zombie_Hulk_EQTOAOUrkIvS2z88.json b/src/packs/adversaries/adversary_Patchwork_Zombie_Hulk_EQTOAOUrkIvS2z88.json index b63e8cb7..999f89b9 100644 --- a/src/packs/adversaries/adversary_Patchwork_Zombie_Hulk_EQTOAOUrkIvS2z88.json +++ b/src/packs/adversaries/adversary_Patchwork_Zombie_Hulk_EQTOAOUrkIvS2z88.json @@ -85,8 +85,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -112,7 +112,7 @@ }, "base": false } - ] + } }, "img": "icons/commodities/biological/hand-clawed-blue.webp", "type": "attack", @@ -252,8 +252,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -281,7 +281,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -360,8 +360,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -386,7 +386,7 @@ }, "type": [] }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -411,7 +411,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -487,8 +487,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -513,7 +513,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Perfected_Zombie_CP6iRfHdyFWniTHY.json b/src/packs/adversaries/adversary_Perfected_Zombie_CP6iRfHdyFWniTHY.json index 8174e9fd..a6ce78d5 100644 --- a/src/packs/adversaries/adversary_Perfected_Zombie_CP6iRfHdyFWniTHY.json +++ b/src/packs/adversaries/adversary_Perfected_Zombie_CP6iRfHdyFWniTHY.json @@ -75,8 +75,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -238,8 +238,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -264,7 +264,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -348,8 +348,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -375,7 +375,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Petty_Noble_wycLpvebWdUqRhpP.json b/src/packs/adversaries/adversary_Petty_Noble_wycLpvebWdUqRhpP.json index db284f40..bee77686 100644 --- a/src/packs/adversaries/adversary_Petty_Noble_wycLpvebWdUqRhpP.json +++ b/src/packs/adversaries/adversary_Petty_Noble_wycLpvebWdUqRhpP.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", diff --git a/src/packs/adversaries/adversary_Pirate_Captain_OROJbjsqagVh7ECV.json b/src/packs/adversaries/adversary_Pirate_Captain_OROJbjsqagVh7ECV.json index 5b00ec60..428a9607 100644 --- a/src/packs/adversaries/adversary_Pirate_Captain_OROJbjsqagVh7ECV.json +++ b/src/packs/adversaries/adversary_Pirate_Captain_OROJbjsqagVh7ECV.json @@ -85,8 +85,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -112,7 +112,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -251,8 +251,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -277,7 +277,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -401,8 +401,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -426,7 +426,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -501,8 +501,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -528,7 +528,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Pirate_Raiders_5YgEajn0wa4i85kC.json b/src/packs/adversaries/adversary_Pirate_Raiders_5YgEajn0wa4i85kC.json index 41f79b49..94137c2f 100644 --- a/src/packs/adversaries/adversary_Pirate_Raiders_5YgEajn0wa4i85kC.json +++ b/src/packs/adversaries/adversary_Pirate_Raiders_5YgEajn0wa4i85kC.json @@ -74,8 +74,8 @@ "description": "

      Seafaring scoundrels moving in a ravaging pack.

      ", "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ "resultBased": false, "base": false } - ] + } }, "name": "Cutlass", "img": "icons/weapons/swords/scimitar-worn-blue.webp", @@ -272,8 +272,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -298,7 +298,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Pirate_Tough_mhcVkVFrzIJ18FDm.json b/src/packs/adversaries/adversary_Pirate_Tough_mhcVkVFrzIJ18FDm.json index 69b59211..2817b191 100644 --- a/src/packs/adversaries/adversary_Pirate_Tough_mhcVkVFrzIJ18FDm.json +++ b/src/packs/adversaries/adversary_Pirate_Tough_mhcVkVFrzIJ18FDm.json @@ -67,8 +67,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -94,7 +94,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", @@ -269,8 +269,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -295,7 +295,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -347,8 +347,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -374,7 +374,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json b/src/packs/adversaries/adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json index cd8a44b6..f74da475 100644 --- a/src/packs/adversaries/adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json +++ b/src/packs/adversaries/adversary_Red_Ooze_9rVlbJVrDNn1x7PS.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/slimes/slime-movement-dripping-pseudopods-green.webp", "type": "attack", @@ -272,8 +272,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -299,7 +299,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -350,8 +350,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -377,7 +377,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json b/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json index 7672961c..6755d27f 100644 --- a/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json +++ b/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json @@ -57,8 +57,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -85,7 +85,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/abilities/mouth-teeth-sharp.webp", "type": "attack", diff --git a/src/packs/adversaries/adversary_Royal_Advisor_EtLJiTsilPPZvLUX.json b/src/packs/adversaries/adversary_Royal_Advisor_EtLJiTsilPPZvLUX.json index 8593ec01..e215a444 100644 --- a/src/packs/adversaries/adversary_Royal_Advisor_EtLJiTsilPPZvLUX.json +++ b/src/packs/adversaries/adversary_Royal_Advisor_EtLJiTsilPPZvLUX.json @@ -86,8 +86,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -113,7 +113,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -249,8 +249,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -275,7 +275,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Secret_Keeper_sLAccjvCWfeedbpI.json b/src/packs/adversaries/adversary_Secret_Keeper_sLAccjvCWfeedbpI.json index d17c3f86..93bfef2c 100644 --- a/src/packs/adversaries/adversary_Secret_Keeper_sLAccjvCWfeedbpI.json +++ b/src/packs/adversaries/adversary_Secret_Keeper_sLAccjvCWfeedbpI.json @@ -85,8 +85,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -112,7 +112,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/staves/staff-ornate-purple.webp", "type": "attack", @@ -256,7 +256,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -336,8 +336,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -362,7 +362,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json b/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json index 514be8f5..ed6d7775 100644 --- a/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json +++ b/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json @@ -61,8 +61,8 @@ "attack": { "name": "Longsword", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -89,7 +89,7 @@ }, "base": false } - ] + } }, "roll": { "bonus": 3, diff --git a/src/packs/adversaries/adversary_Shambling_Zombie_2nXz4ilAY4xuhKLm.json b/src/packs/adversaries/adversary_Shambling_Zombie_2nXz4ilAY4xuhKLm.json index 7c3925ac..5161f8e2 100644 --- a/src/packs/adversaries/adversary_Shambling_Zombie_2nXz4ilAY4xuhKLm.json +++ b/src/packs/adversaries/adversary_Shambling_Zombie_2nXz4ilAY4xuhKLm.json @@ -74,8 +74,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -266,8 +266,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -292,7 +292,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Shark_YmVAkdNsyuXWTtYp.json b/src/packs/adversaries/adversary_Shark_YmVAkdNsyuXWTtYp.json index e385a6c5..f9bad7ea 100644 --- a/src/packs/adversaries/adversary_Shark_YmVAkdNsyuXWTtYp.json +++ b/src/packs/adversaries/adversary_Shark_YmVAkdNsyuXWTtYp.json @@ -75,8 +75,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -238,8 +238,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -264,7 +264,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -316,8 +316,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -343,7 +343,7 @@ } } }, - { + "armor": { "value": { "custom": { "enabled": true, @@ -368,7 +368,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Siren_BK4jwyXSRx7IOQiO.json b/src/packs/adversaries/adversary_Siren_BK4jwyXSRx7IOQiO.json index 05ca67a9..38f77982 100644 --- a/src/packs/adversaries/adversary_Siren_BK4jwyXSRx7IOQiO.json +++ b/src/packs/adversaries/adversary_Siren_BK4jwyXSRx7IOQiO.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/abilities/mouth-teeth-sharp.webp", "range": "melee", @@ -244,8 +244,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -271,7 +271,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -350,7 +350,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Skeleton_Archer_7X5q7a6ueeHs5oA9.json b/src/packs/adversaries/adversary_Skeleton_Archer_7X5q7a6ueeHs5oA9.json index 9d837ac0..f0dde9f0 100644 --- a/src/packs/adversaries/adversary_Skeleton_Archer_7X5q7a6ueeHs5oA9.json +++ b/src/packs/adversaries/adversary_Skeleton_Archer_7X5q7a6ueeHs5oA9.json @@ -74,8 +74,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/bows/shortbow-leather.webp", "type": "attack", @@ -310,8 +310,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -337,7 +337,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json b/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json index 4013d7fe..e4cbab5e 100644 --- a/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json +++ b/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json @@ -58,8 +58,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -86,7 +86,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", diff --git a/src/packs/adversaries/adversary_Skeleton_Knight_Q9LaVTyXF9NF12C7.json b/src/packs/adversaries/adversary_Skeleton_Knight_Q9LaVTyXF9NF12C7.json index 3c26dd28..8b2042f3 100644 --- a/src/packs/adversaries/adversary_Skeleton_Knight_Q9LaVTyXF9NF12C7.json +++ b/src/packs/adversaries/adversary_Skeleton_Knight_Q9LaVTyXF9NF12C7.json @@ -74,8 +74,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -240,8 +240,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -266,7 +266,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -326,8 +326,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -353,7 +353,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -378,7 +378,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -452,8 +452,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -479,7 +479,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Skeleton_Warrior_10YIQl0lvCJXZLfX.json b/src/packs/adversaries/adversary_Skeleton_Warrior_10YIQl0lvCJXZLfX.json index 28003d5c..726b06e1 100644 --- a/src/packs/adversaries/adversary_Skeleton_Warrior_10YIQl0lvCJXZLfX.json +++ b/src/packs/adversaries/adversary_Skeleton_Warrior_10YIQl0lvCJXZLfX.json @@ -73,8 +73,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -100,7 +100,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/swords/sword-guard-brass-worn.webp", "type": "attack", @@ -310,7 +310,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Spectral_Archer_5tCkhnBByUIN5UdG.json b/src/packs/adversaries/adversary_Spectral_Archer_5tCkhnBByUIN5UdG.json index e6cc30f7..5b9cbb65 100644 --- a/src/packs/adversaries/adversary_Spectral_Archer_5tCkhnBByUIN5UdG.json +++ b/src/packs/adversaries/adversary_Spectral_Archer_5tCkhnBByUIN5UdG.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "range": "far", "type": "attack", @@ -343,8 +343,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -370,7 +370,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Spectral_Captain_65cSO3EQEh6ZH6Xk.json b/src/packs/adversaries/adversary_Spectral_Captain_65cSO3EQEh6ZH6Xk.json index b70a5d53..0572e018 100644 --- a/src/packs/adversaries/adversary_Spectral_Captain_65cSO3EQEh6ZH6Xk.json +++ b/src/packs/adversaries/adversary_Spectral_Captain_65cSO3EQEh6ZH6Xk.json @@ -76,8 +76,8 @@ "name": "Longbow", "img": "icons/weapons/bows/longbow-recurve-skull-brown.webp", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -103,7 +103,7 @@ }, "base": false } - ] + } }, "roll": { "bonus": 3, @@ -456,8 +456,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -483,7 +483,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Spectral_Guardian_UFVGl1osOsJTneLf.json b/src/packs/adversaries/adversary_Spectral_Guardian_UFVGl1osOsJTneLf.json index 577a7d25..85893254 100644 --- a/src/packs/adversaries/adversary_Spectral_Guardian_UFVGl1osOsJTneLf.json +++ b/src/packs/adversaries/adversary_Spectral_Guardian_UFVGl1osOsJTneLf.json @@ -81,8 +81,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -350,8 +350,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -377,7 +377,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -402,7 +402,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Spellblade_ldbWEL7uZs84vyrR.json b/src/packs/adversaries/adversary_Spellblade_ldbWEL7uZs84vyrR.json index 13d6ed84..90c7d68f 100644 --- a/src/packs/adversaries/adversary_Spellblade_ldbWEL7uZs84vyrR.json +++ b/src/packs/adversaries/adversary_Spellblade_ldbWEL7uZs84vyrR.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -108,7 +108,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -280,8 +280,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -307,7 +307,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -439,8 +439,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -466,7 +466,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Spy_8zlynOhnVA59KpKT.json b/src/packs/adversaries/adversary_Spy_8zlynOhnVA59KpKT.json index 5affdc44..0e160edb 100644 --- a/src/packs/adversaries/adversary_Spy_8zlynOhnVA59KpKT.json +++ b/src/packs/adversaries/adversary_Spy_8zlynOhnVA59KpKT.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/weapons/daggers/dagger-curved-purple.webp", "range": "melee", @@ -329,8 +329,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": false @@ -354,7 +354,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Stag_Knight_KGVwnLq85ywP9xvB.json b/src/packs/adversaries/adversary_Stag_Knight_KGVwnLq85ywP9xvB.json index 603182cc..e7738a46 100644 --- a/src/packs/adversaries/adversary_Stag_Knight_KGVwnLq85ywP9xvB.json +++ b/src/packs/adversaries/adversary_Stag_Knight_KGVwnLq85ywP9xvB.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -244,8 +244,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -271,7 +271,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -330,7 +330,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -410,8 +410,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -435,7 +435,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Stonewraith_3aAS2Qm3R6cgaYfE.json b/src/packs/adversaries/adversary_Stonewraith_3aAS2Qm3R6cgaYfE.json index d04f41fd..c50c426d 100644 --- a/src/packs/adversaries/adversary_Stonewraith_3aAS2Qm3R6cgaYfE.json +++ b/src/packs/adversaries/adversary_Stonewraith_3aAS2Qm3R6cgaYfE.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "range": "melee", "type": "attack", @@ -276,8 +276,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -301,7 +301,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -435,8 +435,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -462,7 +462,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -573,8 +573,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -600,7 +600,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Swarm_of_Rats_qNgs3AbLyJrY19nt.json b/src/packs/adversaries/adversary_Swarm_of_Rats_qNgs3AbLyJrY19nt.json index 014b3dc6..28d5dabe 100644 --- a/src/packs/adversaries/adversary_Swarm_of_Rats_qNgs3AbLyJrY19nt.json +++ b/src/packs/adversaries/adversary_Swarm_of_Rats_qNgs3AbLyJrY19nt.json @@ -68,8 +68,8 @@ "description": "

      A skittering mass of ordinary rodents moving as one like a ravenous wave.

      ", "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -95,7 +95,7 @@ "resultBased": false, "base": false } - ] + } }, "name": "Claws", "img": "icons/creatures/claws/claw-straight-brown.webp", diff --git a/src/packs/adversaries/adversary_Sylvan_Soldier_VtFBt9XBE0WrGGxP.json b/src/packs/adversaries/adversary_Sylvan_Soldier_VtFBt9XBE0WrGGxP.json index 2ec5e924..f3ce03c3 100644 --- a/src/packs/adversaries/adversary_Sylvan_Soldier_VtFBt9XBE0WrGGxP.json +++ b/src/packs/adversaries/adversary_Sylvan_Soldier_VtFBt9XBE0WrGGxP.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -246,8 +246,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -273,7 +273,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -353,8 +353,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -380,7 +380,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Tangle_Bramble_Swarm_PKSXFuaIHUCoH63A.json b/src/packs/adversaries/adversary_Tangle_Bramble_Swarm_PKSXFuaIHUCoH63A.json index 40297eb6..f8f93cf2 100644 --- a/src/packs/adversaries/adversary_Tangle_Bramble_Swarm_PKSXFuaIHUCoH63A.json +++ b/src/packs/adversaries/adversary_Tangle_Bramble_Swarm_PKSXFuaIHUCoH63A.json @@ -99,8 +99,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -126,7 +126,7 @@ "resultBased": false, "base": false } - ], + }, "includeBase": false }, "description": "", @@ -309,8 +309,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false, @@ -338,7 +338,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, diff --git a/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json b/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json index 33afaa3a..c36502de 100644 --- a/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json +++ b/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json @@ -94,8 +94,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -122,7 +122,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/adversaries/adversary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json b/src/packs/adversaries/adversary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json index 2b3867aa..9470502c 100644 --- a/src/packs/adversaries/adversary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json +++ b/src/packs/adversaries/adversary_Tiny_Green_Ooze_aLkLFuVoKz2NLoBK.json @@ -57,8 +57,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -84,7 +84,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/slimes/slime-movement-dripping-pseudopods-green.webp", "type": "attack", @@ -234,8 +234,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -260,7 +260,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Tiny_Red_Ooze_1fkLQXVtmILqfJ44.json b/src/packs/adversaries/adversary_Tiny_Red_Ooze_1fkLQXVtmILqfJ44.json index 116fffba..28d6490e 100644 --- a/src/packs/adversaries/adversary_Tiny_Red_Ooze_1fkLQXVtmILqfJ44.json +++ b/src/packs/adversaries/adversary_Tiny_Red_Ooze_1fkLQXVtmILqfJ44.json @@ -58,8 +58,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -85,7 +85,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -236,8 +236,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -263,7 +263,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json b/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json index ad9d8107..c6c11d36 100644 --- a/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json +++ b/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json @@ -66,8 +66,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -94,7 +94,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", diff --git a/src/packs/adversaries/adversary_Vampire_WWyUp6Mxl1S3KYUG.json b/src/packs/adversaries/adversary_Vampire_WWyUp6Mxl1S3KYUG.json index 4f51cd79..6ba4935a 100644 --- a/src/packs/adversaries/adversary_Vampire_WWyUp6Mxl1S3KYUG.json +++ b/src/packs/adversaries/adversary_Vampire_WWyUp6Mxl1S3KYUG.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -244,8 +244,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -269,7 +269,7 @@ } } }, - { + "hope": { "value": { "custom": { "enabled": true, @@ -294,7 +294,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -319,7 +319,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Vault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json b/src/packs/adversaries/adversary_Vault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json index 24db9c55..97c493a8 100644 --- a/src/packs/adversaries/adversary_Vault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json +++ b/src/packs/adversaries/adversary_Vault_Guardian_Gaoler_JqYraOqNmmhHk4Yy.json @@ -75,8 +75,8 @@ }, "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -270,7 +270,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Vault_Guardian_Sentinel_FVgYb28fhxlVcGwA.json b/src/packs/adversaries/adversary_Vault_Guardian_Sentinel_FVgYb28fhxlVcGwA.json index e4098e93..d4fa0340 100644 --- a/src/packs/adversaries/adversary_Vault_Guardian_Sentinel_FVgYb28fhxlVcGwA.json +++ b/src/packs/adversaries/adversary_Vault_Guardian_Sentinel_FVgYb28fhxlVcGwA.json @@ -75,8 +75,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -373,8 +373,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -400,7 +400,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -474,8 +474,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -501,7 +501,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Vault_Guardian_Turret_c5hGdvY5UnSjlHws.json b/src/packs/adversaries/adversary_Vault_Guardian_Turret_c5hGdvY5UnSjlHws.json index ab683607..d723df80 100644 --- a/src/packs/adversaries/adversary_Vault_Guardian_Turret_c5hGdvY5UnSjlHws.json +++ b/src/packs/adversaries/adversary_Vault_Guardian_Turret_c5hGdvY5UnSjlHws.json @@ -74,8 +74,8 @@ }, "range": "far", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -101,7 +101,7 @@ }, "base": false } - ] + } }, "img": "icons/commodities/tech/metal-joint.webp", "type": "attack", @@ -429,8 +429,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -456,7 +456,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Volcanic_Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json b/src/packs/adversaries/adversary_Volcanic_Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json index baf84c14..f6a8bc84 100644 --- a/src/packs/adversaries/adversary_Volcanic_Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json +++ b/src/packs/adversaries/adversary_Volcanic_Dragon__Ashen_Tyrant_pMuXGCSOQaxpi5tb.json @@ -67,8 +67,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -94,7 +94,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", @@ -451,8 +451,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -477,7 +477,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -529,8 +529,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -556,7 +556,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -581,7 +581,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -710,8 +710,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -737,7 +737,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Volcanic_Dragon__Molten_Scourge_eArAPuB38CNR0ZIM.json b/src/packs/adversaries/adversary_Volcanic_Dragon__Molten_Scourge_eArAPuB38CNR0ZIM.json index c73b0dda..535d9fc9 100644 --- a/src/packs/adversaries/adversary_Volcanic_Dragon__Molten_Scourge_eArAPuB38CNR0ZIM.json +++ b/src/packs/adversaries/adversary_Volcanic_Dragon__Molten_Scourge_eArAPuB38CNR0ZIM.json @@ -67,8 +67,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -94,7 +94,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", @@ -359,8 +359,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -386,7 +386,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -459,8 +459,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -486,7 +486,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -566,7 +566,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -612,8 +612,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -639,7 +639,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": false @@ -663,7 +663,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -791,8 +791,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -818,7 +818,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, diff --git a/src/packs/adversaries/adversary_Volcanic_Dragon__Obsidian_Predator_ladm7wykhZczYzrQ.json b/src/packs/adversaries/adversary_Volcanic_Dragon__Obsidian_Predator_ladm7wykhZczYzrQ.json index 2e2adbdd..fe8f280f 100644 --- a/src/packs/adversaries/adversary_Volcanic_Dragon__Obsidian_Predator_ladm7wykhZczYzrQ.json +++ b/src/packs/adversaries/adversary_Volcanic_Dragon__Obsidian_Predator_ladm7wykhZczYzrQ.json @@ -67,8 +67,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -94,7 +94,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", @@ -465,8 +465,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -490,7 +490,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -607,8 +607,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -634,7 +634,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -659,7 +659,7 @@ }, "type": [] }, - { + "hope": { "value": { "custom": { "enabled": true, @@ -684,7 +684,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_War_Wizard_noDdT0tsN6FXSmC8.json b/src/packs/adversaries/adversary_War_Wizard_noDdT0tsN6FXSmC8.json index f087c63d..55be8e1a 100644 --- a/src/packs/adversaries/adversary_War_Wizard_noDdT0tsN6FXSmC8.json +++ b/src/packs/adversaries/adversary_War_Wizard_noDdT0tsN6FXSmC8.json @@ -86,8 +86,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -113,7 +113,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -367,8 +367,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -394,7 +394,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -474,8 +474,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -501,7 +501,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -574,8 +574,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -601,7 +601,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Weaponmaster_ZNbQ2jg35LG4t9eH.json b/src/packs/adversaries/adversary_Weaponmaster_ZNbQ2jg35LG4t9eH.json index 55c7cb23..8eaf56f9 100644 --- a/src/packs/adversaries/adversary_Weaponmaster_ZNbQ2jg35LG4t9eH.json +++ b/src/packs/adversaries/adversary_Weaponmaster_ZNbQ2jg35LG4t9eH.json @@ -75,8 +75,8 @@ "img": "icons/weapons/swords/greatsword-guard-gold-worn.webp", "range": "veryClose", "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -102,7 +102,7 @@ }, "base": false } - ] + } }, "type": "attack", "chatDisplay": false @@ -240,8 +240,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -267,7 +267,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -416,8 +416,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -442,7 +442,7 @@ }, "type": [] }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -467,7 +467,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -537,8 +537,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -564,7 +564,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json b/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json index d097f765..9d7f66d0 100644 --- a/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json +++ b/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json @@ -80,8 +80,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ }, "base": false } - ] + } }, "type": "attack", "range": "melee", @@ -253,7 +253,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -440,8 +440,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -467,7 +467,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json b/src/packs/adversaries/adversary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json index 751d73b4..09e76fa8 100644 --- a/src/packs/adversaries/adversary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json +++ b/src/packs/adversaries/adversary_Young_Ice_Dragon_UGPiPLJsPvMTSKEF.json @@ -79,8 +79,8 @@ "type": "attack" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -106,7 +106,7 @@ }, "base": false } - ] + } }, "img": "icons/creatures/claws/claw-scaled-red.webp", "type": "attack", @@ -302,8 +302,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -328,7 +328,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -412,8 +412,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -439,7 +439,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -563,7 +563,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -691,8 +691,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -717,7 +717,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -831,8 +831,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -858,7 +858,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/adversaries/adversary_Zombie_Legion_YhJrP7rTBiRdX5Fp.json b/src/packs/adversaries/adversary_Zombie_Legion_YhJrP7rTBiRdX5Fp.json index 91bdab81..2c3495ff 100644 --- a/src/packs/adversaries/adversary_Zombie_Legion_YhJrP7rTBiRdX5Fp.json +++ b/src/packs/adversaries/adversary_Zombie_Legion_YhJrP7rTBiRdX5Fp.json @@ -68,8 +68,8 @@ "motivesAndTactics": "Consume brain, shred fl esh, surround", "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -95,7 +95,7 @@ "resultBased": false, "base": false } - ] + } }, "name": "Undead Hands", "roll": { diff --git a/src/packs/adversaries/adversary_Zombie_Pack_Nf0v43rtflV56V2T.json b/src/packs/adversaries/adversary_Zombie_Pack_Nf0v43rtflV56V2T.json index 017537ad..f418758a 100644 --- a/src/packs/adversaries/adversary_Zombie_Pack_Nf0v43rtflV56V2T.json +++ b/src/packs/adversaries/adversary_Zombie_Pack_Nf0v43rtflV56V2T.json @@ -68,8 +68,8 @@ "description": "

      A group of shambling corpses instinctively moving together.

      ", "attack": { "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -95,7 +95,7 @@ "resultBased": false, "base": false } - ] + } }, "name": "Bite", "roll": { diff --git a/src/packs/ancestries/feature_Charge_AA2CZlJSWW8GPhrR.json b/src/packs/ancestries/feature_Charge_AA2CZlJSWW8GPhrR.json index f1f7ae35..0af2610a 100644 --- a/src/packs/ancestries/feature_Charge_AA2CZlJSWW8GPhrR.json +++ b/src/packs/ancestries/feature_Charge_AA2CZlJSWW8GPhrR.json @@ -29,8 +29,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -56,7 +56,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/ancestries/feature_Elemental_Breath_sRaE3CgkgjBF1UpV.json b/src/packs/ancestries/feature_Elemental_Breath_sRaE3CgkgjBF1UpV.json index 71ac4438..a660daf5 100644 --- a/src/packs/ancestries/feature_Elemental_Breath_sRaE3CgkgjBF1UpV.json +++ b/src/packs/ancestries/feature_Elemental_Breath_sRaE3CgkgjBF1UpV.json @@ -22,8 +22,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -47,7 +47,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/ancestries/feature_Fungril_Network_9tmeXm623hl4Qnws.json b/src/packs/ancestries/feature_Fungril_Network_9tmeXm623hl4Qnws.json index 9d970a67..87638f37 100644 --- a/src/packs/ancestries/feature_Fungril_Network_9tmeXm623hl4Qnws.json +++ b/src/packs/ancestries/feature_Fungril_Network_9tmeXm623hl4Qnws.json @@ -22,7 +22,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/ancestries/feature_Kick_gpW19TfJk0WWFh1S.json b/src/packs/ancestries/feature_Kick_gpW19TfJk0WWFh1S.json index 89546ded..b363b6c2 100644 --- a/src/packs/ancestries/feature_Kick_gpW19TfJk0WWFh1S.json +++ b/src/packs/ancestries/feature_Kick_gpW19TfJk0WWFh1S.json @@ -31,8 +31,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false, @@ -58,7 +58,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/ancestries/feature_Long_Tongue_oWbdlh51ajn1Q5kL.json b/src/packs/ancestries/feature_Long_Tongue_oWbdlh51ajn1Q5kL.json index aee64a9a..1f1156d7 100644 --- a/src/packs/ancestries/feature_Long_Tongue_oWbdlh51ajn1Q5kL.json +++ b/src/packs/ancestries/feature_Long_Tongue_oWbdlh51ajn1Q5kL.json @@ -29,8 +29,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -54,7 +54,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/ancestries/feature_Luckbringer_8O6SQQMxKWr430QA.json b/src/packs/ancestries/feature_Luckbringer_8O6SQQMxKWr430QA.json index 3c31d62d..36fd73fb 100644 --- a/src/packs/ancestries/feature_Luckbringer_8O6SQQMxKWr430QA.json +++ b/src/packs/ancestries/feature_Luckbringer_8O6SQQMxKWr430QA.json @@ -22,8 +22,8 @@ "recovery": "session" }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -48,7 +48,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/ancestries/feature_Retracting_Claws_Zj69cAeb3NjIa8Hn.json b/src/packs/ancestries/feature_Retracting_Claws_Zj69cAeb3NjIa8Hn.json index 6338548e..b9b000f4 100644 --- a/src/packs/ancestries/feature_Retracting_Claws_Zj69cAeb3NjIa8Hn.json +++ b/src/packs/ancestries/feature_Retracting_Claws_Zj69cAeb3NjIa8Hn.json @@ -22,7 +22,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/ancestries/feature_Tusks_YhxD1ujZpftPu19w.json b/src/packs/ancestries/feature_Tusks_YhxD1ujZpftPu19w.json index 5bd72773..6038f2c6 100644 --- a/src/packs/ancestries/feature_Tusks_YhxD1ujZpftPu19w.json +++ b/src/packs/ancestries/feature_Tusks_YhxD1ujZpftPu19w.json @@ -31,8 +31,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false, @@ -58,7 +58,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/ancestries/feature_Unshakeable_G5pE8FW94V1W9jJx.json b/src/packs/ancestries/feature_Unshakeable_G5pE8FW94V1W9jJx.json index 195b10e8..bf0a241b 100644 --- a/src/packs/ancestries/feature_Unshakeable_G5pE8FW94V1W9jJx.json +++ b/src/packs/ancestries/feature_Unshakeable_G5pE8FW94V1W9jJx.json @@ -22,7 +22,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/beastforms/feature_Demolish_DfBXO8jTchwFG8dZ.json b/src/packs/beastforms/feature_Demolish_DfBXO8jTchwFG8dZ.json index acc7df36..43ba16a8 100644 --- a/src/packs/beastforms/feature_Demolish_DfBXO8jTchwFG8dZ.json +++ b/src/packs/beastforms/feature_Demolish_DfBXO8jTchwFG8dZ.json @@ -27,8 +27,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -52,7 +52,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/beastforms/feature_Elusive_Prey_a7Qvmm14nx9BCysA.json b/src/packs/beastforms/feature_Elusive_Prey_a7Qvmm14nx9BCysA.json index d99a6ab7..a81eb8af 100644 --- a/src/packs/beastforms/feature_Elusive_Prey_a7Qvmm14nx9BCysA.json +++ b/src/packs/beastforms/feature_Elusive_Prey_a7Qvmm14nx9BCysA.json @@ -27,7 +27,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json b/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json index 4dc2c0f7..531b30ea 100644 --- a/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json +++ b/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json @@ -27,8 +27,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -54,7 +54,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/beastforms/feature_Trample_A0lgd6eVEfX6oqSB.json b/src/packs/beastforms/feature_Trample_A0lgd6eVEfX6oqSB.json index 230f6470..4f0ea6c3 100644 --- a/src/packs/beastforms/feature_Trample_A0lgd6eVEfX6oqSB.json +++ b/src/packs/beastforms/feature_Trample_A0lgd6eVEfX6oqSB.json @@ -27,8 +27,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -52,7 +52,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/beastforms/feature_Unyielding_vEAQ4cfsoPmOv2Gg.json b/src/packs/beastforms/feature_Unyielding_vEAQ4cfsoPmOv2Gg.json index 6bfafa79..429b5a1a 100644 --- a/src/packs/beastforms/feature_Unyielding_vEAQ4cfsoPmOv2Gg.json +++ b/src/packs/beastforms/feature_Unyielding_vEAQ4cfsoPmOv2Gg.json @@ -20,7 +20,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/beastforms/feature_Venomous_Strike_uW3853pViM9VAfHb.json b/src/packs/beastforms/feature_Venomous_Strike_uW3853pViM9VAfHb.json index 3b39707d..923e43bd 100644 --- a/src/packs/beastforms/feature_Venomous_Strike_uW3853pViM9VAfHb.json +++ b/src/packs/beastforms/feature_Venomous_Strike_uW3853pViM9VAfHb.json @@ -20,7 +20,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/beastforms/feature_Vicious_Maul_jYUBi7yLHap5ljpa.json b/src/packs/beastforms/feature_Vicious_Maul_jYUBi7yLHap5ljpa.json index 28095ea9..a3494ed0 100644 --- a/src/packs/beastforms/feature_Vicious_Maul_jYUBi7yLHap5ljpa.json +++ b/src/packs/beastforms/feature_Vicious_Maul_jYUBi7yLHap5ljpa.json @@ -27,8 +27,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -53,7 +53,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/beastforms/feature_Webslinger_D73fS1iM4SZPFimu.json b/src/packs/beastforms/feature_Webslinger_D73fS1iM4SZPFimu.json index 9e3a3d93..7cd48d25 100644 --- a/src/packs/beastforms/feature_Webslinger_D73fS1iM4SZPFimu.json +++ b/src/packs/beastforms/feature_Webslinger_D73fS1iM4SZPFimu.json @@ -20,7 +20,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json b/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json index 7b7be61a..81fd08cc 100644 --- a/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json +++ b/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json @@ -29,8 +29,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/classes/feature_Life_Support_lSlvSUHbOoX36q2j.json b/src/packs/classes/feature_Life_Support_lSlvSUHbOoX36q2j.json index 9c4fc450..b788f1f4 100644 --- a/src/packs/classes/feature_Life_Support_lSlvSUHbOoX36q2j.json +++ b/src/packs/classes/feature_Life_Support_lSlvSUHbOoX36q2j.json @@ -31,8 +31,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -57,7 +57,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/classes/feature_Minor_Illusion_cshTYdtz9yoXYYB3.json b/src/packs/classes/feature_Minor_Illusion_cshTYdtz9yoXYYB3.json index fe18f68b..5f4d5fe7 100644 --- a/src/packs/classes/feature_Minor_Illusion_cshTYdtz9yoXYYB3.json +++ b/src/packs/classes/feature_Minor_Illusion_cshTYdtz9yoXYYB3.json @@ -23,7 +23,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json index 953b3a2c..ce1f499f 100644 --- a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json +++ b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json @@ -29,8 +29,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_A_Soldier_s_Bond_Y08dLFuPXsgeRrHi.json b/src/packs/domains/domainCard_A_Soldier_s_Bond_Y08dLFuPXsgeRrHi.json index d3da85b6..09255a76 100644 --- a/src/packs/domains/domainCard_A_Soldier_s_Bond_Y08dLFuPXsgeRrHi.json +++ b/src/packs/domains/domainCard_A_Soldier_s_Bond_Y08dLFuPXsgeRrHi.json @@ -25,8 +25,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -51,7 +51,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Arcane_Reflection_JzSvxy9Mu3RJp1jV.json b/src/packs/domains/domainCard_Arcane_Reflection_JzSvxy9Mu3RJp1jV.json index e6d38e3f..e557b8cd 100644 --- a/src/packs/domains/domainCard_Arcane_Reflection_JzSvxy9Mu3RJp1jV.json +++ b/src/packs/domains/domainCard_Arcane_Reflection_JzSvxy9Mu3RJp1jV.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Armorer_cy8GjBPGc9w9RaGO.json b/src/packs/domains/domainCard_Armorer_cy8GjBPGc9w9RaGO.json index aa9910dc..059fb24c 100644 --- a/src/packs/domains/domainCard_Armorer_cy8GjBPGc9w9RaGO.json +++ b/src/packs/domains/domainCard_Armorer_cy8GjBPGc9w9RaGO.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Banish_AIbHfryMA2Rvs1ut.json b/src/packs/domains/domainCard_Banish_AIbHfryMA2Rvs1ut.json index b637a622..e3c23dbf 100644 --- a/src/packs/domains/domainCard_Banish_AIbHfryMA2Rvs1ut.json +++ b/src/packs/domains/domainCard_Banish_AIbHfryMA2Rvs1ut.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json b/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json index 432f9992..c9ae6071 100644 --- a/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json +++ b/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json @@ -25,8 +25,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -51,7 +51,7 @@ }, "type": [] }, - { + "hope": { "value": { "custom": { "enabled": true, @@ -76,7 +76,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Battle_Hardened_NeEOghgfyDUBTwBG.json b/src/packs/domains/domainCard_Battle_Hardened_NeEOghgfyDUBTwBG.json index dfd0c68d..852cd329 100644 --- a/src/packs/domains/domainCard_Battle_Hardened_NeEOghgfyDUBTwBG.json +++ b/src/packs/domains/domainCard_Battle_Hardened_NeEOghgfyDUBTwBG.json @@ -25,8 +25,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -51,7 +51,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Blink_Out_Qu0iA4s3Xov10Erd.json b/src/packs/domains/domainCard_Blink_Out_Qu0iA4s3Xov10Erd.json index 0b96d99c..617bd27b 100644 --- a/src/packs/domains/domainCard_Blink_Out_Qu0iA4s3Xov10Erd.json +++ b/src/packs/domains/domainCard_Blink_Out_Qu0iA4s3Xov10Erd.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json b/src/packs/domains/domainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json index c1964896..df7d36a4 100644 --- a/src/packs/domains/domainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json +++ b/src/packs/domains/domainCard_Bolt_Beacon_BNevJyGk7hmN7XOY.json @@ -33,8 +33,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -58,7 +58,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json b/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json index 8c531bcd..7977e56a 100644 --- a/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json +++ b/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -51,7 +51,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -131,7 +131,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -177,8 +177,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -204,7 +204,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Exota_oVs2MSC6Uf5GbgEG.json b/src/packs/domains/domainCard_Book_of_Exota_oVs2MSC6Uf5GbgEG.json index 032a2de2..d228d04b 100644 --- a/src/packs/domains/domainCard_Book_of_Exota_oVs2MSC6Uf5GbgEG.json +++ b/src/packs/domains/domainCard_Book_of_Exota_oVs2MSC6Uf5GbgEG.json @@ -25,7 +25,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -100,8 +100,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -127,7 +127,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Grynn_R0LNheiZycZlZzV3.json b/src/packs/domains/domainCard_Book_of_Grynn_R0LNheiZycZlZzV3.json index 05276707..d84a8e18 100644 --- a/src/packs/domains/domainCard_Book_of_Grynn_R0LNheiZycZlZzV3.json +++ b/src/packs/domains/domainCard_Book_of_Grynn_R0LNheiZycZlZzV3.json @@ -76,8 +76,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -103,7 +103,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Homet_gFMx08ogQ8hS2Obi.json b/src/packs/domains/domainCard_Book_of_Homet_gFMx08ogQ8hS2Obi.json index a0102739..f6f048d1 100644 --- a/src/packs/domains/domainCard_Book_of_Homet_gFMx08ogQ8hS2Obi.json +++ b/src/packs/domains/domainCard_Book_of_Homet_gFMx08ogQ8hS2Obi.json @@ -25,7 +25,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -72,7 +72,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json b/src/packs/domains/domainCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json index 71ce49f6..b34fa000 100644 --- a/src/packs/domains/domainCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json +++ b/src/packs/domains/domainCard_Book_of_Illiat_df4iRqQzRntrF6Qw.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -82,8 +82,8 @@ "recovery": "shortRest" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -107,7 +107,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Korvax_cWRFHJdxEZ0M1dAg.json b/src/packs/domains/domainCard_Book_of_Korvax_cWRFHJdxEZ0M1dAg.json index 5bef4922..d97c514b 100644 --- a/src/packs/domains/domainCard_Book_of_Korvax_cWRFHJdxEZ0M1dAg.json +++ b/src/packs/domains/domainCard_Book_of_Korvax_cWRFHJdxEZ0M1dAg.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -77,7 +77,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -130,8 +130,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -157,7 +157,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json b/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json index 05d0a219..f32f380a 100644 --- a/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json +++ b/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "resultBased": false, "value": { "custom": { @@ -50,7 +50,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -101,8 +101,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -128,7 +128,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json b/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json index c809a8e0..88bb759d 100644 --- a/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json +++ b/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -75,7 +75,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json b/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json index e88ccea9..16b7a63b 100644 --- a/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json +++ b/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json @@ -85,7 +85,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Tyfar_1VXzwRbvbBj5bd5V.json b/src/packs/domains/domainCard_Book_of_Tyfar_1VXzwRbvbBj5bd5V.json index 5e5cb054..fcea175c 100644 --- a/src/packs/domains/domainCard_Book_of_Tyfar_1VXzwRbvbBj5bd5V.json +++ b/src/packs/domains/domainCard_Book_of_Tyfar_1VXzwRbvbBj5bd5V.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -51,7 +51,7 @@ } } }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -76,7 +76,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -144,7 +144,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Vagras_aknDDYtN7EObv94t.json b/src/packs/domains/domainCard_Book_of_Vagras_aknDDYtN7EObv94t.json index eadd1550..a5764f48 100644 --- a/src/packs/domains/domainCard_Book_of_Vagras_aknDDYtN7EObv94t.json +++ b/src/packs/domains/domainCard_Book_of_Vagras_aknDDYtN7EObv94t.json @@ -25,7 +25,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -79,7 +79,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -125,7 +125,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Vyola_VOIgm2j2Ijszwc5m.json b/src/packs/domains/domainCard_Book_of_Vyola_VOIgm2j2Ijszwc5m.json index b94cd702..522a8f7c 100644 --- a/src/packs/domains/domainCard_Book_of_Vyola_VOIgm2j2Ijszwc5m.json +++ b/src/packs/domains/domainCard_Book_of_Vyola_VOIgm2j2Ijszwc5m.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json b/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json index 4e08fb0e..17eb4223 100644 --- a/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json +++ b/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Chain_Lightning_0kAVO6rordCfZqYP.json b/src/packs/domains/domainCard_Chain_Lightning_0kAVO6rordCfZqYP.json index 682357cc..c2bf721f 100644 --- a/src/packs/domains/domainCard_Chain_Lightning_0kAVO6rordCfZqYP.json +++ b/src/packs/domains/domainCard_Chain_Lightning_0kAVO6rordCfZqYP.json @@ -33,8 +33,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -60,7 +60,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -107,8 +107,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -134,7 +134,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Champion_s_Edge_rnejRbUQsNGX1GMC.json b/src/packs/domains/domainCard_Champion_s_Edge_rnejRbUQsNGX1GMC.json index be639515..304541e4 100644 --- a/src/packs/domains/domainCard_Champion_s_Edge_rnejRbUQsNGX1GMC.json +++ b/src/packs/domains/domainCard_Champion_s_Edge_rnejRbUQsNGX1GMC.json @@ -33,8 +33,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -59,7 +59,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -109,8 +109,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -135,7 +135,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -185,8 +185,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -213,7 +213,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Chokehold_R5GYUalYXLLFRlNl.json b/src/packs/domains/domainCard_Chokehold_R5GYUalYXLLFRlNl.json index 587e7855..73424902 100644 --- a/src/packs/domains/domainCard_Chokehold_R5GYUalYXLLFRlNl.json +++ b/src/packs/domains/domainCard_Chokehold_R5GYUalYXLLFRlNl.json @@ -65,7 +65,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json b/src/packs/domains/domainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json index 5ffab1e2..e3df986e 100644 --- a/src/packs/domains/domainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json +++ b/src/packs/domains/domainCard_Cinder_Grasp_5EP2Lgf7ojfrc0Is.json @@ -25,8 +25,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -52,7 +52,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -104,8 +104,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -131,7 +131,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Confusing_Aura_R8NDiJXJWmC48WSr.json b/src/packs/domains/domainCard_Confusing_Aura_R8NDiJXJWmC48WSr.json index 859635f3..a261da89 100644 --- a/src/packs/domains/domainCard_Confusing_Aura_R8NDiJXJWmC48WSr.json +++ b/src/packs/domains/domainCard_Confusing_Aura_R8NDiJXJWmC48WSr.json @@ -25,7 +25,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -80,7 +80,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json b/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json index ededde93..d1d1789d 100644 --- a/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json +++ b/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json @@ -31,7 +31,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -119,8 +119,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -148,7 +148,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json b/src/packs/domains/domainCard_Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json index 09dff08a..01d88111 100644 --- a/src/packs/domains/domainCard_Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json +++ b/src/packs/domains/domainCard_Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -49,7 +49,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Counterspell_6dhqo1kzGxejCjHa.json b/src/packs/domains/domainCard_Counterspell_6dhqo1kzGxejCjHa.json index 7d3a74c9..a8d403d8 100644 --- a/src/packs/domains/domainCard_Counterspell_6dhqo1kzGxejCjHa.json +++ b/src/packs/domains/domainCard_Counterspell_6dhqo1kzGxejCjHa.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Critical_Inspiration_ABp9pUfBS69NomTD.json b/src/packs/domains/domainCard_Critical_Inspiration_ABp9pUfBS69NomTD.json index c8013a14..252878ea 100644 --- a/src/packs/domains/domainCard_Critical_Inspiration_ABp9pUfBS69NomTD.json +++ b/src/packs/domains/domainCard_Critical_Inspiration_ABp9pUfBS69NomTD.json @@ -24,7 +24,7 @@ "recovery": "longRest" }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Dark_Whispers_yL2qrSWmTwXVOySH.json b/src/packs/domains/domainCard_Dark_Whispers_yL2qrSWmTwXVOySH.json index 390a2526..7d8ecf40 100644 --- a/src/packs/domains/domainCard_Dark_Whispers_yL2qrSWmTwXVOySH.json +++ b/src/packs/domains/domainCard_Dark_Whispers_yL2qrSWmTwXVOySH.json @@ -31,7 +31,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Death_Grip_x0FVGE1YbfXalJiw.json b/src/packs/domains/domainCard_Death_Grip_x0FVGE1YbfXalJiw.json index 6e7c6b64..214dae17 100644 --- a/src/packs/domains/domainCard_Death_Grip_x0FVGE1YbfXalJiw.json +++ b/src/packs/domains/domainCard_Death_Grip_x0FVGE1YbfXalJiw.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -75,8 +75,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "resultBased": false, "value": { "custom": { @@ -101,7 +101,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -152,8 +152,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -179,7 +179,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Disintegration_Wave_kja5qvh4rdeDBB96.json b/src/packs/domains/domainCard_Disintegration_Wave_kja5qvh4rdeDBB96.json index 4e3c3083..7d7ed27d 100644 --- a/src/packs/domains/domainCard_Disintegration_Wave_kja5qvh4rdeDBB96.json +++ b/src/packs/domains/domainCard_Disintegration_Wave_kja5qvh4rdeDBB96.json @@ -25,7 +25,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json b/src/packs/domains/domainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json index 614f7e32..0a35c95f 100644 --- a/src/packs/domains/domainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json +++ b/src/packs/domains/domainCard_Earthquake_C0qLOwSSvZ6PG3Ws.json @@ -34,8 +34,8 @@ "consumeOnSuccess": true }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -61,7 +61,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Eclipse_62Sj67PdPFzwWVe3.json b/src/packs/domains/domainCard_Eclipse_62Sj67PdPFzwWVe3.json index bd080f0d..c797a148 100644 --- a/src/packs/domains/domainCard_Eclipse_62Sj67PdPFzwWVe3.json +++ b/src/packs/domains/domainCard_Eclipse_62Sj67PdPFzwWVe3.json @@ -25,7 +25,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -76,8 +76,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -102,7 +102,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Encore_klahWDFwihqqEhXP.json b/src/packs/domains/domainCard_Encore_klahWDFwihqqEhXP.json index 23358d47..fabc00e1 100644 --- a/src/packs/domains/domainCard_Encore_klahWDFwihqqEhXP.json +++ b/src/packs/domains/domainCard_Encore_klahWDFwihqqEhXP.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Enrapture_a8lFiKX1o8T924ze.json b/src/packs/domains/domainCard_Enrapture_a8lFiKX1o8T924ze.json index dc574dec..284682fa 100644 --- a/src/packs/domains/domainCard_Enrapture_a8lFiKX1o8T924ze.json +++ b/src/packs/domains/domainCard_Enrapture_a8lFiKX1o8T924ze.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -82,8 +82,8 @@ "recovery": "shortRest" }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -108,7 +108,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Falling_Sky_hZJp9mdkMnqKDROe.json b/src/packs/domains/domainCard_Falling_Sky_hZJp9mdkMnqKDROe.json index ee36e25d..05496132 100644 --- a/src/packs/domains/domainCard_Falling_Sky_hZJp9mdkMnqKDROe.json +++ b/src/packs/domains/domainCard_Falling_Sky_hZJp9mdkMnqKDROe.json @@ -33,8 +33,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -59,7 +59,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Final_Words_Nbw6Jnh1vRZzwHQI.json b/src/packs/domains/domainCard_Final_Words_Nbw6Jnh1vRZzwHQI.json index 757a705a..65d7dd83 100644 --- a/src/packs/domains/domainCard_Final_Words_Nbw6Jnh1vRZzwHQI.json +++ b/src/packs/domains/domainCard_Final_Words_Nbw6Jnh1vRZzwHQI.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Flight_54GUjNuBEy7xdzMz.json b/src/packs/domains/domainCard_Flight_54GUjNuBEy7xdzMz.json index bd8744f7..4d1355ee 100644 --- a/src/packs/domains/domainCard_Flight_54GUjNuBEy7xdzMz.json +++ b/src/packs/domains/domainCard_Flight_54GUjNuBEy7xdzMz.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Forager_06UapZuaA5S6fAKl.json b/src/packs/domains/domainCard_Forager_06UapZuaA5S6fAKl.json index 292961b8..e8919d6b 100644 --- a/src/packs/domains/domainCard_Forager_06UapZuaA5S6fAKl.json +++ b/src/packs/domains/domainCard_Forager_06UapZuaA5S6fAKl.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -91,8 +91,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -117,7 +117,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -185,8 +185,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -211,7 +211,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Forest_Sprites_JrkUMTzaFmQNBHVm.json b/src/packs/domains/domainCard_Forest_Sprites_JrkUMTzaFmQNBHVm.json index feb095a2..4fa469a6 100644 --- a/src/packs/domains/domainCard_Forest_Sprites_JrkUMTzaFmQNBHVm.json +++ b/src/packs/domains/domainCard_Forest_Sprites_JrkUMTzaFmQNBHVm.json @@ -32,7 +32,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Glancing_Blow_nCNCqSH7UgW4O3To.json b/src/packs/domains/domainCard_Glancing_Blow_nCNCqSH7UgW4O3To.json index 70338c03..a0766c1c 100644 --- a/src/packs/domains/domainCard_Glancing_Blow_nCNCqSH7UgW4O3To.json +++ b/src/packs/domains/domainCard_Glancing_Blow_nCNCqSH7UgW4O3To.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json b/src/packs/domains/domainCard_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json index 3c17d4ee..35100187 100644 --- a/src/packs/domains/domainCard_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json +++ b/src/packs/domains/domainCard_Glyph_of_Nightfall_B5HXqYRJiL3xMNKT.json @@ -32,7 +32,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Goad_Them_On_HufF5KzuNfEb9RTi.json b/src/packs/domains/domainCard_Goad_Them_On_HufF5KzuNfEb9RTi.json index 190028ed..db28b8b5 100644 --- a/src/packs/domains/domainCard_Goad_Them_On_HufF5KzuNfEb9RTi.json +++ b/src/packs/domains/domainCard_Goad_Them_On_HufF5KzuNfEb9RTi.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "resultBased": false, "value": { "custom": { @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Gore_and_Glory_3zvjgZ5Od343wHzx.json b/src/packs/domains/domainCard_Gore_and_Glory_3zvjgZ5Od343wHzx.json index 8151beaa..ff648409 100644 --- a/src/packs/domains/domainCard_Gore_and_Glory_3zvjgZ5Od343wHzx.json +++ b/src/packs/domains/domainCard_Gore_and_Glory_3zvjgZ5Od343wHzx.json @@ -25,8 +25,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -51,7 +51,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -93,8 +93,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -119,7 +119,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Grace_Touched_KAuNb51AwhD8KEXk.json b/src/packs/domains/domainCard_Grace_Touched_KAuNb51AwhD8KEXk.json index 346a81f2..2e48f07b 100644 --- a/src/packs/domains/domainCard_Grace_Touched_KAuNb51AwhD8KEXk.json +++ b/src/packs/domains/domainCard_Grace_Touched_KAuNb51AwhD8KEXk.json @@ -53,8 +53,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -79,7 +79,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Ground_Pound_WnGldYhJPDhx8v9X.json b/src/packs/domains/domainCard_Ground_Pound_WnGldYhJPDhx8v9X.json index 56387c50..5663c6b3 100644 --- a/src/packs/domains/domainCard_Ground_Pound_WnGldYhJPDhx8v9X.json +++ b/src/packs/domains/domainCard_Ground_Pound_WnGldYhJPDhx8v9X.json @@ -31,8 +31,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -58,7 +58,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Healing_Field_GlRm1Dxlc0Z1b04o.json b/src/packs/domains/domainCard_Healing_Field_GlRm1Dxlc0Z1b04o.json index 0f60cc32..d918d49f 100644 --- a/src/packs/domains/domainCard_Healing_Field_GlRm1Dxlc0Z1b04o.json +++ b/src/packs/domains/domainCard_Healing_Field_GlRm1Dxlc0Z1b04o.json @@ -33,8 +33,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -59,7 +59,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -117,8 +117,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -143,7 +143,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Healing_Hands_WTlhnQMajc1r8i50.json b/src/packs/domains/domainCard_Healing_Hands_WTlhnQMajc1r8i50.json index 1245fc4b..c771562c 100644 --- a/src/packs/domains/domainCard_Healing_Hands_WTlhnQMajc1r8i50.json +++ b/src/packs/domains/domainCard_Healing_Hands_WTlhnQMajc1r8i50.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -85,8 +85,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -111,7 +111,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -161,8 +161,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -187,7 +187,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -237,8 +237,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -263,7 +263,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -313,8 +313,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -339,7 +339,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Healing_Strike_XtSc0jIJLOoMTMYS.json b/src/packs/domains/domainCard_Healing_Strike_XtSc0jIJLOoMTMYS.json index 26fab1a9..22f7f2c9 100644 --- a/src/packs/domains/domainCard_Healing_Strike_XtSc0jIJLOoMTMYS.json +++ b/src/packs/domains/domainCard_Healing_Strike_XtSc0jIJLOoMTMYS.json @@ -33,8 +33,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -59,7 +59,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Hush_gwmYasmfgXZ7tFS6.json b/src/packs/domains/domainCard_Hush_gwmYasmfgXZ7tFS6.json index 23b9588d..28c41c7e 100644 --- a/src/packs/domains/domainCard_Hush_gwmYasmfgXZ7tFS6.json +++ b/src/packs/domains/domainCard_Hush_gwmYasmfgXZ7tFS6.json @@ -32,7 +32,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json b/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json index ffa0226d..15a59635 100644 --- a/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json +++ b/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json @@ -25,8 +25,8 @@ "consumeOnSuccess": true }, "damage": { - "parts": [ - { + "parts": { + "stress": { "resultBased": false, "value": { "custom": { @@ -51,7 +51,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_I_See_It_Coming_Kp6RejHGimnuoBom.json b/src/packs/domains/domainCard_I_See_It_Coming_Kp6RejHGimnuoBom.json index 8d5f6536..5fa005b6 100644 --- a/src/packs/domains/domainCard_I_See_It_Coming_Kp6RejHGimnuoBom.json +++ b/src/packs/domains/domainCard_I_See_It_Coming_Kp6RejHGimnuoBom.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Inspirational_Words_cWu1o82ZF7GvnbXc.json b/src/packs/domains/domainCard_Inspirational_Words_cWu1o82ZF7GvnbXc.json index 0c28d499..b1f15298 100644 --- a/src/packs/domains/domainCard_Inspirational_Words_cWu1o82ZF7GvnbXc.json +++ b/src/packs/domains/domainCard_Inspirational_Words_cWu1o82ZF7GvnbXc.json @@ -41,8 +41,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -67,7 +67,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -117,8 +117,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -143,7 +143,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -193,8 +193,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -219,7 +219,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Invigoration_X8OfkEoI5gLTRf1B.json b/src/packs/domains/domainCard_Invigoration_X8OfkEoI5gLTRf1B.json index 658e12fd..e3c5436c 100644 --- a/src/packs/domains/domainCard_Invigoration_X8OfkEoI5gLTRf1B.json +++ b/src/packs/domains/domainCard_Invigoration_X8OfkEoI5gLTRf1B.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Invisibility_KHkzA4Zrw8EWN1CH.json b/src/packs/domains/domainCard_Invisibility_KHkzA4Zrw8EWN1CH.json index be46b1c3..5f67ff74 100644 --- a/src/packs/domains/domainCard_Invisibility_KHkzA4Zrw8EWN1CH.json +++ b/src/packs/domains/domainCard_Invisibility_KHkzA4Zrw8EWN1CH.json @@ -32,7 +32,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Know_Thy_Enemy_O38MQMhJWdZnXi6b.json b/src/packs/domains/domainCard_Know_Thy_Enemy_O38MQMhJWdZnXi6b.json index 12308e6b..44885c23 100644 --- a/src/packs/domains/domainCard_Know_Thy_Enemy_O38MQMhJWdZnXi6b.json +++ b/src/packs/domains/domainCard_Know_Thy_Enemy_O38MQMhJWdZnXi6b.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Lean_on_Me_BdePs1ZWpZTZvY1Z.json b/src/packs/domains/domainCard_Lean_on_Me_BdePs1ZWpZTZvY1Z.json index 883e2522..ae8c5b82 100644 --- a/src/packs/domains/domainCard_Lean_on_Me_BdePs1ZWpZTZvY1Z.json +++ b/src/packs/domains/domainCard_Lean_on_Me_BdePs1ZWpZTZvY1Z.json @@ -24,8 +24,8 @@ "recovery": "longRest" }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Manifest_Wall_TtGOtWkbr23VhHfH.json b/src/packs/domains/domainCard_Manifest_Wall_TtGOtWkbr23VhHfH.json index 6e4b4654..02f20956 100644 --- a/src/packs/domains/domainCard_Manifest_Wall_TtGOtWkbr23VhHfH.json +++ b/src/packs/domains/domainCard_Manifest_Wall_TtGOtWkbr23VhHfH.json @@ -33,7 +33,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json b/src/packs/domains/domainCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json index d4df379e..7e925700 100644 --- a/src/packs/domains/domainCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json +++ b/src/packs/domains/domainCard_Mass_Enrapture_ubpixIgZrJXKyM3b.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -82,8 +82,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -108,7 +108,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Mending_Touch_TGjR4vJVNbQRV8zr.json b/src/packs/domains/domainCard_Mending_Touch_TGjR4vJVNbQRV8zr.json index 4cb8c2a2..60367dcc 100644 --- a/src/packs/domains/domainCard_Mending_Touch_TGjR4vJVNbQRV8zr.json +++ b/src/packs/domains/domainCard_Mending_Touch_TGjR4vJVNbQRV8zr.json @@ -33,8 +33,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -59,7 +59,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -109,8 +109,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -135,7 +135,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -194,8 +194,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -220,7 +220,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -279,8 +279,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -305,7 +305,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Midnight_Spirit_FXLsB3QbQvTtqX5B.json b/src/packs/domains/domainCard_Midnight_Spirit_FXLsB3QbQvTtqX5B.json index f97fe53d..f951054e 100644 --- a/src/packs/domains/domainCard_Midnight_Spirit_FXLsB3QbQvTtqX5B.json +++ b/src/packs/domains/domainCard_Midnight_Spirit_FXLsB3QbQvTtqX5B.json @@ -60,8 +60,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -87,7 +87,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Midnight_Touched_uSyGKVxOJcnp28po.json b/src/packs/domains/domainCard_Midnight_Touched_uSyGKVxOJcnp28po.json index 10c42418..b859aafb 100644 --- a/src/packs/domains/domainCard_Midnight_Touched_uSyGKVxOJcnp28po.json +++ b/src/packs/domains/domainCard_Midnight_Touched_uSyGKVxOJcnp28po.json @@ -24,8 +24,8 @@ "recovery": "shortRest" }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Natural_Familiar_Tag303LoRNC5zGgl.json b/src/packs/domains/domainCard_Natural_Familiar_Tag303LoRNC5zGgl.json index 7aa85b0f..c14116d4 100644 --- a/src/packs/domains/domainCard_Natural_Familiar_Tag303LoRNC5zGgl.json +++ b/src/packs/domains/domainCard_Natural_Familiar_Tag303LoRNC5zGgl.json @@ -59,7 +59,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Nature_s_Tongue_atWLorlCOxcrq8WB.json b/src/packs/domains/domainCard_Nature_s_Tongue_atWLorlCOxcrq8WB.json index c722954b..b6c9464a 100644 --- a/src/packs/domains/domainCard_Nature_s_Tongue_atWLorlCOxcrq8WB.json +++ b/src/packs/domains/domainCard_Nature_s_Tongue_atWLorlCOxcrq8WB.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Night_Terror_zcldCuqOg3dphUVI.json b/src/packs/domains/domainCard_Night_Terror_zcldCuqOg3dphUVI.json index 59a0c924..d40b1c43 100644 --- a/src/packs/domains/domainCard_Night_Terror_zcldCuqOg3dphUVI.json +++ b/src/packs/domains/domainCard_Night_Terror_zcldCuqOg3dphUVI.json @@ -24,7 +24,7 @@ "recovery": "longRest" }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -84,8 +84,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -109,7 +109,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Onslaught_I7pNsQ9Yx6mRJX4V.json b/src/packs/domains/domainCard_Onslaught_I7pNsQ9Yx6mRJX4V.json index d2d7361d..5122ec4b 100644 --- a/src/packs/domains/domainCard_Onslaught_I7pNsQ9Yx6mRJX4V.json +++ b/src/packs/domains/domainCard_Onslaught_I7pNsQ9Yx6mRJX4V.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json b/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json index 1b0173d7..9aa2f5b4 100644 --- a/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json +++ b/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Plant_Dominion_9a6xP5pxhVvdugk9.json b/src/packs/domains/domainCard_Plant_Dominion_9a6xP5pxhVvdugk9.json index 64b1e1c2..4b4767fa 100644 --- a/src/packs/domains/domainCard_Plant_Dominion_9a6xP5pxhVvdugk9.json +++ b/src/packs/domains/domainCard_Plant_Dominion_9a6xP5pxhVvdugk9.json @@ -25,7 +25,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Preservation_Blast_1p1cOmbnRd5CoKBp.json b/src/packs/domains/domainCard_Preservation_Blast_1p1cOmbnRd5CoKBp.json index 46dc2bdb..5470c340 100644 --- a/src/packs/domains/domainCard_Preservation_Blast_1p1cOmbnRd5CoKBp.json +++ b/src/packs/domains/domainCard_Preservation_Blast_1p1cOmbnRd5CoKBp.json @@ -25,8 +25,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -52,7 +52,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json index 839dc2c2..080dd67f 100644 --- a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json +++ b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json @@ -31,8 +31,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -56,7 +56,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -102,8 +102,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -127,7 +127,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Reaper_s_Strike_MCgNRlh0s5XUPCfl.json b/src/packs/domains/domainCard_Reaper_s_Strike_MCgNRlh0s5XUPCfl.json index f4feebbb..3bb8accd 100644 --- a/src/packs/domains/domainCard_Reaper_s_Strike_MCgNRlh0s5XUPCfl.json +++ b/src/packs/domains/domainCard_Reaper_s_Strike_MCgNRlh0s5XUPCfl.json @@ -33,7 +33,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Rejuvenation_Barrier_HtWx5IIemCoorMj2.json b/src/packs/domains/domainCard_Rejuvenation_Barrier_HtWx5IIemCoorMj2.json index 25c991c2..11e15a0b 100644 --- a/src/packs/domains/domainCard_Rejuvenation_Barrier_HtWx5IIemCoorMj2.json +++ b/src/packs/domains/domainCard_Rejuvenation_Barrier_HtWx5IIemCoorMj2.json @@ -25,8 +25,8 @@ "consumeOnSuccess": true }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Restoration_wUQFsRtww18naYaq.json b/src/packs/domains/domainCard_Restoration_wUQFsRtww18naYaq.json index 8d4d7695..a83044a0 100644 --- a/src/packs/domains/domainCard_Restoration_wUQFsRtww18naYaq.json +++ b/src/packs/domains/domainCard_Restoration_wUQFsRtww18naYaq.json @@ -42,8 +42,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -68,7 +68,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -119,8 +119,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -145,7 +145,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Resurrection_z30ciOwQI7g3tHla.json b/src/packs/domains/domainCard_Resurrection_z30ciOwQI7g3tHla.json index 82b1b4fa..b7faa7ae 100644 --- a/src/packs/domains/domainCard_Resurrection_z30ciOwQI7g3tHla.json +++ b/src/packs/domains/domainCard_Resurrection_z30ciOwQI7g3tHla.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -72,7 +72,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Rift_Walker_vd5STqX29RpYbGxa.json b/src/packs/domains/domainCard_Rift_Walker_vd5STqX29RpYbGxa.json index c112e373..163cd293 100644 --- a/src/packs/domains/domainCard_Rift_Walker_vd5STqX29RpYbGxa.json +++ b/src/packs/domains/domainCard_Rift_Walker_vd5STqX29RpYbGxa.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Rise_Up_oDIZoC4l19Nli0Fj.json b/src/packs/domains/domainCard_Rise_Up_oDIZoC4l19Nli0Fj.json index 38c900b2..5d46562d 100644 --- a/src/packs/domains/domainCard_Rise_Up_oDIZoC4l19Nli0Fj.json +++ b/src/packs/domains/domainCard_Rise_Up_oDIZoC4l19Nli0Fj.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Rune_Ward_GEhBUmv9Bj7oJfHk.json b/src/packs/domains/domainCard_Rune_Ward_GEhBUmv9Bj7oJfHk.json index 54b0edbb..01515230 100644 --- a/src/packs/domains/domainCard_Rune_Ward_GEhBUmv9Bj7oJfHk.json +++ b/src/packs/domains/domainCard_Rune_Ward_GEhBUmv9Bj7oJfHk.json @@ -31,7 +31,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json b/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json index c7aeb02f..3f81c8ac 100644 --- a/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json +++ b/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json @@ -33,8 +33,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -59,7 +59,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Second_Wind_ffPbSEvLuFrFsMxl.json b/src/packs/domains/domainCard_Second_Wind_ffPbSEvLuFrFsMxl.json index 8dc535cc..764a3c87 100644 --- a/src/packs/domains/domainCard_Second_Wind_ffPbSEvLuFrFsMxl.json +++ b/src/packs/domains/domainCard_Second_Wind_ffPbSEvLuFrFsMxl.json @@ -34,8 +34,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -60,7 +60,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -111,8 +111,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -137,7 +137,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Sensory_Projection_gZOMzskSOfeiXn54.json b/src/packs/domains/domainCard_Sensory_Projection_gZOMzskSOfeiXn54.json index 2701b0ce..c0aea3df 100644 --- a/src/packs/domains/domainCard_Sensory_Projection_gZOMzskSOfeiXn54.json +++ b/src/packs/domains/domainCard_Sensory_Projection_gZOMzskSOfeiXn54.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json b/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json index 39139dfa..0fc6ce4b 100644 --- a/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json +++ b/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Shrug_It_Off_JwfhtgmmuRxg4zhI.json b/src/packs/domains/domainCard_Shrug_It_Off_JwfhtgmmuRxg4zhI.json index 7cabf19d..3e757308 100644 --- a/src/packs/domains/domainCard_Shrug_It_Off_JwfhtgmmuRxg4zhI.json +++ b/src/packs/domains/domainCard_Shrug_It_Off_JwfhtgmmuRxg4zhI.json @@ -32,7 +32,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Soothing_Speech_QED2PDYePOSTbLtC.json b/src/packs/domains/domainCard_Soothing_Speech_QED2PDYePOSTbLtC.json index daecba3b..b7ef626d 100644 --- a/src/packs/domains/domainCard_Soothing_Speech_QED2PDYePOSTbLtC.json +++ b/src/packs/domains/domainCard_Soothing_Speech_QED2PDYePOSTbLtC.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -91,8 +91,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -117,7 +117,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Splintering_Strike_TYKfM3H9vBXyWiH4.json b/src/packs/domains/domainCard_Splintering_Strike_TYKfM3H9vBXyWiH4.json index e36c744c..a63fd477 100644 --- a/src/packs/domains/domainCard_Splintering_Strike_TYKfM3H9vBXyWiH4.json +++ b/src/packs/domains/domainCard_Splintering_Strike_TYKfM3H9vBXyWiH4.json @@ -33,7 +33,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json b/src/packs/domains/domainCard_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json index f9b86bd0..2f66c1f0 100644 --- a/src/packs/domains/domainCard_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json +++ b/src/packs/domains/domainCard_Stunning_Sunlight_lRHo6ZkK1zybeEoG.json @@ -33,8 +33,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -60,7 +60,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -112,8 +112,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -139,7 +139,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Swift_Step_H6TqCJBaa1eWEQ1z.json b/src/packs/domains/domainCard_Swift_Step_H6TqCJBaa1eWEQ1z.json index bd153045..76e9e6e9 100644 --- a/src/packs/domains/domainCard_Swift_Step_H6TqCJBaa1eWEQ1z.json +++ b/src/packs/domains/domainCard_Swift_Step_H6TqCJBaa1eWEQ1z.json @@ -25,8 +25,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -51,7 +51,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -93,8 +93,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -119,7 +119,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Telekinesis_FgzBppvLjXr0UbUI.json b/src/packs/domains/domainCard_Telekinesis_FgzBppvLjXr0UbUI.json index f80954e7..0436b1aa 100644 --- a/src/packs/domains/domainCard_Telekinesis_FgzBppvLjXr0UbUI.json +++ b/src/packs/domains/domainCard_Telekinesis_FgzBppvLjXr0UbUI.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -72,8 +72,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -99,7 +99,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Teleport_HnPwVrWblYa9hwSt.json b/src/packs/domains/domainCard_Teleport_HnPwVrWblYa9hwSt.json index b4563eac..ff262d6f 100644 --- a/src/packs/domains/domainCard_Teleport_HnPwVrWblYa9hwSt.json +++ b/src/packs/domains/domainCard_Teleport_HnPwVrWblYa9hwSt.json @@ -24,7 +24,7 @@ "recovery": "longRest" }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Tell_No_Lies_HTv9QEPS466WsstP.json b/src/packs/domains/domainCard_Tell_No_Lies_HTv9QEPS466WsstP.json index f1056dfc..1efb1374 100644 --- a/src/packs/domains/domainCard_Tell_No_Lies_HTv9QEPS466WsstP.json +++ b/src/packs/domains/domainCard_Tell_No_Lies_HTv9QEPS466WsstP.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "resultBased": false, "value": { "custom": { @@ -50,7 +50,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Tempest_X7YaZgFieBlqaPdZ.json b/src/packs/domains/domainCard_Tempest_X7YaZgFieBlqaPdZ.json index 7d587b02..3599017c 100644 --- a/src/packs/domains/domainCard_Tempest_X7YaZgFieBlqaPdZ.json +++ b/src/packs/domains/domainCard_Tempest_X7YaZgFieBlqaPdZ.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -51,7 +51,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -102,8 +102,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -129,7 +129,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -180,8 +180,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -207,7 +207,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Thought_Delver_B4choj481tqajWb9.json b/src/packs/domains/domainCard_Thought_Delver_B4choj481tqajWb9.json index 0dbc078d..7c8d6235 100644 --- a/src/packs/domains/domainCard_Thought_Delver_B4choj481tqajWb9.json +++ b/src/packs/domains/domainCard_Thought_Delver_B4choj481tqajWb9.json @@ -58,7 +58,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Towering_Stalk_n0P3VS1WfxvmXbB6.json b/src/packs/domains/domainCard_Towering_Stalk_n0P3VS1WfxvmXbB6.json index d7d984d1..4696c144 100644 --- a/src/packs/domains/domainCard_Towering_Stalk_n0P3VS1WfxvmXbB6.json +++ b/src/packs/domains/domainCard_Towering_Stalk_n0P3VS1WfxvmXbB6.json @@ -41,8 +41,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -68,7 +68,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Troublemaker_JrdZedm1BFKeV7Yb.json b/src/packs/domains/domainCard_Troublemaker_JrdZedm1BFKeV7Yb.json index 9d740283..161c499f 100644 --- a/src/packs/domains/domainCard_Troublemaker_JrdZedm1BFKeV7Yb.json +++ b/src/packs/domains/domainCard_Troublemaker_JrdZedm1BFKeV7Yb.json @@ -25,8 +25,8 @@ "consumeOnSuccess": true }, "damage": { - "parts": [ - { + "parts": { + "stress": { "resultBased": false, "value": { "custom": { @@ -51,7 +51,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Twilight_Toll_SDjjV61TC1NceV1m.json b/src/packs/domains/domainCard_Twilight_Toll_SDjjV61TC1NceV1m.json index a6a987dd..cc7f45c7 100644 --- a/src/packs/domains/domainCard_Twilight_Toll_SDjjV61TC1NceV1m.json +++ b/src/packs/domains/domainCard_Twilight_Toll_SDjjV61TC1NceV1m.json @@ -40,8 +40,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -65,7 +65,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Unbreakable_CUIQmrPjf9VCHmwJ.json b/src/packs/domains/domainCard_Unbreakable_CUIQmrPjf9VCHmwJ.json index e4847ee4..b822b632 100644 --- a/src/packs/domains/domainCard_Unbreakable_CUIQmrPjf9VCHmwJ.json +++ b/src/packs/domains/domainCard_Unbreakable_CUIQmrPjf9VCHmwJ.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json b/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json index 46d9c472..62953005 100644 --- a/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json +++ b/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json b/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json index 62bd00a0..69049d01 100644 --- a/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json +++ b/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json @@ -42,8 +42,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -69,7 +69,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Valor_Touched_k1AtYd3lSchIymBr.json b/src/packs/domains/domainCard_Valor_Touched_k1AtYd3lSchIymBr.json index 20fe18ea..84ef1025 100644 --- a/src/packs/domains/domainCard_Valor_Touched_k1AtYd3lSchIymBr.json +++ b/src/packs/domains/domainCard_Valor_Touched_k1AtYd3lSchIymBr.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "armor": { "value": { "custom": { "enabled": true, @@ -50,7 +50,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json b/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json index a6263afe..abac35be 100644 --- a/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json +++ b/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json @@ -24,7 +24,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json b/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json index 85935876..3b7358bc 100644 --- a/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json +++ b/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -51,7 +51,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Wild_Fortress_9dFvcM1i3bxG3BSA.json b/src/packs/domains/domainCard_Wild_Fortress_9dFvcM1i3bxG3BSA.json index 655f0c2b..d61f914b 100644 --- a/src/packs/domains/domainCard_Wild_Fortress_9dFvcM1i3bxG3BSA.json +++ b/src/packs/domains/domainCard_Wild_Fortress_9dFvcM1i3bxG3BSA.json @@ -32,7 +32,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Words_of_Discord_ZjAdi1FSNCDDHI3X.json b/src/packs/domains/domainCard_Words_of_Discord_ZjAdi1FSNCDDHI3X.json index fb3c6611..d743f5e1 100644 --- a/src/packs/domains/domainCard_Words_of_Discord_ZjAdi1FSNCDDHI3X.json +++ b/src/packs/domains/domainCard_Words_of_Discord_ZjAdi1FSNCDDHI3X.json @@ -24,8 +24,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "resultBased": false, "value": { "custom": { @@ -50,7 +50,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json b/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json index 16753e1e..22042f7d 100644 --- a/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json +++ b/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json @@ -25,7 +25,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/domains/domainCard_Zone_of_Protection_lOZaRb4fCVgQsWB5.json b/src/packs/domains/domainCard_Zone_of_Protection_lOZaRb4fCVgQsWB5.json index 5669173d..19bff136 100644 --- a/src/packs/domains/domainCard_Zone_of_Protection_lOZaRb4fCVgQsWB5.json +++ b/src/packs/domains/domainCard_Zone_of_Protection_lOZaRb4fCVgQsWB5.json @@ -25,7 +25,7 @@ "consumeOnSuccess": true }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json index 0fa0a09e..75fc932f 100644 --- a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json +++ b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json @@ -183,8 +183,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -210,7 +210,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Ambushed_uGEdNYERCTJBEjc5.json b/src/packs/environments/environment_Ambushed_uGEdNYERCTJBEjc5.json index b0ccd435..ea1a158c 100644 --- a/src/packs/environments/environment_Ambushed_uGEdNYERCTJBEjc5.json +++ b/src/packs/environments/environment_Ambushed_uGEdNYERCTJBEjc5.json @@ -166,8 +166,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -192,7 +192,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json b/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json index a48d9b83..c380a4e2 100644 --- a/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json +++ b/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json @@ -213,7 +213,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -342,8 +342,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -369,7 +369,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -442,8 +442,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -469,7 +469,7 @@ } } } - ], + }, "includeBase": false, "direct": true }, diff --git a/src/packs/environments/environment_Bustling_Marketplace_HZKA7hkej7JJY503.json b/src/packs/environments/environment_Bustling_Marketplace_HZKA7hkej7JJY503.json index af0b3a70..e10fad1a 100644 --- a/src/packs/environments/environment_Bustling_Marketplace_HZKA7hkej7JJY503.json +++ b/src/packs/environments/environment_Bustling_Marketplace_HZKA7hkej7JJY503.json @@ -205,7 +205,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Castle_Siege_1eZ32Esq7rfZOjlu.json b/src/packs/environments/environment_Castle_Siege_1eZ32Esq7rfZOjlu.json index 411a10c7..fdfc9a5d 100644 --- a/src/packs/environments/environment_Castle_Siege_1eZ32Esq7rfZOjlu.json +++ b/src/packs/environments/environment_Castle_Siege_1eZ32Esq7rfZOjlu.json @@ -185,8 +185,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "fear": { "value": { "custom": { "enabled": true, @@ -211,7 +211,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -342,8 +342,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -369,7 +369,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Chaos_Realm_2Z1mKc65LxNk2PqR.json b/src/packs/environments/environment_Chaos_Realm_2Z1mKc65LxNk2PqR.json index 361b15bc..e7c2c4e0 100644 --- a/src/packs/environments/environment_Chaos_Realm_2Z1mKc65LxNk2PqR.json +++ b/src/packs/environments/environment_Chaos_Realm_2Z1mKc65LxNk2PqR.json @@ -150,8 +150,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -176,7 +176,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -265,7 +265,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -381,8 +381,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -408,7 +408,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -552,8 +552,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -578,7 +578,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Cliffside_Ascent_LPpfdlNKqiZIl04w.json b/src/packs/environments/environment_Cliffside_Ascent_LPpfdlNKqiZIl04w.json index 548cf7c4..ef367d67 100644 --- a/src/packs/environments/environment_Cliffside_Ascent_LPpfdlNKqiZIl04w.json +++ b/src/packs/environments/environment_Cliffside_Ascent_LPpfdlNKqiZIl04w.json @@ -214,8 +214,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -240,7 +240,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -292,8 +292,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -317,7 +317,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -343,8 +343,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -368,7 +368,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -394,8 +394,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -419,7 +419,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Divine_Usurpation_4DLYez7VbMCFDAuZ.json b/src/packs/environments/environment_Divine_Usurpation_4DLYez7VbMCFDAuZ.json index d8e9cded..637cdd41 100644 --- a/src/packs/environments/environment_Divine_Usurpation_4DLYez7VbMCFDAuZ.json +++ b/src/packs/environments/environment_Divine_Usurpation_4DLYez7VbMCFDAuZ.json @@ -323,8 +323,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -349,7 +349,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -600,8 +600,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -625,7 +625,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Hallowed_Temple_dsA6j69AnaJhUyqH.json b/src/packs/environments/environment_Hallowed_Temple_dsA6j69AnaJhUyqH.json index f005fa59..c510a87f 100644 --- a/src/packs/environments/environment_Hallowed_Temple_dsA6j69AnaJhUyqH.json +++ b/src/packs/environments/environment_Hallowed_Temple_dsA6j69AnaJhUyqH.json @@ -150,8 +150,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -176,7 +176,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -268,8 +268,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -294,7 +294,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Imperial_Court_jr1xAoXzVwVblzxI.json b/src/packs/environments/environment_Imperial_Court_jr1xAoXzVwVblzxI.json index 4b49c341..5807d43c 100644 --- a/src/packs/environments/environment_Imperial_Court_jr1xAoXzVwVblzxI.json +++ b/src/packs/environments/environment_Imperial_Court_jr1xAoXzVwVblzxI.json @@ -209,8 +209,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -234,7 +234,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -307,7 +307,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { @@ -387,7 +387,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Local_Tavern_cM4X81DOyvxNIi52.json b/src/packs/environments/environment_Local_Tavern_cM4X81DOyvxNIi52.json index 105f230f..da2d2830 100644 --- a/src/packs/environments/environment_Local_Tavern_cM4X81DOyvxNIi52.json +++ b/src/packs/environments/environment_Local_Tavern_cM4X81DOyvxNIi52.json @@ -266,8 +266,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -291,7 +291,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Mountain_Pass_acMu9wJrMZZzLSTJ.json b/src/packs/environments/environment_Mountain_Pass_acMu9wJrMZZzLSTJ.json index 9ba6a918..eaad99f7 100644 --- a/src/packs/environments/environment_Mountain_Pass_acMu9wJrMZZzLSTJ.json +++ b/src/packs/environments/environment_Mountain_Pass_acMu9wJrMZZzLSTJ.json @@ -183,8 +183,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -208,7 +208,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -315,8 +315,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -341,7 +341,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Necromancer_s_Ossuary_h3KyRL7AshhLAmcH.json b/src/packs/environments/environment_Necromancer_s_Ossuary_h3KyRL7AshhLAmcH.json index e96b9177..949e3dc1 100644 --- a/src/packs/environments/environment_Necromancer_s_Ossuary_h3KyRL7AshhLAmcH.json +++ b/src/packs/environments/environment_Necromancer_s_Ossuary_h3KyRL7AshhLAmcH.json @@ -149,8 +149,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -175,7 +175,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -252,8 +252,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -277,7 +277,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -350,7 +350,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json b/src/packs/environments/environment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json index 7be27924..14c724e3 100644 --- a/src/packs/environments/environment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json +++ b/src/packs/environments/environment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json @@ -218,8 +218,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -245,7 +245,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/environments/environment_Raging_River_t4cdqTfzcqP3H1vJ.json b/src/packs/environments/environment_Raging_River_t4cdqTfzcqP3H1vJ.json index 03dd72e3..6c87c446 100644 --- a/src/packs/environments/environment_Raging_River_t4cdqTfzcqP3H1vJ.json +++ b/src/packs/environments/environment_Raging_River_t4cdqTfzcqP3H1vJ.json @@ -230,8 +230,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -257,7 +257,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/armors/armor_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json b/src/packs/items/armors/armor_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json index df692143..588d9cf9 100644 --- a/src/packs/items/armors/armor_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json +++ b/src/packs/items/armors/armor_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json @@ -48,7 +48,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/items/armors/armor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json b/src/packs/items/armors/armor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json index 8ccc27e3..15b07474 100644 --- a/src/packs/items/armors/armor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json +++ b/src/packs/items/armors/armor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json @@ -20,8 +20,8 @@ "amount": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "applyTo": "stress", "value": { "custom": { @@ -46,7 +46,7 @@ "base": false, "type": [] } - ], + }, "includeBase": false }, "_id": "L8mHf4A8SylyxsMH", diff --git a/src/packs/items/armors/armor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json b/src/packs/items/armors/armor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json index 61d1fed7..82b4f791 100644 --- a/src/packs/items/armors/armor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json +++ b/src/packs/items/armors/armor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json @@ -39,7 +39,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json b/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json index f484f3c9..2a4c6ac9 100644 --- a/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json +++ b/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -55,7 +55,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Dripfang_Poison_eU8VpbWB2NHIL47n.json b/src/packs/items/consumables/consumable_Dripfang_Poison_eU8VpbWB2NHIL47n.json index 70070ab2..c37b8bb4 100644 --- a/src/packs/items/consumables/consumable_Dripfang_Poison_eU8VpbWB2NHIL47n.json +++ b/src/packs/items/consumables/consumable_Dripfang_Poison_eU8VpbWB2NHIL47n.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -57,7 +57,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Feast_of_Xuria_aX6NyxkNzu0LcJpt.json b/src/packs/items/consumables/consumable_Feast_of_Xuria_aX6NyxkNzu0LcJpt.json index f98d86f1..b23f091b 100644 --- a/src/packs/items/consumables/consumable_Feast_of_Xuria_aX6NyxkNzu0LcJpt.json +++ b/src/packs/items/consumables/consumable_Feast_of_Xuria_aX6NyxkNzu0LcJpt.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -56,7 +56,7 @@ }, "type": [] }, - { + "stress": { "value": { "custom": { "enabled": true, @@ -81,7 +81,7 @@ }, "type": [] }, - { + "hope": { "value": { "custom": { "enabled": false @@ -105,7 +105,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Health_Potion_Aruc2NLutWuVIjP1.json b/src/packs/items/consumables/consumable_Health_Potion_Aruc2NLutWuVIjP1.json index 8c620942..bc82daa2 100644 --- a/src/packs/items/consumables/consumable_Health_Potion_Aruc2NLutWuVIjP1.json +++ b/src/packs/items/consumables/consumable_Health_Potion_Aruc2NLutWuVIjP1.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Improved_Arcane_Shard_nQTo6mNoPTEVBtkm.json b/src/packs/items/consumables/consumable_Improved_Arcane_Shard_nQTo6mNoPTEVBtkm.json index 2081f33b..b39db9b5 100644 --- a/src/packs/items/consumables/consumable_Improved_Arcane_Shard_nQTo6mNoPTEVBtkm.json +++ b/src/packs/items/consumables/consumable_Improved_Arcane_Shard_nQTo6mNoPTEVBtkm.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -57,7 +57,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Jar_of_Lost_Voices_yUol6M5b8jsbk9za.json b/src/packs/items/consumables/consumable_Jar_of_Lost_Voices_yUol6M5b8jsbk9za.json index c655f7ca..3e716230 100644 --- a/src/packs/items/consumables/consumable_Jar_of_Lost_Voices_yUol6M5b8jsbk9za.json +++ b/src/packs/items/consumables/consumable_Jar_of_Lost_Voices_yUol6M5b8jsbk9za.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -57,7 +57,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Major_Arcane_Shard_AA7bmiwv00lshPrC.json b/src/packs/items/consumables/consumable_Major_Arcane_Shard_AA7bmiwv00lshPrC.json index b9293362..25cb8fb3 100644 --- a/src/packs/items/consumables/consumable_Major_Arcane_Shard_AA7bmiwv00lshPrC.json +++ b/src/packs/items/consumables/consumable_Major_Arcane_Shard_AA7bmiwv00lshPrC.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -55,7 +55,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Major_Health_Potion_cM7pHe8bBAxSZ2xR.json b/src/packs/items/consumables/consumable_Major_Health_Potion_cM7pHe8bBAxSZ2xR.json index 201233ce..843249de 100644 --- a/src/packs/items/consumables/consumable_Major_Health_Potion_cM7pHe8bBAxSZ2xR.json +++ b/src/packs/items/consumables/consumable_Major_Health_Potion_cM7pHe8bBAxSZ2xR.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Major_Stamina_Potion_I4cQ03xbxnc81EGa.json b/src/packs/items/consumables/consumable_Major_Stamina_Potion_I4cQ03xbxnc81EGa.json index 3f7d2a00..ebe07a1f 100644 --- a/src/packs/items/consumables/consumable_Major_Stamina_Potion_I4cQ03xbxnc81EGa.json +++ b/src/packs/items/consumables/consumable_Major_Stamina_Potion_I4cQ03xbxnc81EGa.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Minor_Health_Potion_tPfKtKRRjv8qdSqy.json b/src/packs/items/consumables/consumable_Minor_Health_Potion_tPfKtKRRjv8qdSqy.json index 1e1e31f1..bac0f425 100644 --- a/src/packs/items/consumables/consumable_Minor_Health_Potion_tPfKtKRRjv8qdSqy.json +++ b/src/packs/items/consumables/consumable_Minor_Health_Potion_tPfKtKRRjv8qdSqy.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Minor_Stamina_Potion_b6vGSPFWOlzZZDLO.json b/src/packs/items/consumables/consumable_Minor_Stamina_Potion_b6vGSPFWOlzZZDLO.json index 9895b3ef..79a29e8b 100644 --- a/src/packs/items/consumables/consumable_Minor_Stamina_Potion_b6vGSPFWOlzZZDLO.json +++ b/src/packs/items/consumables/consumable_Minor_Stamina_Potion_b6vGSPFWOlzZZDLO.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Sleeping_Sap_XZavUVlHEvE2srEt.json b/src/packs/items/consumables/consumable_Sleeping_Sap_XZavUVlHEvE2srEt.json index 74fbf749..942f449e 100644 --- a/src/packs/items/consumables/consumable_Sleeping_Sap_XZavUVlHEvE2srEt.json +++ b/src/packs/items/consumables/consumable_Sleeping_Sap_XZavUVlHEvE2srEt.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -56,7 +56,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Snap_Powder_cg6VtQ0eVZjDdcK0.json b/src/packs/items/consumables/consumable_Snap_Powder_cg6VtQ0eVZjDdcK0.json index adcf8a31..1ae34797 100644 --- a/src/packs/items/consumables/consumable_Snap_Powder_cg6VtQ0eVZjDdcK0.json +++ b/src/packs/items/consumables/consumable_Snap_Powder_cg6VtQ0eVZjDdcK0.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -56,7 +56,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Stamina_Potion_hf3k1POoVSooJyN2.json b/src/packs/items/consumables/consumable_Stamina_Potion_hf3k1POoVSooJyN2.json index 9cb4716e..3affdb8e 100644 --- a/src/packs/items/consumables/consumable_Stamina_Potion_hf3k1POoVSooJyN2.json +++ b/src/packs/items/consumables/consumable_Stamina_Potion_hf3k1POoVSooJyN2.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Stardrop_y4c1jrlHrf0wBWOq.json b/src/packs/items/consumables/consumable_Stardrop_y4c1jrlHrf0wBWOq.json index 1a8800cd..be977305 100644 --- a/src/packs/items/consumables/consumable_Stardrop_y4c1jrlHrf0wBWOq.json +++ b/src/packs/items/consumables/consumable_Stardrop_y4c1jrlHrf0wBWOq.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -57,7 +57,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Sun_Tree_Sap_kwexUzdM9wm1Qums.json b/src/packs/items/consumables/consumable_Sun_Tree_Sap_kwexUzdM9wm1Qums.json index 48a61a28..2669a49b 100644 --- a/src/packs/items/consumables/consumable_Sun_Tree_Sap_kwexUzdM9wm1Qums.json +++ b/src/packs/items/consumables/consumable_Sun_Tree_Sap_kwexUzdM9wm1Qums.json @@ -30,7 +30,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Sweet_Moss_GrDrRqWgv7gvl9vn.json b/src/packs/items/consumables/consumable_Sweet_Moss_GrDrRqWgv7gvl9vn.json index f8b6fbb7..c17d6514 100644 --- a/src/packs/items/consumables/consumable_Sweet_Moss_GrDrRqWgv7gvl9vn.json +++ b/src/packs/items/consumables/consumable_Sweet_Moss_GrDrRqWgv7gvl9vn.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -55,7 +55,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -105,8 +105,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": false @@ -130,7 +130,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Unstable_Arcane_Shard_mUepnLbkvFk0ha4Z.json b/src/packs/items/consumables/consumable_Unstable_Arcane_Shard_mUepnLbkvFk0ha4Z.json index 96b83780..a5890ab0 100644 --- a/src/packs/items/consumables/consumable_Unstable_Arcane_Shard_mUepnLbkvFk0ha4Z.json +++ b/src/packs/items/consumables/consumable_Unstable_Arcane_Shard_mUepnLbkvFk0ha4Z.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -57,7 +57,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/consumables/consumable_Varik_Leaves_hvy5BkG3F6iOIXTx.json b/src/packs/items/consumables/consumable_Varik_Leaves_hvy5BkG3F6iOIXTx.json index 79f4d11e..27e83aa4 100644 --- a/src/packs/items/consumables/consumable_Varik_Leaves_hvy5BkG3F6iOIXTx.json +++ b/src/packs/items/consumables/consumable_Varik_Leaves_hvy5BkG3F6iOIXTx.json @@ -30,8 +30,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -56,7 +56,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/items/loot/loot_Bag_of_Ficklesand_v758j4FwNVAurhYK.json b/src/packs/items/loot/loot_Bag_of_Ficklesand_v758j4FwNVAurhYK.json index c083e7ca..2cb80d86 100644 --- a/src/packs/items/loot/loot_Bag_of_Ficklesand_v758j4FwNVAurhYK.json +++ b/src/packs/items/loot/loot_Bag_of_Ficklesand_v758j4FwNVAurhYK.json @@ -21,7 +21,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/items/loot/loot_Box_of_Many_Goods_bZyT7Qw7iafswlTY.json b/src/packs/items/loot/loot_Box_of_Many_Goods_bZyT7Qw7iafswlTY.json index 1090323f..cb603dc7 100644 --- a/src/packs/items/loot/loot_Box_of_Many_Goods_bZyT7Qw7iafswlTY.json +++ b/src/packs/items/loot/loot_Box_of_Many_Goods_bZyT7Qw7iafswlTY.json @@ -21,7 +21,7 @@ "recovery": "longRest" }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/items/loot/loot_Calming_Pendant_tgFFMxpuRSiRrrEB.json b/src/packs/items/loot/loot_Calming_Pendant_tgFFMxpuRSiRrrEB.json index 3d5b9651..ea5b50eb 100644 --- a/src/packs/items/loot/loot_Calming_Pendant_tgFFMxpuRSiRrrEB.json +++ b/src/packs/items/loot/loot_Calming_Pendant_tgFFMxpuRSiRrrEB.json @@ -21,7 +21,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/items/loot/loot_Skeleton_Key_edkNgwy4xghZreBa.json b/src/packs/items/loot/loot_Skeleton_Key_edkNgwy4xghZreBa.json index 72a8d3e0..b5769661 100644 --- a/src/packs/items/loot/loot_Skeleton_Key_edkNgwy4xghZreBa.json +++ b/src/packs/items/loot/loot_Skeleton_Key_edkNgwy4xghZreBa.json @@ -21,7 +21,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/items/loot/loot_Woven_Net_ARuv48PWUGJGBC4n.json b/src/packs/items/loot/loot_Woven_Net_ARuv48PWUGJGBC4n.json index 99d52ea9..2732b61f 100644 --- a/src/packs/items/loot/loot_Woven_Net_ARuv48PWUGJGBC4n.json +++ b/src/packs/items/loot/loot_Woven_Net_ARuv48PWUGJGBC4n.json @@ -21,7 +21,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/items/weapons/weapon_Aantari_Bow_ijodu5yNBoMxpkHV.json b/src/packs/items/weapons/weapon_Aantari_Bow_ijodu5yNBoMxpkHV.json index 93e6404c..7b51d436 100644 --- a/src/packs/items/weapons/weapon_Aantari_Bow_ijodu5yNBoMxpkHV.json +++ b/src/packs/items/weapons/weapon_Aantari_Bow_ijodu5yNBoMxpkHV.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 11, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Arcane_Frame_Wheelchair_la3sAWgnvadc4NvP.json b/src/packs/items/weapons/weapon_Advanced_Arcane_Frame_Wheelchair_la3sAWgnvadc4NvP.json index 36f1be3a..a727bcd5 100644 --- a/src/packs/items/weapons/weapon_Advanced_Arcane_Frame_Wheelchair_la3sAWgnvadc4NvP.json +++ b/src/packs/items/weapons/weapon_Advanced_Arcane_Frame_Wheelchair_la3sAWgnvadc4NvP.json @@ -48,8 +48,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -75,7 +75,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Arcane_Gauntlets_hXR56fTKwZ6s1obs.json b/src/packs/items/weapons/weapon_Advanced_Arcane_Gauntlets_hXR56fTKwZ6s1obs.json index 183ac117..67400768 100644 --- a/src/packs/items/weapons/weapon_Advanced_Arcane_Gauntlets_hXR56fTKwZ6s1obs.json +++ b/src/packs/items/weapons/weapon_Advanced_Arcane_Gauntlets_hXR56fTKwZ6s1obs.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Battleaxe_FcbvY1ydbNVMjKvk.json b/src/packs/items/weapons/weapon_Advanced_Battleaxe_FcbvY1ydbNVMjKvk.json index 6d2879d7..e780eeed 100644 --- a/src/packs/items/weapons/weapon_Advanced_Battleaxe_FcbvY1ydbNVMjKvk.json +++ b/src/packs/items/weapons/weapon_Advanced_Battleaxe_FcbvY1ydbNVMjKvk.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Broadsword_WtQAGz0TUgz8Xg70.json b/src/packs/items/weapons/weapon_Advanced_Broadsword_WtQAGz0TUgz8Xg70.json index 9e4a67b0..4581c52f 100644 --- a/src/packs/items/weapons/weapon_Advanced_Broadsword_WtQAGz0TUgz8Xg70.json +++ b/src/packs/items/weapons/weapon_Advanced_Broadsword_WtQAGz0TUgz8Xg70.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Crossbow_3HGs0AgVrdIBTaKG.json b/src/packs/items/weapons/weapon_Advanced_Crossbow_3HGs0AgVrdIBTaKG.json index 44e47499..437423f5 100644 --- a/src/packs/items/weapons/weapon_Advanced_Crossbow_3HGs0AgVrdIBTaKG.json +++ b/src/packs/items/weapons/weapon_Advanced_Crossbow_3HGs0AgVrdIBTaKG.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 7, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Cutlass_bw9WO9lxkM9bWZxQ.json b/src/packs/items/weapons/weapon_Advanced_Cutlass_bw9WO9lxkM9bWZxQ.json index a20acb5b..45d9a597 100644 --- a/src/packs/items/weapons/weapon_Advanced_Cutlass_bw9WO9lxkM9bWZxQ.json +++ b/src/packs/items/weapons/weapon_Advanced_Cutlass_bw9WO9lxkM9bWZxQ.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 7, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Dagger_mrioysDjNQEIE8hN.json b/src/packs/items/weapons/weapon_Advanced_Dagger_mrioysDjNQEIE8hN.json index a7004807..03f27c99 100644 --- a/src/packs/items/weapons/weapon_Advanced_Dagger_mrioysDjNQEIE8hN.json +++ b/src/packs/items/weapons/weapon_Advanced_Dagger_mrioysDjNQEIE8hN.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 7, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Dualstaff_X5x3sC7v2f3L9sjL.json b/src/packs/items/weapons/weapon_Advanced_Dualstaff_X5x3sC7v2f3L9sjL.json index 3baed3d4..033b873c 100644 --- a/src/packs/items/weapons/weapon_Advanced_Dualstaff_X5x3sC7v2f3L9sjL.json +++ b/src/packs/items/weapons/weapon_Advanced_Dualstaff_X5x3sC7v2f3L9sjL.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 9, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Glowing_Rings_InQoh8mZPnwarQkX.json b/src/packs/items/weapons/weapon_Advanced_Glowing_Rings_InQoh8mZPnwarQkX.json index 2bdfed49..2ea7b20d 100644 --- a/src/packs/items/weapons/weapon_Advanced_Glowing_Rings_InQoh8mZPnwarQkX.json +++ b/src/packs/items/weapons/weapon_Advanced_Glowing_Rings_InQoh8mZPnwarQkX.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 8, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Grappler_7vvhVl4TDJHtjpFK.json b/src/packs/items/weapons/weapon_Advanced_Grappler_7vvhVl4TDJHtjpFK.json index 01bef9b2..390a0b9a 100644 --- a/src/packs/items/weapons/weapon_Advanced_Grappler_7vvhVl4TDJHtjpFK.json +++ b/src/packs/items/weapons/weapon_Advanced_Grappler_7vvhVl4TDJHtjpFK.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json b/src/packs/items/weapons/weapon_Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json index c66354c2..3cc7b986 100644 --- a/src/packs/items/weapons/weapon_Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json +++ b/src/packs/items/weapons/weapon_Advanced_Greatstaff_4UzxqfkwF8gDSdu7.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Greatsword_MAC6YWTo4lzSotQc.json b/src/packs/items/weapons/weapon_Advanced_Greatsword_MAC6YWTo4lzSotQc.json index 71226630..9e04bf7a 100644 --- a/src/packs/items/weapons/weapon_Advanced_Greatsword_MAC6YWTo4lzSotQc.json +++ b/src/packs/items/weapons/weapon_Advanced_Greatsword_MAC6YWTo4lzSotQc.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Halberd_C8gQn7onAc9wsrCs.json b/src/packs/items/weapons/weapon_Advanced_Halberd_C8gQn7onAc9wsrCs.json index 59d7437d..6c11724c 100644 --- a/src/packs/items/weapons/weapon_Advanced_Halberd_C8gQn7onAc9wsrCs.json +++ b/src/packs/items/weapons/weapon_Advanced_Halberd_C8gQn7onAc9wsrCs.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 8, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Hallowed_Axe_BiyXKX2Mo1TQbKgk.json b/src/packs/items/weapons/weapon_Advanced_Hallowed_Axe_BiyXKX2Mo1TQbKgk.json index e6403810..ce89ccc2 100644 --- a/src/packs/items/weapons/weapon_Advanced_Hallowed_Axe_BiyXKX2Mo1TQbKgk.json +++ b/src/packs/items/weapons/weapon_Advanced_Hallowed_Axe_BiyXKX2Mo1TQbKgk.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 7, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Hand_Crossbow_Lsvocst8aGqkBj7g.json b/src/packs/items/weapons/weapon_Advanced_Hand_Crossbow_Lsvocst8aGqkBj7g.json index 521ee38d..88ba7077 100644 --- a/src/packs/items/weapons/weapon_Advanced_Hand_Crossbow_Lsvocst8aGqkBj7g.json +++ b/src/packs/items/weapons/weapon_Advanced_Hand_Crossbow_Lsvocst8aGqkBj7g.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 5, @@ -70,7 +70,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Hand_Runes_PQACczSghZIVTdgZ.json b/src/packs/items/weapons/weapon_Advanced_Hand_Runes_PQACczSghZIVTdgZ.json index 23f33ba1..60ffb789 100644 --- a/src/packs/items/weapons/weapon_Advanced_Hand_Runes_PQACczSghZIVTdgZ.json +++ b/src/packs/items/weapons/weapon_Advanced_Hand_Runes_PQACczSghZIVTdgZ.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 6, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Heavy_Frame_Wheelchair_eT2Qwb0RdrLX2hH1.json b/src/packs/items/weapons/weapon_Advanced_Heavy_Frame_Wheelchair_eT2Qwb0RdrLX2hH1.json index 39afe3e6..7f5bb9c7 100644 --- a/src/packs/items/weapons/weapon_Advanced_Heavy_Frame_Wheelchair_eT2Qwb0RdrLX2hH1.json +++ b/src/packs/items/weapons/weapon_Advanced_Heavy_Frame_Wheelchair_eT2Qwb0RdrLX2hH1.json @@ -48,8 +48,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 9, @@ -75,7 +75,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Light_Frame_Wheelchair_BuMfupnCzHbziQ8o.json b/src/packs/items/weapons/weapon_Advanced_Light_Frame_Wheelchair_BuMfupnCzHbziQ8o.json index 4600088d..fca77911 100644 --- a/src/packs/items/weapons/weapon_Advanced_Light_Frame_Wheelchair_BuMfupnCzHbziQ8o.json +++ b/src/packs/items/weapons/weapon_Advanced_Light_Frame_Wheelchair_BuMfupnCzHbziQ8o.json @@ -77,8 +77,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -104,7 +104,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Longbow_M5CywMAyPKGgebsJ.json b/src/packs/items/weapons/weapon_Advanced_Longbow_M5CywMAyPKGgebsJ.json index ad8a5bc9..14327a8c 100644 --- a/src/packs/items/weapons/weapon_Advanced_Longbow_M5CywMAyPKGgebsJ.json +++ b/src/packs/items/weapons/weapon_Advanced_Longbow_M5CywMAyPKGgebsJ.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Longsword_9xkB3MWXahrsVP4N.json b/src/packs/items/weapons/weapon_Advanced_Longsword_9xkB3MWXahrsVP4N.json index 2cf2c43c..0b51f2ae 100644 --- a/src/packs/items/weapons/weapon_Advanced_Longsword_9xkB3MWXahrsVP4N.json +++ b/src/packs/items/weapons/weapon_Advanced_Longsword_9xkB3MWXahrsVP4N.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Mace_WreMYiH5uhVDaoVw.json b/src/packs/items/weapons/weapon_Advanced_Mace_WreMYiH5uhVDaoVw.json index db8cde18..e255da65 100644 --- a/src/packs/items/weapons/weapon_Advanced_Mace_WreMYiH5uhVDaoVw.json +++ b/src/packs/items/weapons/weapon_Advanced_Mace_WreMYiH5uhVDaoVw.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 7, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Quarterstaff_zJtm2f9ZFKZRtCRg.json b/src/packs/items/weapons/weapon_Advanced_Quarterstaff_zJtm2f9ZFKZRtCRg.json index d6beafb2..00f0b694 100644 --- a/src/packs/items/weapons/weapon_Advanced_Quarterstaff_zJtm2f9ZFKZRtCRg.json +++ b/src/packs/items/weapons/weapon_Advanced_Quarterstaff_zJtm2f9ZFKZRtCRg.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Rapier_KxFne76d7cak15dO.json b/src/packs/items/weapons/weapon_Advanced_Rapier_KxFne76d7cak15dO.json index 315d8401..28c508b8 100644 --- a/src/packs/items/weapons/weapon_Advanced_Rapier_KxFne76d7cak15dO.json +++ b/src/packs/items/weapons/weapon_Advanced_Rapier_KxFne76d7cak15dO.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Returning_Blade_sIGXA4KMeYBUjcEO.json b/src/packs/items/weapons/weapon_Advanced_Returning_Blade_sIGXA4KMeYBUjcEO.json index bbdce2d4..0694a020 100644 --- a/src/packs/items/weapons/weapon_Advanced_Returning_Blade_sIGXA4KMeYBUjcEO.json +++ b/src/packs/items/weapons/weapon_Advanced_Returning_Blade_sIGXA4KMeYBUjcEO.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Round_Shield_hiEOGF2reabGLUoi.json b/src/packs/items/weapons/weapon_Advanced_Round_Shield_hiEOGF2reabGLUoi.json index c1c4fba5..da5d5bf8 100644 --- a/src/packs/items/weapons/weapon_Advanced_Round_Shield_hiEOGF2reabGLUoi.json +++ b/src/packs/items/weapons/weapon_Advanced_Round_Shield_hiEOGF2reabGLUoi.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d4", "bonus": 4, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Scepter_2Khzuj768yoWN9QK.json b/src/packs/items/weapons/weapon_Advanced_Scepter_2Khzuj768yoWN9QK.json index cf619a89..6dff775a 100644 --- a/src/packs/items/weapons/weapon_Advanced_Scepter_2Khzuj768yoWN9QK.json +++ b/src/packs/items/weapons/weapon_Advanced_Scepter_2Khzuj768yoWN9QK.json @@ -11,8 +11,8 @@ "type": "attack", "damage": { "includeBase": false, - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -38,7 +38,7 @@ } } } - ] + } }, "range": "melee", "roll": { @@ -115,8 +115,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -142,7 +142,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Shortbow_JpSlJvDR0X8VFDns.json b/src/packs/items/weapons/weapon_Advanced_Shortbow_JpSlJvDR0X8VFDns.json index 5693e814..f5efc9a9 100644 --- a/src/packs/items/weapons/weapon_Advanced_Shortbow_JpSlJvDR0X8VFDns.json +++ b/src/packs/items/weapons/weapon_Advanced_Shortbow_JpSlJvDR0X8VFDns.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 9, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Shortstaff_T5exRCqOXhrjSYnI.json b/src/packs/items/weapons/weapon_Advanced_Shortstaff_T5exRCqOXhrjSYnI.json index 71d66d82..0e3b3161 100644 --- a/src/packs/items/weapons/weapon_Advanced_Shortstaff_T5exRCqOXhrjSYnI.json +++ b/src/packs/items/weapons/weapon_Advanced_Shortstaff_T5exRCqOXhrjSYnI.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 7, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Shortsword_p3nz5CaGUoyuGVg0.json b/src/packs/items/weapons/weapon_Advanced_Shortsword_p3nz5CaGUoyuGVg0.json index 397fa061..dd48db21 100644 --- a/src/packs/items/weapons/weapon_Advanced_Shortsword_p3nz5CaGUoyuGVg0.json +++ b/src/packs/items/weapons/weapon_Advanced_Shortsword_p3nz5CaGUoyuGVg0.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Small_Dagger_0thN0BpN05KT8Avx.json b/src/packs/items/weapons/weapon_Advanced_Small_Dagger_0thN0BpN05KT8Avx.json index 7af59440..a63789f3 100644 --- a/src/packs/items/weapons/weapon_Advanced_Small_Dagger_0thN0BpN05KT8Avx.json +++ b/src/packs/items/weapons/weapon_Advanced_Small_Dagger_0thN0BpN05KT8Avx.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Spear_pK6dsNABKKp1CIGN.json b/src/packs/items/weapons/weapon_Advanced_Spear_pK6dsNABKKp1CIGN.json index e5f3f8ec..099761b4 100644 --- a/src/packs/items/weapons/weapon_Advanced_Spear_pK6dsNABKKp1CIGN.json +++ b/src/packs/items/weapons/weapon_Advanced_Spear_pK6dsNABKKp1CIGN.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Tower_Shield_OfOzQbs4hg6QbfTG.json b/src/packs/items/weapons/weapon_Advanced_Tower_Shield_OfOzQbs4hg6QbfTG.json index b2fb16d8..9fe47fc0 100644 --- a/src/packs/items/weapons/weapon_Advanced_Tower_Shield_OfOzQbs4hg6QbfTG.json +++ b/src/packs/items/weapons/weapon_Advanced_Tower_Shield_OfOzQbs4hg6QbfTG.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Wand_jU9jWIardjtdAQcs.json b/src/packs/items/weapons/weapon_Advanced_Wand_jU9jWIardjtdAQcs.json index 4cb4e2b2..8043e360 100644 --- a/src/packs/items/weapons/weapon_Advanced_Wand_jU9jWIardjtdAQcs.json +++ b/src/packs/items/weapons/weapon_Advanced_Wand_jU9jWIardjtdAQcs.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 7, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Warhammer_8Lipw3RRKDgBVP0p.json b/src/packs/items/weapons/weapon_Advanced_Warhammer_8Lipw3RRKDgBVP0p.json index 72983f6b..bb142281 100644 --- a/src/packs/items/weapons/weapon_Advanced_Warhammer_8Lipw3RRKDgBVP0p.json +++ b/src/packs/items/weapons/weapon_Advanced_Warhammer_8Lipw3RRKDgBVP0p.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 9, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Advanced_Whip_01izMUSJcAUo79IX.json b/src/packs/items/weapons/weapon_Advanced_Whip_01izMUSJcAUo79IX.json index 6e1753c8..142eb542 100644 --- a/src/packs/items/weapons/weapon_Advanced_Whip_01izMUSJcAUo79IX.json +++ b/src/packs/items/weapons/weapon_Advanced_Whip_01izMUSJcAUo79IX.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Arcane_Frame_Wheelchair_XRChepscgr75Uug7.json b/src/packs/items/weapons/weapon_Arcane_Frame_Wheelchair_XRChepscgr75Uug7.json index 58ef5f4b..6959fb30 100644 --- a/src/packs/items/weapons/weapon_Arcane_Frame_Wheelchair_XRChepscgr75Uug7.json +++ b/src/packs/items/weapons/weapon_Arcane_Frame_Wheelchair_XRChepscgr75Uug7.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": null, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Arcane_Gauntlets_PC5EyEIq7NWBV0n5.json b/src/packs/items/weapons/weapon_Arcane_Gauntlets_PC5EyEIq7NWBV0n5.json index 8d9e3c31..29afcc13 100644 --- a/src/packs/items/weapons/weapon_Arcane_Gauntlets_PC5EyEIq7NWBV0n5.json +++ b/src/packs/items/weapons/weapon_Arcane_Gauntlets_PC5EyEIq7NWBV0n5.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Axe_of_Fortunis_YcS1rHgfnSlla8Xf.json b/src/packs/items/weapons/weapon_Axe_of_Fortunis_YcS1rHgfnSlla8Xf.json index 6ce0ce68..8e4b00c5 100644 --- a/src/packs/items/weapons/weapon_Axe_of_Fortunis_YcS1rHgfnSlla8Xf.json +++ b/src/packs/items/weapons/weapon_Axe_of_Fortunis_YcS1rHgfnSlla8Xf.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 8, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Battleaxe_fbDYUja3ll9vCtrB.json b/src/packs/items/weapons/weapon_Battleaxe_fbDYUja3ll9vCtrB.json index 9b43f8dc..9ab3321e 100644 --- a/src/packs/items/weapons/weapon_Battleaxe_fbDYUja3ll9vCtrB.json +++ b/src/packs/items/weapons/weapon_Battleaxe_fbDYUja3ll9vCtrB.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json b/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json index 845fee44..34371c2b 100644 --- a/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json +++ b/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 8, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Bladed_Whip_5faflfNz20cFW1EM.json b/src/packs/items/weapons/weapon_Bladed_Whip_5faflfNz20cFW1EM.json index 5238578f..2b2a5b9c 100644 --- a/src/packs/items/weapons/weapon_Bladed_Whip_5faflfNz20cFW1EM.json +++ b/src/packs/items/weapons/weapon_Bladed_Whip_5faflfNz20cFW1EM.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 3, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Blessed_Anlace_n1oPTk5czTIGTkVj.json b/src/packs/items/weapons/weapon_Blessed_Anlace_n1oPTk5czTIGTkVj.json index 82140411..14448edc 100644 --- a/src/packs/items/weapons/weapon_Blessed_Anlace_n1oPTk5czTIGTkVj.json +++ b/src/packs/items/weapons/weapon_Blessed_Anlace_n1oPTk5czTIGTkVj.json @@ -19,8 +19,8 @@ "amount": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "applyTo": "hitPoints", "value": { "custom": { @@ -45,7 +45,7 @@ "base": false, "type": [] } - ], + }, "includeBase": false }, "_id": "o18UvqLPWLe1A8XJ", @@ -117,8 +117,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 6, @@ -144,7 +144,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Bloodstaff_IoMVDz92WVvfGGdc.json b/src/packs/items/weapons/weapon_Bloodstaff_IoMVDz92WVvfGGdc.json index 2450d69c..ab6bb15d 100644 --- a/src/packs/items/weapons/weapon_Bloodstaff_IoMVDz92WVvfGGdc.json +++ b/src/packs/items/weapons/weapon_Bloodstaff_IoMVDz92WVvfGGdc.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d20", "bonus": 7, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json b/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json index 1fba6130..9b2f455a 100644 --- a/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json +++ b/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Braveshield_QEvgVoz9xKBSKsGi.json b/src/packs/items/weapons/weapon_Braveshield_QEvgVoz9xKBSKsGi.json index aef812a6..2f5ec85d 100644 --- a/src/packs/items/weapons/weapon_Braveshield_QEvgVoz9xKBSKsGi.json +++ b/src/packs/items/weapons/weapon_Braveshield_QEvgVoz9xKBSKsGi.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d4", "bonus": 6, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Bravesword_QZrWAkprA2tL2MOI.json b/src/packs/items/weapons/weapon_Bravesword_QZrWAkprA2tL2MOI.json index 7c3e65f5..412a7083 100644 --- a/src/packs/items/weapons/weapon_Bravesword_QZrWAkprA2tL2MOI.json +++ b/src/packs/items/weapons/weapon_Bravesword_QZrWAkprA2tL2MOI.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 7, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Broadsword_1cwWNt4sqlgA8gCT.json b/src/packs/items/weapons/weapon_Broadsword_1cwWNt4sqlgA8gCT.json index 0e9a557e..87c6f7e8 100644 --- a/src/packs/items/weapons/weapon_Broadsword_1cwWNt4sqlgA8gCT.json +++ b/src/packs/items/weapons/weapon_Broadsword_1cwWNt4sqlgA8gCT.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "type": [ "physical" ], @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Buckler_EmFTp9wzT6MHSaNz.json b/src/packs/items/weapons/weapon_Buckler_EmFTp9wzT6MHSaNz.json index 4d815a6c..be147888 100644 --- a/src/packs/items/weapons/weapon_Buckler_EmFTp9wzT6MHSaNz.json +++ b/src/packs/items/weapons/weapon_Buckler_EmFTp9wzT6MHSaNz.json @@ -87,8 +87,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d4", "bonus": 4, @@ -114,7 +114,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Casting_Sword_2Fbf2cxLfbdGkU4I.json b/src/packs/items/weapons/weapon_Casting_Sword_2Fbf2cxLfbdGkU4I.json index 9af34d45..c861e735 100644 --- a/src/packs/items/weapons/weapon_Casting_Sword_2Fbf2cxLfbdGkU4I.json +++ b/src/packs/items/weapons/weapon_Casting_Sword_2Fbf2cxLfbdGkU4I.json @@ -11,8 +11,8 @@ "type": "attack", "damage": { "includeBase": false, - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -36,7 +36,7 @@ } } } - ] + } }, "range": "far", "roll": { @@ -113,8 +113,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 4, @@ -140,7 +140,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Crossbow_cw7HG1Z7hp7OOLD0.json b/src/packs/items/weapons/weapon_Crossbow_cw7HG1Z7hp7OOLD0.json index 749ef4aa..62ad7f3d 100644 --- a/src/packs/items/weapons/weapon_Crossbow_cw7HG1Z7hp7OOLD0.json +++ b/src/packs/items/weapons/weapon_Crossbow_cw7HG1Z7hp7OOLD0.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Curved_Dagger_Fk69R40svV0kanZD.json b/src/packs/items/weapons/weapon_Curved_Dagger_Fk69R40svV0kanZD.json index a98a56b1..1af2c372 100644 --- a/src/packs/items/weapons/weapon_Curved_Dagger_Fk69R40svV0kanZD.json +++ b/src/packs/items/weapons/weapon_Curved_Dagger_Fk69R40svV0kanZD.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Cutlass_CWrbnethuILXrEpA.json b/src/packs/items/weapons/weapon_Cutlass_CWrbnethuILXrEpA.json index 7343b92c..4750891f 100644 --- a/src/packs/items/weapons/weapon_Cutlass_CWrbnethuILXrEpA.json +++ b/src/packs/items/weapons/weapon_Cutlass_CWrbnethuILXrEpA.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Dagger_iStO0BbeMTTR0rQi.json b/src/packs/items/weapons/weapon_Dagger_iStO0BbeMTTR0rQi.json index 5ef12e05..4818c79f 100644 --- a/src/packs/items/weapons/weapon_Dagger_iStO0BbeMTTR0rQi.json +++ b/src/packs/items/weapons/weapon_Dagger_iStO0BbeMTTR0rQi.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Devouring_Dagger_C5wSGglR8e0euQnY.json b/src/packs/items/weapons/weapon_Devouring_Dagger_C5wSGglR8e0euQnY.json index e00d665c..7775501d 100644 --- a/src/packs/items/weapons/weapon_Devouring_Dagger_C5wSGglR8e0euQnY.json +++ b/src/packs/items/weapons/weapon_Devouring_Dagger_C5wSGglR8e0euQnY.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Double_Flail_xm1yU7k58fMgXxRR.json b/src/packs/items/weapons/weapon_Double_Flail_xm1yU7k58fMgXxRR.json index a118b399..e5b603c6 100644 --- a/src/packs/items/weapons/weapon_Double_Flail_xm1yU7k58fMgXxRR.json +++ b/src/packs/items/weapons/weapon_Double_Flail_xm1yU7k58fMgXxRR.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 8, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Dual_Ended_Sword_nXjuBa215H1sTUK3.json b/src/packs/items/weapons/weapon_Dual_Ended_Sword_nXjuBa215H1sTUK3.json index da15604c..df1c3e54 100644 --- a/src/packs/items/weapons/weapon_Dual_Ended_Sword_nXjuBa215H1sTUK3.json +++ b/src/packs/items/weapons/weapon_Dual_Ended_Sword_nXjuBa215H1sTUK3.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Dualstaff_j8cdNeIUYxxzFVji.json b/src/packs/items/weapons/weapon_Dualstaff_j8cdNeIUYxxzFVji.json index e7c458c3..a3b44d76 100644 --- a/src/packs/items/weapons/weapon_Dualstaff_j8cdNeIUYxxzFVji.json +++ b/src/packs/items/weapons/weapon_Dualstaff_j8cdNeIUYxxzFVji.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Ego_Blade_G7rH31KQ5eEZXcv0.json b/src/packs/items/weapons/weapon_Ego_Blade_G7rH31KQ5eEZXcv0.json index 36d3b0ba..17e24aea 100644 --- a/src/packs/items/weapons/weapon_Ego_Blade_G7rH31KQ5eEZXcv0.json +++ b/src/packs/items/weapons/weapon_Ego_Blade_G7rH31KQ5eEZXcv0.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 4, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Elder_Bow_JdWcn9W1edhAEInL.json b/src/packs/items/weapons/weapon_Elder_Bow_JdWcn9W1edhAEInL.json index 35659402..73bb5a46 100644 --- a/src/packs/items/weapons/weapon_Elder_Bow_JdWcn9W1edhAEInL.json +++ b/src/packs/items/weapons/weapon_Elder_Bow_JdWcn9W1edhAEInL.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Extended_Polearm_fJHKMxZokVP34MCi.json b/src/packs/items/weapons/weapon_Extended_Polearm_fJHKMxZokVP34MCi.json index 62bcb3e0..35829bd5 100644 --- a/src/packs/items/weapons/weapon_Extended_Polearm_fJHKMxZokVP34MCi.json +++ b/src/packs/items/weapons/weapon_Extended_Polearm_fJHKMxZokVP34MCi.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 10, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Finehair_Bow_ykF3jouxHZ6YR8Bg.json b/src/packs/items/weapons/weapon_Finehair_Bow_ykF3jouxHZ6YR8Bg.json index 6dc27659..d1bd58b5 100644 --- a/src/packs/items/weapons/weapon_Finehair_Bow_ykF3jouxHZ6YR8Bg.json +++ b/src/packs/items/weapons/weapon_Finehair_Bow_ykF3jouxHZ6YR8Bg.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 5, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Firestaff_BtCm2RhWEfs00g38.json b/src/packs/items/weapons/weapon_Firestaff_BtCm2RhWEfs00g38.json index 983f98d1..ba7350f4 100644 --- a/src/packs/items/weapons/weapon_Firestaff_BtCm2RhWEfs00g38.json +++ b/src/packs/items/weapons/weapon_Firestaff_BtCm2RhWEfs00g38.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 7, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Flickerfly_Blade_xLJ5RRpUoTRmAC3G.json b/src/packs/items/weapons/weapon_Flickerfly_Blade_xLJ5RRpUoTRmAC3G.json index 5dd5f04e..acf0cfd6 100644 --- a/src/packs/items/weapons/weapon_Flickerfly_Blade_xLJ5RRpUoTRmAC3G.json +++ b/src/packs/items/weapons/weapon_Flickerfly_Blade_xLJ5RRpUoTRmAC3G.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 5, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Floating_Bladeshards_3vti3xfo0wJND7ew.json b/src/packs/items/weapons/weapon_Floating_Bladeshards_3vti3xfo0wJND7ew.json index 232f26e9..0460e12d 100644 --- a/src/packs/items/weapons/weapon_Floating_Bladeshards_3vti3xfo0wJND7ew.json +++ b/src/packs/items/weapons/weapon_Floating_Bladeshards_3vti3xfo0wJND7ew.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Fusion_Gloves_uK1RhtYAsDeoPNGx.json b/src/packs/items/weapons/weapon_Fusion_Gloves_uK1RhtYAsDeoPNGx.json index 747bc046..034ad5cf 100644 --- a/src/packs/items/weapons/weapon_Fusion_Gloves_uK1RhtYAsDeoPNGx.json +++ b/src/packs/items/weapons/weapon_Fusion_Gloves_uK1RhtYAsDeoPNGx.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 9, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Ghostblade_6gFvOFTE97QZ74Zr.json b/src/packs/items/weapons/weapon_Ghostblade_6gFvOFTE97QZ74Zr.json index 7784b43d..0e17e0c2 100644 --- a/src/packs/items/weapons/weapon_Ghostblade_6gFvOFTE97QZ74Zr.json +++ b/src/packs/items/weapons/weapon_Ghostblade_6gFvOFTE97QZ74Zr.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 7, @@ -69,7 +69,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Gilded_Bow_ctTgFfMbM3YtmsYU.json b/src/packs/items/weapons/weapon_Gilded_Bow_ctTgFfMbM3YtmsYU.json index 88f5a163..0147cfdb 100644 --- a/src/packs/items/weapons/weapon_Gilded_Bow_ctTgFfMbM3YtmsYU.json +++ b/src/packs/items/weapons/weapon_Gilded_Bow_ctTgFfMbM3YtmsYU.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 7, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json b/src/packs/items/weapons/weapon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json index ee8afebc..fdf5835e 100644 --- a/src/packs/items/weapons/weapon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json +++ b/src/packs/items/weapons/weapon_Gilded_Falchion_VwcOgqnzjf9LBj2S.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 4, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Glowing_Rings_wG9f5NpCwSbaLy8t.json b/src/packs/items/weapons/weapon_Glowing_Rings_wG9f5NpCwSbaLy8t.json index 214d08a9..8996dbc8 100644 --- a/src/packs/items/weapons/weapon_Glowing_Rings_wG9f5NpCwSbaLy8t.json +++ b/src/packs/items/weapons/weapon_Glowing_Rings_wG9f5NpCwSbaLy8t.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Grappler_iEzPscUc18GuFoB6.json b/src/packs/items/weapons/weapon_Grappler_iEzPscUc18GuFoB6.json index 7f42998b..9a9158c7 100644 --- a/src/packs/items/weapons/weapon_Grappler_iEzPscUc18GuFoB6.json +++ b/src/packs/items/weapons/weapon_Grappler_iEzPscUc18GuFoB6.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": null, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Greatbow_MXBpbqQsZFln4rZk.json b/src/packs/items/weapons/weapon_Greatbow_MXBpbqQsZFln4rZk.json index f56e77c7..87118bb8 100644 --- a/src/packs/items/weapons/weapon_Greatbow_MXBpbqQsZFln4rZk.json +++ b/src/packs/items/weapons/weapon_Greatbow_MXBpbqQsZFln4rZk.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Greatstaff_Yk8pTEmyLLi4095S.json b/src/packs/items/weapons/weapon_Greatstaff_Yk8pTEmyLLi4095S.json index 66c12e5e..798541d1 100644 --- a/src/packs/items/weapons/weapon_Greatstaff_Yk8pTEmyLLi4095S.json +++ b/src/packs/items/weapons/weapon_Greatstaff_Yk8pTEmyLLi4095S.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": null, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Greatsword_70ysaFJDREwTgvZa.json b/src/packs/items/weapons/weapon_Greatsword_70ysaFJDREwTgvZa.json index f60e438d..f0e450a4 100644 --- a/src/packs/items/weapons/weapon_Greatsword_70ysaFJDREwTgvZa.json +++ b/src/packs/items/weapons/weapon_Greatsword_70ysaFJDREwTgvZa.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 3, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Halberd_qT7FfmauAumOjJoq.json b/src/packs/items/weapons/weapon_Halberd_qT7FfmauAumOjJoq.json index 6259e63e..5a990da3 100644 --- a/src/packs/items/weapons/weapon_Halberd_qT7FfmauAumOjJoq.json +++ b/src/packs/items/weapons/weapon_Halberd_qT7FfmauAumOjJoq.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 2, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Hallowed_Axe_Vayg7CnRTFBrunjM.json b/src/packs/items/weapons/weapon_Hallowed_Axe_Vayg7CnRTFBrunjM.json index 9d8885b9..1fe37eba 100644 --- a/src/packs/items/weapons/weapon_Hallowed_Axe_Vayg7CnRTFBrunjM.json +++ b/src/packs/items/weapons/weapon_Hallowed_Axe_Vayg7CnRTFBrunjM.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Hammer_of_Exota_0lAkBEUvbM9Osmqb.json b/src/packs/items/weapons/weapon_Hammer_of_Exota_0lAkBEUvbM9Osmqb.json index ba955850..15e00303 100644 --- a/src/packs/items/weapons/weapon_Hammer_of_Exota_0lAkBEUvbM9Osmqb.json +++ b/src/packs/items/weapons/weapon_Hammer_of_Exota_0lAkBEUvbM9Osmqb.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Hammer_of_Wrath_1R4uzOpWD8bkYRUm.json b/src/packs/items/weapons/weapon_Hammer_of_Wrath_1R4uzOpWD8bkYRUm.json index 122c0769..fdab6f80 100644 --- a/src/packs/items/weapons/weapon_Hammer_of_Wrath_1R4uzOpWD8bkYRUm.json +++ b/src/packs/items/weapons/weapon_Hammer_of_Wrath_1R4uzOpWD8bkYRUm.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 7, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json b/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json index 1396fb1d..4967c6e4 100644 --- a/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json +++ b/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 12, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Hand_Crossbow_zxKt6qXe7uZB6ljm.json b/src/packs/items/weapons/weapon_Hand_Crossbow_zxKt6qXe7uZB6ljm.json index 9f69ffc9..6501f5d4 100644 --- a/src/packs/items/weapons/weapon_Hand_Crossbow_zxKt6qXe7uZB6ljm.json +++ b/src/packs/items/weapons/weapon_Hand_Crossbow_zxKt6qXe7uZB6ljm.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Hand_Runes_3whiedn0jBMNRdIb.json b/src/packs/items/weapons/weapon_Hand_Runes_3whiedn0jBMNRdIb.json index 32761768..00cb6e9b 100644 --- a/src/packs/items/weapons/weapon_Hand_Runes_3whiedn0jBMNRdIb.json +++ b/src/packs/items/weapons/weapon_Hand_Runes_3whiedn0jBMNRdIb.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": null, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Hand_Sling_RAIaoMi6iO1PKIlK.json b/src/packs/items/weapons/weapon_Hand_Sling_RAIaoMi6iO1PKIlK.json index 82641ca1..5c110b70 100644 --- a/src/packs/items/weapons/weapon_Hand_Sling_RAIaoMi6iO1PKIlK.json +++ b/src/packs/items/weapons/weapon_Hand_Sling_RAIaoMi6iO1PKIlK.json @@ -11,8 +11,8 @@ "type": "attack", "damage": { "includeBase": false, - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -36,7 +36,7 @@ } } } - ] + } }, "range": "close", "roll": { @@ -113,8 +113,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -140,7 +140,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Heavy_Frame_Wheelchair_XjPQjhRCH08VUIbr.json b/src/packs/items/weapons/weapon_Heavy_Frame_Wheelchair_XjPQjhRCH08VUIbr.json index db20063d..e74ff4aa 100644 --- a/src/packs/items/weapons/weapon_Heavy_Frame_Wheelchair_XjPQjhRCH08VUIbr.json +++ b/src/packs/items/weapons/weapon_Heavy_Frame_Wheelchair_XjPQjhRCH08VUIbr.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 3, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json b/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json index 8234ab3a..cddd762a 100644 --- a/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json +++ b/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Impact_Gauntlet_Zg6IutksQVOqAg8K.json b/src/packs/items/weapons/weapon_Impact_Gauntlet_Zg6IutksQVOqAg8K.json index 8c8cc81d..5e9891f9 100644 --- a/src/packs/items/weapons/weapon_Impact_Gauntlet_Zg6IutksQVOqAg8K.json +++ b/src/packs/items/weapons/weapon_Impact_Gauntlet_Zg6IutksQVOqAg8K.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 11, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Arcane_Frame_Wheelchair_N9P695V5KKlJbAY5.json b/src/packs/items/weapons/weapon_Improved_Arcane_Frame_Wheelchair_N9P695V5KKlJbAY5.json index fad45a2b..33f96030 100644 --- a/src/packs/items/weapons/weapon_Improved_Arcane_Frame_Wheelchair_N9P695V5KKlJbAY5.json +++ b/src/packs/items/weapons/weapon_Improved_Arcane_Frame_Wheelchair_N9P695V5KKlJbAY5.json @@ -48,8 +48,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 3, @@ -75,7 +75,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Arcane_Gauntlets_kENTDpa1hr5LDhIT.json b/src/packs/items/weapons/weapon_Improved_Arcane_Gauntlets_kENTDpa1hr5LDhIT.json index 40b85dde..1880d45b 100644 --- a/src/packs/items/weapons/weapon_Improved_Arcane_Gauntlets_kENTDpa1hr5LDhIT.json +++ b/src/packs/items/weapons/weapon_Improved_Arcane_Gauntlets_kENTDpa1hr5LDhIT.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 6, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Battleaxe_nxGUpuHLmuKdKsDC.json b/src/packs/items/weapons/weapon_Improved_Battleaxe_nxGUpuHLmuKdKsDC.json index 23068751..4652d62d 100644 --- a/src/packs/items/weapons/weapon_Improved_Battleaxe_nxGUpuHLmuKdKsDC.json +++ b/src/packs/items/weapons/weapon_Improved_Battleaxe_nxGUpuHLmuKdKsDC.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 6, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Broadsword_OcKeLJxvmdT81VBc.json b/src/packs/items/weapons/weapon_Improved_Broadsword_OcKeLJxvmdT81VBc.json index a17caadf..6d6f1921 100644 --- a/src/packs/items/weapons/weapon_Improved_Broadsword_OcKeLJxvmdT81VBc.json +++ b/src/packs/items/weapons/weapon_Improved_Broadsword_OcKeLJxvmdT81VBc.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 3, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Crossbow_55NwHIIZHUeKSE3M.json b/src/packs/items/weapons/weapon_Improved_Crossbow_55NwHIIZHUeKSE3M.json index 185f8f11..5bc082ef 100644 --- a/src/packs/items/weapons/weapon_Improved_Crossbow_55NwHIIZHUeKSE3M.json +++ b/src/packs/items/weapons/weapon_Improved_Crossbow_55NwHIIZHUeKSE3M.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Cutlass_ddRjXnp2vbohu7rJ.json b/src/packs/items/weapons/weapon_Improved_Cutlass_ddRjXnp2vbohu7rJ.json index 520e5679..87dfbfae 100644 --- a/src/packs/items/weapons/weapon_Improved_Cutlass_ddRjXnp2vbohu7rJ.json +++ b/src/packs/items/weapons/weapon_Improved_Cutlass_ddRjXnp2vbohu7rJ.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Dagger_ScjTkb9qrndhlk9S.json b/src/packs/items/weapons/weapon_Improved_Dagger_ScjTkb9qrndhlk9S.json index 1db4504e..194ec879 100644 --- a/src/packs/items/weapons/weapon_Improved_Dagger_ScjTkb9qrndhlk9S.json +++ b/src/packs/items/weapons/weapon_Improved_Dagger_ScjTkb9qrndhlk9S.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Dualstaff_f7hhHlZ5nL3AhYEM.json b/src/packs/items/weapons/weapon_Improved_Dualstaff_f7hhHlZ5nL3AhYEM.json index 84cc75ba..d614130e 100644 --- a/src/packs/items/weapons/weapon_Improved_Dualstaff_f7hhHlZ5nL3AhYEM.json +++ b/src/packs/items/weapons/weapon_Improved_Dualstaff_f7hhHlZ5nL3AhYEM.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Glowing_Rings_N5amhkxR1xn3B7r2.json b/src/packs/items/weapons/weapon_Improved_Glowing_Rings_N5amhkxR1xn3B7r2.json index 01439f5b..f8b29720 100644 --- a/src/packs/items/weapons/weapon_Improved_Glowing_Rings_N5amhkxR1xn3B7r2.json +++ b/src/packs/items/weapons/weapon_Improved_Glowing_Rings_N5amhkxR1xn3B7r2.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 5, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Grappler_3T3o9zfe61t22L1H.json b/src/packs/items/weapons/weapon_Improved_Grappler_3T3o9zfe61t22L1H.json index 74ee510a..31dfff22 100644 --- a/src/packs/items/weapons/weapon_Improved_Grappler_3T3o9zfe61t22L1H.json +++ b/src/packs/items/weapons/weapon_Improved_Grappler_3T3o9zfe61t22L1H.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 2, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Greatstaff_LCuTrYXi4lhg6LqW.json b/src/packs/items/weapons/weapon_Improved_Greatstaff_LCuTrYXi4lhg6LqW.json index cf1bdf63..25be3393 100644 --- a/src/packs/items/weapons/weapon_Improved_Greatstaff_LCuTrYXi4lhg6LqW.json +++ b/src/packs/items/weapons/weapon_Improved_Greatstaff_LCuTrYXi4lhg6LqW.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 3, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Greatsword_FPX4ouDrxXiQ5MDf.json b/src/packs/items/weapons/weapon_Improved_Greatsword_FPX4ouDrxXiQ5MDf.json index f71e5ea6..60e5dd53 100644 --- a/src/packs/items/weapons/weapon_Improved_Greatsword_FPX4ouDrxXiQ5MDf.json +++ b/src/packs/items/weapons/weapon_Improved_Greatsword_FPX4ouDrxXiQ5MDf.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Halberd_F9PETfCQGwczBPif.json b/src/packs/items/weapons/weapon_Improved_Halberd_F9PETfCQGwczBPif.json index 168d8953..fb5a1dcc 100644 --- a/src/packs/items/weapons/weapon_Improved_Halberd_F9PETfCQGwczBPif.json +++ b/src/packs/items/weapons/weapon_Improved_Halberd_F9PETfCQGwczBPif.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 5, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Hallowed_Axe_wFOXMN2uiX4j6Gd9.json b/src/packs/items/weapons/weapon_Improved_Hallowed_Axe_wFOXMN2uiX4j6Gd9.json index a79ad56d..71b28bea 100644 --- a/src/packs/items/weapons/weapon_Improved_Hallowed_Axe_wFOXMN2uiX4j6Gd9.json +++ b/src/packs/items/weapons/weapon_Improved_Hallowed_Axe_wFOXMN2uiX4j6Gd9.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Hand_Crossbow_XEDRkuw3BhMoVBn9.json b/src/packs/items/weapons/weapon_Improved_Hand_Crossbow_XEDRkuw3BhMoVBn9.json index 74e74f2c..91641856 100644 --- a/src/packs/items/weapons/weapon_Improved_Hand_Crossbow_XEDRkuw3BhMoVBn9.json +++ b/src/packs/items/weapons/weapon_Improved_Hand_Crossbow_XEDRkuw3BhMoVBn9.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Hand_Runes_jMEukC3VpNDz5AOD.json b/src/packs/items/weapons/weapon_Improved_Hand_Runes_jMEukC3VpNDz5AOD.json index 546e9762..dc7f2005 100644 --- a/src/packs/items/weapons/weapon_Improved_Hand_Runes_jMEukC3VpNDz5AOD.json +++ b/src/packs/items/weapons/weapon_Improved_Hand_Runes_jMEukC3VpNDz5AOD.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Heavy_Frame_Wheelchair_L5KeCtrs768PmYWW.json b/src/packs/items/weapons/weapon_Improved_Heavy_Frame_Wheelchair_L5KeCtrs768PmYWW.json index 3a386fa8..e65cc221 100644 --- a/src/packs/items/weapons/weapon_Improved_Heavy_Frame_Wheelchair_L5KeCtrs768PmYWW.json +++ b/src/packs/items/weapons/weapon_Improved_Heavy_Frame_Wheelchair_L5KeCtrs768PmYWW.json @@ -48,8 +48,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 6, @@ -75,7 +75,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Light_Frame_Wheelchair_ZJsetdHKV77ygtCE.json b/src/packs/items/weapons/weapon_Improved_Light_Frame_Wheelchair_ZJsetdHKV77ygtCE.json index 6fbe3c9d..315ceaf3 100644 --- a/src/packs/items/weapons/weapon_Improved_Light_Frame_Wheelchair_ZJsetdHKV77ygtCE.json +++ b/src/packs/items/weapons/weapon_Improved_Light_Frame_Wheelchair_ZJsetdHKV77ygtCE.json @@ -77,8 +77,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 3, @@ -104,7 +104,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Longbow_NacNonjbzyoVMNhI.json b/src/packs/items/weapons/weapon_Improved_Longbow_NacNonjbzyoVMNhI.json index 787d5a28..c197f726 100644 --- a/src/packs/items/weapons/weapon_Improved_Longbow_NacNonjbzyoVMNhI.json +++ b/src/packs/items/weapons/weapon_Improved_Longbow_NacNonjbzyoVMNhI.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Longsword_QyBZ5NxM8F9nCL9s.json b/src/packs/items/weapons/weapon_Improved_Longsword_QyBZ5NxM8F9nCL9s.json index 982ca3ac..aaa38270 100644 --- a/src/packs/items/weapons/weapon_Improved_Longsword_QyBZ5NxM8F9nCL9s.json +++ b/src/packs/items/weapons/weapon_Improved_Longsword_QyBZ5NxM8F9nCL9s.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 6, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Mace_zSLx52U4Yltqx8F1.json b/src/packs/items/weapons/weapon_Improved_Mace_zSLx52U4Yltqx8F1.json index c1b626d5..1ce84304 100644 --- a/src/packs/items/weapons/weapon_Improved_Mace_zSLx52U4Yltqx8F1.json +++ b/src/packs/items/weapons/weapon_Improved_Mace_zSLx52U4Yltqx8F1.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Quarterstaff_BEmAR60PM3ZaiNXa.json b/src/packs/items/weapons/weapon_Improved_Quarterstaff_BEmAR60PM3ZaiNXa.json index 888022ed..9dc81b98 100644 --- a/src/packs/items/weapons/weapon_Improved_Quarterstaff_BEmAR60PM3ZaiNXa.json +++ b/src/packs/items/weapons/weapon_Improved_Quarterstaff_BEmAR60PM3ZaiNXa.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 6, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Rapier_LFPH8nD2f4Blv3AM.json b/src/packs/items/weapons/weapon_Improved_Rapier_LFPH8nD2f4Blv3AM.json index cc6099da..e9db95d1 100644 --- a/src/packs/items/weapons/weapon_Improved_Rapier_LFPH8nD2f4Blv3AM.json +++ b/src/packs/items/weapons/weapon_Improved_Rapier_LFPH8nD2f4Blv3AM.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 3, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Returning_Blade_SKNwkW23eVQjN4Zy.json b/src/packs/items/weapons/weapon_Improved_Returning_Blade_SKNwkW23eVQjN4Zy.json index 31688a23..1dff30a1 100644 --- a/src/packs/items/weapons/weapon_Improved_Returning_Blade_SKNwkW23eVQjN4Zy.json +++ b/src/packs/items/weapons/weapon_Improved_Returning_Blade_SKNwkW23eVQjN4Zy.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 3, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Round_Shield_DlinEBGZfIlvreO3.json b/src/packs/items/weapons/weapon_Improved_Round_Shield_DlinEBGZfIlvreO3.json index 53a8e9b6..091355c2 100644 --- a/src/packs/items/weapons/weapon_Improved_Round_Shield_DlinEBGZfIlvreO3.json +++ b/src/packs/items/weapons/weapon_Improved_Round_Shield_DlinEBGZfIlvreO3.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d4", "bonus": 2, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Scepter_tj26lbNkwy8bORF4.json b/src/packs/items/weapons/weapon_Improved_Scepter_tj26lbNkwy8bORF4.json index dc41692f..ac226b84 100644 --- a/src/packs/items/weapons/weapon_Improved_Scepter_tj26lbNkwy8bORF4.json +++ b/src/packs/items/weapons/weapon_Improved_Scepter_tj26lbNkwy8bORF4.json @@ -11,8 +11,8 @@ "type": "attack", "damage": { "includeBase": false, - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -38,7 +38,7 @@ } } } - ] + } }, "range": "melee", "roll": { @@ -115,8 +115,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 3, @@ -142,7 +142,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Shortbow_6ZWl6ARfvYBaAMwY.json b/src/packs/items/weapons/weapon_Improved_Shortbow_6ZWl6ARfvYBaAMwY.json index 421b8f8e..b0e101fb 100644 --- a/src/packs/items/weapons/weapon_Improved_Shortbow_6ZWl6ARfvYBaAMwY.json +++ b/src/packs/items/weapons/weapon_Improved_Shortbow_6ZWl6ARfvYBaAMwY.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Shortstaff_Mn8ja5Oi1sXvvPSk.json b/src/packs/items/weapons/weapon_Improved_Shortstaff_Mn8ja5Oi1sXvvPSk.json index 08bf9251..7d90cf94 100644 --- a/src/packs/items/weapons/weapon_Improved_Shortstaff_Mn8ja5Oi1sXvvPSk.json +++ b/src/packs/items/weapons/weapon_Improved_Shortstaff_Mn8ja5Oi1sXvvPSk.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Shortsword_rSyBNRwemBVuTo3H.json b/src/packs/items/weapons/weapon_Improved_Shortsword_rSyBNRwemBVuTo3H.json index df2f324c..96ecd37f 100644 --- a/src/packs/items/weapons/weapon_Improved_Shortsword_rSyBNRwemBVuTo3H.json +++ b/src/packs/items/weapons/weapon_Improved_Shortsword_rSyBNRwemBVuTo3H.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 2, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Small_Dagger_nMuF8ZDZ2aXZVTg6.json b/src/packs/items/weapons/weapon_Improved_Small_Dagger_nMuF8ZDZ2aXZVTg6.json index b69332a5..1553d75d 100644 --- a/src/packs/items/weapons/weapon_Improved_Small_Dagger_nMuF8ZDZ2aXZVTg6.json +++ b/src/packs/items/weapons/weapon_Improved_Small_Dagger_nMuF8ZDZ2aXZVTg6.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 2, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Spear_j5Pt1thLfcvopBij.json b/src/packs/items/weapons/weapon_Improved_Spear_j5Pt1thLfcvopBij.json index ae6b0987..790f9e51 100644 --- a/src/packs/items/weapons/weapon_Improved_Spear_j5Pt1thLfcvopBij.json +++ b/src/packs/items/weapons/weapon_Improved_Spear_j5Pt1thLfcvopBij.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Tower_Shield_bxt3NsbMqTSdI5ab.json b/src/packs/items/weapons/weapon_Improved_Tower_Shield_bxt3NsbMqTSdI5ab.json index 839d4352..6f55fe3d 100644 --- a/src/packs/items/weapons/weapon_Improved_Tower_Shield_bxt3NsbMqTSdI5ab.json +++ b/src/packs/items/weapons/weapon_Improved_Tower_Shield_bxt3NsbMqTSdI5ab.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 2, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Wand_6d9B2b5X2d2U56jt.json b/src/packs/items/weapons/weapon_Improved_Wand_6d9B2b5X2d2U56jt.json index cecb818d..53ee6746 100644 --- a/src/packs/items/weapons/weapon_Improved_Wand_6d9B2b5X2d2U56jt.json +++ b/src/packs/items/weapons/weapon_Improved_Wand_6d9B2b5X2d2U56jt.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Warhammer_pxaN4ZK4eqKrjtWj.json b/src/packs/items/weapons/weapon_Improved_Warhammer_pxaN4ZK4eqKrjtWj.json index 40a5faba..aa24c4ad 100644 --- a/src/packs/items/weapons/weapon_Improved_Warhammer_pxaN4ZK4eqKrjtWj.json +++ b/src/packs/items/weapons/weapon_Improved_Warhammer_pxaN4ZK4eqKrjtWj.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Improved_Whip_ftTp8VlsBQ1r4LFD.json b/src/packs/items/weapons/weapon_Improved_Whip_ftTp8VlsBQ1r4LFD.json index dff7fb25..4dd1dcb1 100644 --- a/src/packs/items/weapons/weapon_Improved_Whip_ftTp8VlsBQ1r4LFD.json +++ b/src/packs/items/weapons/weapon_Improved_Whip_ftTp8VlsBQ1r4LFD.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 2, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Keeper_s_Staff_q382JqMkqLaaFLIr.json b/src/packs/items/weapons/weapon_Keeper_s_Staff_q382JqMkqLaaFLIr.json index 27b8044b..21fe863b 100644 --- a/src/packs/items/weapons/weapon_Keeper_s_Staff_q382JqMkqLaaFLIr.json +++ b/src/packs/items/weapons/weapon_Keeper_s_Staff_q382JqMkqLaaFLIr.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Knuckle_Blades_U8gfyvxoHm024inM.json b/src/packs/items/weapons/weapon_Knuckle_Blades_U8gfyvxoHm024inM.json index 6bc27412..56ff6d62 100644 --- a/src/packs/items/weapons/weapon_Knuckle_Blades_U8gfyvxoHm024inM.json +++ b/src/packs/items/weapons/weapon_Knuckle_Blades_U8gfyvxoHm024inM.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 6, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Knuckle_Claws_SFqganS8Du4aEKjQ.json b/src/packs/items/weapons/weapon_Knuckle_Claws_SFqganS8Du4aEKjQ.json index df995a3f..a7629e7d 100644 --- a/src/packs/items/weapons/weapon_Knuckle_Claws_SFqganS8Du4aEKjQ.json +++ b/src/packs/items/weapons/weapon_Knuckle_Claws_SFqganS8Du4aEKjQ.json @@ -72,8 +72,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 8, @@ -99,7 +99,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json b/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json index 4958bbe5..0dd7d23a 100644 --- a/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json +++ b/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 7, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Arcane_Frame_Wheelchair_gA2tiET9VHGhwMoO.json b/src/packs/items/weapons/weapon_Legendary_Arcane_Frame_Wheelchair_gA2tiET9VHGhwMoO.json index d8068f41..f947acac 100644 --- a/src/packs/items/weapons/weapon_Legendary_Arcane_Frame_Wheelchair_gA2tiET9VHGhwMoO.json +++ b/src/packs/items/weapons/weapon_Legendary_Arcane_Frame_Wheelchair_gA2tiET9VHGhwMoO.json @@ -48,8 +48,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 9, @@ -75,7 +75,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Arcane_Gauntlets_umADDPYCaykXDc1v.json b/src/packs/items/weapons/weapon_Legendary_Arcane_Gauntlets_umADDPYCaykXDc1v.json index 94da915c..c7adc0f2 100644 --- a/src/packs/items/weapons/weapon_Legendary_Arcane_Gauntlets_umADDPYCaykXDc1v.json +++ b/src/packs/items/weapons/weapon_Legendary_Arcane_Gauntlets_umADDPYCaykXDc1v.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 12, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Battleaxe_1nztpLzoHGfbKf5x.json b/src/packs/items/weapons/weapon_Legendary_Battleaxe_1nztpLzoHGfbKf5x.json index 06a8a85f..3c22e3f2 100644 --- a/src/packs/items/weapons/weapon_Legendary_Battleaxe_1nztpLzoHGfbKf5x.json +++ b/src/packs/items/weapons/weapon_Legendary_Battleaxe_1nztpLzoHGfbKf5x.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 12, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Broadsword_y3hfTPfZhMognyaJ.json b/src/packs/items/weapons/weapon_Legendary_Broadsword_y3hfTPfZhMognyaJ.json index 786ba8b6..361d0353 100644 --- a/src/packs/items/weapons/weapon_Legendary_Broadsword_y3hfTPfZhMognyaJ.json +++ b/src/packs/items/weapons/weapon_Legendary_Broadsword_y3hfTPfZhMognyaJ.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Crossbow_1G6xX2QL9O0Rsgz7.json b/src/packs/items/weapons/weapon_Legendary_Crossbow_1G6xX2QL9O0Rsgz7.json index e263bcbe..358cdd56 100644 --- a/src/packs/items/weapons/weapon_Legendary_Crossbow_1G6xX2QL9O0Rsgz7.json +++ b/src/packs/items/weapons/weapon_Legendary_Crossbow_1G6xX2QL9O0Rsgz7.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 10, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Cutlass_Rpyz0jbFJ1SwqfyD.json b/src/packs/items/weapons/weapon_Legendary_Cutlass_Rpyz0jbFJ1SwqfyD.json index 2c284332..9ce1b48a 100644 --- a/src/packs/items/weapons/weapon_Legendary_Cutlass_Rpyz0jbFJ1SwqfyD.json +++ b/src/packs/items/weapons/weapon_Legendary_Cutlass_Rpyz0jbFJ1SwqfyD.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 10, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Dagger_GmTg3Fdne1UPNs8t.json b/src/packs/items/weapons/weapon_Legendary_Dagger_GmTg3Fdne1UPNs8t.json index 0e9e988d..a7bb2fc9 100644 --- a/src/packs/items/weapons/weapon_Legendary_Dagger_GmTg3Fdne1UPNs8t.json +++ b/src/packs/items/weapons/weapon_Legendary_Dagger_GmTg3Fdne1UPNs8t.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 10, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Dualstaff_o3rsLvImcLAx5TvD.json b/src/packs/items/weapons/weapon_Legendary_Dualstaff_o3rsLvImcLAx5TvD.json index 9c618ba5..b06909e4 100644 --- a/src/packs/items/weapons/weapon_Legendary_Dualstaff_o3rsLvImcLAx5TvD.json +++ b/src/packs/items/weapons/weapon_Legendary_Dualstaff_o3rsLvImcLAx5TvD.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 12, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Glowing_Rings_PReWrfuPjoNQuieo.json b/src/packs/items/weapons/weapon_Legendary_Glowing_Rings_PReWrfuPjoNQuieo.json index 1710084f..ca9937ee 100644 --- a/src/packs/items/weapons/weapon_Legendary_Glowing_Rings_PReWrfuPjoNQuieo.json +++ b/src/packs/items/weapons/weapon_Legendary_Glowing_Rings_PReWrfuPjoNQuieo.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 11, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Grappler_IrtUj0UntBMNn49G.json b/src/packs/items/weapons/weapon_Legendary_Grappler_IrtUj0UntBMNn49G.json index 83334710..f20425b2 100644 --- a/src/packs/items/weapons/weapon_Legendary_Grappler_IrtUj0UntBMNn49G.json +++ b/src/packs/items/weapons/weapon_Legendary_Grappler_IrtUj0UntBMNn49G.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Greatstaff_jDtvEabkHY1GFgfc.json b/src/packs/items/weapons/weapon_Legendary_Greatstaff_jDtvEabkHY1GFgfc.json index a5ea82f9..09fd1f7a 100644 --- a/src/packs/items/weapons/weapon_Legendary_Greatstaff_jDtvEabkHY1GFgfc.json +++ b/src/packs/items/weapons/weapon_Legendary_Greatstaff_jDtvEabkHY1GFgfc.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 9, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Greatsword_zMZ46F9VR7zdTxb9.json b/src/packs/items/weapons/weapon_Legendary_Greatsword_zMZ46F9VR7zdTxb9.json index 840e7ec7..aa9d2ef0 100644 --- a/src/packs/items/weapons/weapon_Legendary_Greatsword_zMZ46F9VR7zdTxb9.json +++ b/src/packs/items/weapons/weapon_Legendary_Greatsword_zMZ46F9VR7zdTxb9.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 12, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Halberd_1AuMNiJz96Ez9fur.json b/src/packs/items/weapons/weapon_Legendary_Halberd_1AuMNiJz96Ez9fur.json index 22e6af1b..af6b2a07 100644 --- a/src/packs/items/weapons/weapon_Legendary_Halberd_1AuMNiJz96Ez9fur.json +++ b/src/packs/items/weapons/weapon_Legendary_Halberd_1AuMNiJz96Ez9fur.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 11, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Hallowed_Axe_0HmhnZnv1I6uX69c.json b/src/packs/items/weapons/weapon_Legendary_Hallowed_Axe_0HmhnZnv1I6uX69c.json index 2bf11f05..70379d45 100644 --- a/src/packs/items/weapons/weapon_Legendary_Hallowed_Axe_0HmhnZnv1I6uX69c.json +++ b/src/packs/items/weapons/weapon_Legendary_Hallowed_Axe_0HmhnZnv1I6uX69c.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 10, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Hand_Crossbow_32nYyMaeDWaakSxz.json b/src/packs/items/weapons/weapon_Legendary_Hand_Crossbow_32nYyMaeDWaakSxz.json index ff43c21b..930310a8 100644 --- a/src/packs/items/weapons/weapon_Legendary_Hand_Crossbow_32nYyMaeDWaakSxz.json +++ b/src/packs/items/weapons/weapon_Legendary_Hand_Crossbow_32nYyMaeDWaakSxz.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 7, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Hand_Runes_DWLkswhluXuMy3bB.json b/src/packs/items/weapons/weapon_Legendary_Hand_Runes_DWLkswhluXuMy3bB.json index 2dcad490..e909f460 100644 --- a/src/packs/items/weapons/weapon_Legendary_Hand_Runes_DWLkswhluXuMy3bB.json +++ b/src/packs/items/weapons/weapon_Legendary_Hand_Runes_DWLkswhluXuMy3bB.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Heavy_Frame_Wheelchair_S6nB0CNlzdU05o5U.json b/src/packs/items/weapons/weapon_Legendary_Heavy_Frame_Wheelchair_S6nB0CNlzdU05o5U.json index 7e561c26..d8816fc9 100644 --- a/src/packs/items/weapons/weapon_Legendary_Heavy_Frame_Wheelchair_S6nB0CNlzdU05o5U.json +++ b/src/packs/items/weapons/weapon_Legendary_Heavy_Frame_Wheelchair_S6nB0CNlzdU05o5U.json @@ -48,8 +48,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 12, @@ -75,7 +75,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Light_Frame_Wheelchair_Xt8tVSn5Fu6ly6LF.json b/src/packs/items/weapons/weapon_Legendary_Light_Frame_Wheelchair_Xt8tVSn5Fu6ly6LF.json index 9f96601c..7afbd979 100644 --- a/src/packs/items/weapons/weapon_Legendary_Light_Frame_Wheelchair_Xt8tVSn5Fu6ly6LF.json +++ b/src/packs/items/weapons/weapon_Legendary_Light_Frame_Wheelchair_Xt8tVSn5Fu6ly6LF.json @@ -77,8 +77,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -104,7 +104,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Longbow_Utt1GpoH1fhaTOtN.json b/src/packs/items/weapons/weapon_Legendary_Longbow_Utt1GpoH1fhaTOtN.json index bb0b8ab3..bd5ab13b 100644 --- a/src/packs/items/weapons/weapon_Legendary_Longbow_Utt1GpoH1fhaTOtN.json +++ b/src/packs/items/weapons/weapon_Legendary_Longbow_Utt1GpoH1fhaTOtN.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 12, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Longsword_14abPqQcROJfDChR.json b/src/packs/items/weapons/weapon_Legendary_Longsword_14abPqQcROJfDChR.json index 9399edd4..e6555b02 100644 --- a/src/packs/items/weapons/weapon_Legendary_Longsword_14abPqQcROJfDChR.json +++ b/src/packs/items/weapons/weapon_Legendary_Longsword_14abPqQcROJfDChR.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 12, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Mace_RsDsy7tIhrhaAQQc.json b/src/packs/items/weapons/weapon_Legendary_Mace_RsDsy7tIhrhaAQQc.json index 95de32f0..7f4a9dab 100644 --- a/src/packs/items/weapons/weapon_Legendary_Mace_RsDsy7tIhrhaAQQc.json +++ b/src/packs/items/weapons/weapon_Legendary_Mace_RsDsy7tIhrhaAQQc.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 10, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Quarterstaff_1ZciqG7vIKLYpKsP.json b/src/packs/items/weapons/weapon_Legendary_Quarterstaff_1ZciqG7vIKLYpKsP.json index cf25daa7..91bc8ac8 100644 --- a/src/packs/items/weapons/weapon_Legendary_Quarterstaff_1ZciqG7vIKLYpKsP.json +++ b/src/packs/items/weapons/weapon_Legendary_Quarterstaff_1ZciqG7vIKLYpKsP.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 12, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Rapier_BakN97v4jTePcXiZ.json b/src/packs/items/weapons/weapon_Legendary_Rapier_BakN97v4jTePcXiZ.json index 59a49063..d5bac6d1 100644 --- a/src/packs/items/weapons/weapon_Legendary_Rapier_BakN97v4jTePcXiZ.json +++ b/src/packs/items/weapons/weapon_Legendary_Rapier_BakN97v4jTePcXiZ.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Returning_Blade_mcj3CPkcSSDdAcBB.json b/src/packs/items/weapons/weapon_Legendary_Returning_Blade_mcj3CPkcSSDdAcBB.json index 211c88c2..b3f3fc3d 100644 --- a/src/packs/items/weapons/weapon_Legendary_Returning_Blade_mcj3CPkcSSDdAcBB.json +++ b/src/packs/items/weapons/weapon_Legendary_Returning_Blade_mcj3CPkcSSDdAcBB.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Round_Shield_A28WL9E2lJ3iLZHW.json b/src/packs/items/weapons/weapon_Legendary_Round_Shield_A28WL9E2lJ3iLZHW.json index c7b18355..5eedb6c2 100644 --- a/src/packs/items/weapons/weapon_Legendary_Round_Shield_A28WL9E2lJ3iLZHW.json +++ b/src/packs/items/weapons/weapon_Legendary_Round_Shield_A28WL9E2lJ3iLZHW.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d4", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Scepter_IZ4CWNxfuM46JeCN.json b/src/packs/items/weapons/weapon_Legendary_Scepter_IZ4CWNxfuM46JeCN.json index 9630fd69..882ecfdf 100644 --- a/src/packs/items/weapons/weapon_Legendary_Scepter_IZ4CWNxfuM46JeCN.json +++ b/src/packs/items/weapons/weapon_Legendary_Scepter_IZ4CWNxfuM46JeCN.json @@ -11,8 +11,8 @@ "type": "attack", "damage": { "includeBase": false, - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -38,7 +38,7 @@ } } } - ] + } }, "range": "melee", "roll": { @@ -115,8 +115,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 9, @@ -142,7 +142,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Shortbow_j7kp36jaetfn5jb3.json b/src/packs/items/weapons/weapon_Legendary_Shortbow_j7kp36jaetfn5jb3.json index 7fad3f95..b3dbd210 100644 --- a/src/packs/items/weapons/weapon_Legendary_Shortbow_j7kp36jaetfn5jb3.json +++ b/src/packs/items/weapons/weapon_Legendary_Shortbow_j7kp36jaetfn5jb3.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 12, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Shortstaff_D3SbNvNJZAFuzfhg.json b/src/packs/items/weapons/weapon_Legendary_Shortstaff_D3SbNvNJZAFuzfhg.json index 59d53e44..3d29bc25 100644 --- a/src/packs/items/weapons/weapon_Legendary_Shortstaff_D3SbNvNJZAFuzfhg.json +++ b/src/packs/items/weapons/weapon_Legendary_Shortstaff_D3SbNvNJZAFuzfhg.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 10, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Shortsword_dEumq3BIZBk5xYTk.json b/src/packs/items/weapons/weapon_Legendary_Shortsword_dEumq3BIZBk5xYTk.json index d7f9632a..a2203a1d 100644 --- a/src/packs/items/weapons/weapon_Legendary_Shortsword_dEumq3BIZBk5xYTk.json +++ b/src/packs/items/weapons/weapon_Legendary_Shortsword_dEumq3BIZBk5xYTk.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Small_Dagger_Px3Rh3kIvAqyISxJ.json b/src/packs/items/weapons/weapon_Legendary_Small_Dagger_Px3Rh3kIvAqyISxJ.json index c6bf5437..cf63bde2 100644 --- a/src/packs/items/weapons/weapon_Legendary_Small_Dagger_Px3Rh3kIvAqyISxJ.json +++ b/src/packs/items/weapons/weapon_Legendary_Small_Dagger_Px3Rh3kIvAqyISxJ.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Spear_4e5pWxi2qohuGsWh.json b/src/packs/items/weapons/weapon_Legendary_Spear_4e5pWxi2qohuGsWh.json index 114ea79e..e0248f4b 100644 --- a/src/packs/items/weapons/weapon_Legendary_Spear_4e5pWxi2qohuGsWh.json +++ b/src/packs/items/weapons/weapon_Legendary_Spear_4e5pWxi2qohuGsWh.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 12, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Tower_Shield_MaJIROht7A9LxIZx.json b/src/packs/items/weapons/weapon_Legendary_Tower_Shield_MaJIROht7A9LxIZx.json index 47e707d3..49ca454a 100644 --- a/src/packs/items/weapons/weapon_Legendary_Tower_Shield_MaJIROht7A9LxIZx.json +++ b/src/packs/items/weapons/weapon_Legendary_Tower_Shield_MaJIROht7A9LxIZx.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Wand_wPjg0LufJH9vUfVM.json b/src/packs/items/weapons/weapon_Legendary_Wand_wPjg0LufJH9vUfVM.json index 4bb0acd9..65925525 100644 --- a/src/packs/items/weapons/weapon_Legendary_Wand_wPjg0LufJH9vUfVM.json +++ b/src/packs/items/weapons/weapon_Legendary_Wand_wPjg0LufJH9vUfVM.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 10, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Warhammer_W9ymfEDck2icfvla.json b/src/packs/items/weapons/weapon_Legendary_Warhammer_W9ymfEDck2icfvla.json index 0918f8b1..562122d2 100644 --- a/src/packs/items/weapons/weapon_Legendary_Warhammer_W9ymfEDck2icfvla.json +++ b/src/packs/items/weapons/weapon_Legendary_Warhammer_W9ymfEDck2icfvla.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 12, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Legendary_Whip_Wcdbf6yS3LEt7nsg.json b/src/packs/items/weapons/weapon_Legendary_Whip_Wcdbf6yS3LEt7nsg.json index 83ac59c7..baf6d369 100644 --- a/src/packs/items/weapons/weapon_Legendary_Whip_Wcdbf6yS3LEt7nsg.json +++ b/src/packs/items/weapons/weapon_Legendary_Whip_Wcdbf6yS3LEt7nsg.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 6, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Light_Frame_Wheelchair_iaGnlUkShBgdeMo0.json b/src/packs/items/weapons/weapon_Light_Frame_Wheelchair_iaGnlUkShBgdeMo0.json index fdda2b56..7fadde01 100644 --- a/src/packs/items/weapons/weapon_Light_Frame_Wheelchair_iaGnlUkShBgdeMo0.json +++ b/src/packs/items/weapons/weapon_Light_Frame_Wheelchair_iaGnlUkShBgdeMo0.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "type": [ "physical" ], @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Longbow_YfVs6Se903az4Yet.json b/src/packs/items/weapons/weapon_Longbow_YfVs6Se903az4Yet.json index c6e98bc4..43af46ab 100644 --- a/src/packs/items/weapons/weapon_Longbow_YfVs6Se903az4Yet.json +++ b/src/packs/items/weapons/weapon_Longbow_YfVs6Se903az4Yet.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 3, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Longsword_Iv8BZM1R24QMT72M.json b/src/packs/items/weapons/weapon_Longsword_Iv8BZM1R24QMT72M.json index cba1bac8..3961d7c3 100644 --- a/src/packs/items/weapons/weapon_Longsword_Iv8BZM1R24QMT72M.json +++ b/src/packs/items/weapons/weapon_Longsword_Iv8BZM1R24QMT72M.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Mace_cKQCDyM2UopDL9zF.json b/src/packs/items/weapons/weapon_Mace_cKQCDyM2UopDL9zF.json index 0e6670f4..7a7582cb 100644 --- a/src/packs/items/weapons/weapon_Mace_cKQCDyM2UopDL9zF.json +++ b/src/packs/items/weapons/weapon_Mace_cKQCDyM2UopDL9zF.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json b/src/packs/items/weapons/weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json index 3b5983f5..dbd218f1 100644 --- a/src/packs/items/weapons/weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json +++ b/src/packs/items/weapons/weapon_Mage_Orb_XKBmBUEoGLdLcuqQ.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 7, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json b/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json index 42a65010..9dbbb1c1 100644 --- a/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json +++ b/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 13, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Meridian_Cutlass_Gi26Zk9VqlAAgx3E.json b/src/packs/items/weapons/weapon_Meridian_Cutlass_Gi26Zk9VqlAAgx3E.json index dcc236a0..b83a18be 100644 --- a/src/packs/items/weapons/weapon_Meridian_Cutlass_Gi26Zk9VqlAAgx3E.json +++ b/src/packs/items/weapons/weapon_Meridian_Cutlass_Gi26Zk9VqlAAgx3E.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 5, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Midas_Scythe_BdLfy5i488VZgkjP.json b/src/packs/items/weapons/weapon_Midas_Scythe_BdLfy5i488VZgkjP.json index 799413be..57fe2367 100644 --- a/src/packs/items/weapons/weapon_Midas_Scythe_BdLfy5i488VZgkjP.json +++ b/src/packs/items/weapons/weapon_Midas_Scythe_BdLfy5i488VZgkjP.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Parrying_Dagger_taAZDkDCpeNgxhnn.json b/src/packs/items/weapons/weapon_Parrying_Dagger_taAZDkDCpeNgxhnn.json index 482d813b..b5844e89 100644 --- a/src/packs/items/weapons/weapon_Parrying_Dagger_taAZDkDCpeNgxhnn.json +++ b/src/packs/items/weapons/weapon_Parrying_Dagger_taAZDkDCpeNgxhnn.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 2, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Powered_Gauntlet_bW3xw5S9DbaLCN3E.json b/src/packs/items/weapons/weapon_Powered_Gauntlet_bW3xw5S9DbaLCN3E.json index 67922f8b..3c4ebd1c 100644 --- a/src/packs/items/weapons/weapon_Powered_Gauntlet_bW3xw5S9DbaLCN3E.json +++ b/src/packs/items/weapons/weapon_Powered_Gauntlet_bW3xw5S9DbaLCN3E.json @@ -85,8 +85,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -112,7 +112,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Primer_Shard_SxcblanBvqaest3A.json b/src/packs/items/weapons/weapon_Primer_Shard_SxcblanBvqaest3A.json index fb42af74..0cf59179 100644 --- a/src/packs/items/weapons/weapon_Primer_Shard_SxcblanBvqaest3A.json +++ b/src/packs/items/weapons/weapon_Primer_Shard_SxcblanBvqaest3A.json @@ -72,8 +72,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d4", "bonus": null, @@ -99,7 +99,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Quarterstaff_mlIj88p1wcQNjEDG.json b/src/packs/items/weapons/weapon_Quarterstaff_mlIj88p1wcQNjEDG.json index 37c56a21..00940b6c 100644 --- a/src/packs/items/weapons/weapon_Quarterstaff_mlIj88p1wcQNjEDG.json +++ b/src/packs/items/weapons/weapon_Quarterstaff_mlIj88p1wcQNjEDG.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Rapier_zkAgEW6zMkRZalEm.json b/src/packs/items/weapons/weapon_Rapier_zkAgEW6zMkRZalEm.json index b4de1c00..45642f14 100644 --- a/src/packs/items/weapons/weapon_Rapier_zkAgEW6zMkRZalEm.json +++ b/src/packs/items/weapons/weapon_Rapier_zkAgEW6zMkRZalEm.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "type": [ "physical" ], @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Retractable_Saber_i8CqVTzqoRoCewNe.json b/src/packs/items/weapons/weapon_Retractable_Saber_i8CqVTzqoRoCewNe.json index c1b350a3..548de60f 100644 --- a/src/packs/items/weapons/weapon_Retractable_Saber_i8CqVTzqoRoCewNe.json +++ b/src/packs/items/weapons/weapon_Retractable_Saber_i8CqVTzqoRoCewNe.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 7, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Returning_Axe_FtsQGwOg3r8uUCST.json b/src/packs/items/weapons/weapon_Returning_Axe_FtsQGwOg3r8uUCST.json index 76a887ae..9f3cca62 100644 --- a/src/packs/items/weapons/weapon_Returning_Axe_FtsQGwOg3r8uUCST.json +++ b/src/packs/items/weapons/weapon_Returning_Axe_FtsQGwOg3r8uUCST.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Returning_Blade_4fQpVfQ3NVwTHStA.json b/src/packs/items/weapons/weapon_Returning_Blade_4fQpVfQ3NVwTHStA.json index 74be4227..efd81e48 100644 --- a/src/packs/items/weapons/weapon_Returning_Blade_4fQpVfQ3NVwTHStA.json +++ b/src/packs/items/weapons/weapon_Returning_Blade_4fQpVfQ3NVwTHStA.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": null, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Ricochet_Axes_E9QDh5o9eQ1Qx0kH.json b/src/packs/items/weapons/weapon_Ricochet_Axes_E9QDh5o9eQ1Qx0kH.json index 1067bb02..34f3b200 100644 --- a/src/packs/items/weapons/weapon_Ricochet_Axes_E9QDh5o9eQ1Qx0kH.json +++ b/src/packs/items/weapons/weapon_Ricochet_Axes_E9QDh5o9eQ1Qx0kH.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 11, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Round_Shield_mxwWKDujgsRcZWPT.json b/src/packs/items/weapons/weapon_Round_Shield_mxwWKDujgsRcZWPT.json index 47b096af..ccba4b0e 100644 --- a/src/packs/items/weapons/weapon_Round_Shield_mxwWKDujgsRcZWPT.json +++ b/src/packs/items/weapons/weapon_Round_Shield_mxwWKDujgsRcZWPT.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d4", "bonus": null, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Runes_of_Ruination_EG6mZhr3ib56r974.json b/src/packs/items/weapons/weapon_Runes_of_Ruination_EG6mZhr3ib56r974.json index e0676ed2..3a966fe0 100644 --- a/src/packs/items/weapons/weapon_Runes_of_Ruination_EG6mZhr3ib56r974.json +++ b/src/packs/items/weapons/weapon_Runes_of_Ruination_EG6mZhr3ib56r974.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d20", "bonus": 4, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Scepter_GZh345N8fmuS4Jeh.json b/src/packs/items/weapons/weapon_Scepter_GZh345N8fmuS4Jeh.json index 04ce4c4b..f1e85577 100644 --- a/src/packs/items/weapons/weapon_Scepter_GZh345N8fmuS4Jeh.json +++ b/src/packs/items/weapons/weapon_Scepter_GZh345N8fmuS4Jeh.json @@ -11,8 +11,8 @@ "type": "attack", "damage": { "includeBase": false, - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -38,7 +38,7 @@ } } } - ] + } }, "range": "melee", "roll": { @@ -115,8 +115,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": null, @@ -142,7 +142,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Scepter_of_Elias_acPGwIaUhx3R0mTq.json b/src/packs/items/weapons/weapon_Scepter_of_Elias_acPGwIaUhx3R0mTq.json index 8c8e50af..f2c13441 100644 --- a/src/packs/items/weapons/weapon_Scepter_of_Elias_acPGwIaUhx3R0mTq.json +++ b/src/packs/items/weapons/weapon_Scepter_of_Elias_acPGwIaUhx3R0mTq.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 3, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Shortbow_p9tdjQr2AZP19RYm.json b/src/packs/items/weapons/weapon_Shortbow_p9tdjQr2AZP19RYm.json index 82216a2d..56a791ba 100644 --- a/src/packs/items/weapons/weapon_Shortbow_p9tdjQr2AZP19RYm.json +++ b/src/packs/items/weapons/weapon_Shortbow_p9tdjQr2AZP19RYm.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Shortstaff_vHDHG3STcxTEfYAM.json b/src/packs/items/weapons/weapon_Shortstaff_vHDHG3STcxTEfYAM.json index 0d468da8..3dc3a137 100644 --- a/src/packs/items/weapons/weapon_Shortstaff_vHDHG3STcxTEfYAM.json +++ b/src/packs/items/weapons/weapon_Shortstaff_vHDHG3STcxTEfYAM.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Shortsword_cjGZpXCoshEqi1FI.json b/src/packs/items/weapons/weapon_Shortsword_cjGZpXCoshEqi1FI.json index 84ae1a2a..0ea874f8 100644 --- a/src/packs/items/weapons/weapon_Shortsword_cjGZpXCoshEqi1FI.json +++ b/src/packs/items/weapons/weapon_Shortsword_cjGZpXCoshEqi1FI.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": null, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Siphoning_Gauntlets_1N1jggda5DfdzdMj.json b/src/packs/items/weapons/weapon_Siphoning_Gauntlets_1N1jggda5DfdzdMj.json index 0eb65689..205aa4e0 100644 --- a/src/packs/items/weapons/weapon_Siphoning_Gauntlets_1N1jggda5DfdzdMj.json +++ b/src/packs/items/weapons/weapon_Siphoning_Gauntlets_1N1jggda5DfdzdMj.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 9, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Sledge_Axe_OxsEmffWriiQmqJK.json b/src/packs/items/weapons/weapon_Sledge_Axe_OxsEmffWriiQmqJK.json index b5ffe457..d0230362 100644 --- a/src/packs/items/weapons/weapon_Sledge_Axe_OxsEmffWriiQmqJK.json +++ b/src/packs/items/weapons/weapon_Sledge_Axe_OxsEmffWriiQmqJK.json @@ -20,8 +20,8 @@ "amount": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "applyTo": "stress", "value": { "custom": { @@ -46,7 +46,7 @@ "base": false, "type": [] } - ], + }, "includeBase": false }, "_id": "0qVTvNMfapVST3sQ", @@ -104,8 +104,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 13, @@ -131,7 +131,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Small_Dagger_wKklDxs5nkzILNp4.json b/src/packs/items/weapons/weapon_Small_Dagger_wKklDxs5nkzILNp4.json index 5b63acd7..058d138e 100644 --- a/src/packs/items/weapons/weapon_Small_Dagger_wKklDxs5nkzILNp4.json +++ b/src/packs/items/weapons/weapon_Small_Dagger_wKklDxs5nkzILNp4.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "type": [ "physical" ], @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Spear_TF85tKJetUjLwh54.json b/src/packs/items/weapons/weapon_Spear_TF85tKJetUjLwh54.json index ea217ad9..a0ebc4ec 100644 --- a/src/packs/items/weapons/weapon_Spear_TF85tKJetUjLwh54.json +++ b/src/packs/items/weapons/weapon_Spear_TF85tKJetUjLwh54.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 3, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Spiked_Bow_O1w8KPYH85ZS8X64.json b/src/packs/items/weapons/weapon_Spiked_Bow_O1w8KPYH85ZS8X64.json index 99faed64..5421fc10 100644 --- a/src/packs/items/weapons/weapon_Spiked_Bow_O1w8KPYH85ZS8X64.json +++ b/src/packs/items/weapons/weapon_Spiked_Bow_O1w8KPYH85ZS8X64.json @@ -11,8 +11,8 @@ "type": "attack", "damage": { "includeBase": false, - "parts": [ - { + "parts": { + "hitPoints": { "resultBased": false, "value": { "custom": { @@ -36,7 +36,7 @@ } } } - ] + } }, "range": "melee", "roll": { @@ -113,8 +113,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 7, @@ -140,7 +140,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json b/src/packs/items/weapons/weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json index edadecf9..2dd08026 100644 --- a/src/packs/items/weapons/weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json +++ b/src/packs/items/weapons/weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 2, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Steelforged_Halberd_6bkbw4Ap644KZGvJ.json b/src/packs/items/weapons/weapon_Steelforged_Halberd_6bkbw4Ap644KZGvJ.json index c4730e94..1fccc471 100644 --- a/src/packs/items/weapons/weapon_Steelforged_Halberd_6bkbw4Ap644KZGvJ.json +++ b/src/packs/items/weapons/weapon_Steelforged_Halberd_6bkbw4Ap644KZGvJ.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 4, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Swinging_Ropeblade_1jOJHHKdtk3s2jaY.json b/src/packs/items/weapons/weapon_Swinging_Ropeblade_1jOJHHKdtk3s2jaY.json index b8fe04b6..cd9490f2 100644 --- a/src/packs/items/weapons/weapon_Swinging_Ropeblade_1jOJHHKdtk3s2jaY.json +++ b/src/packs/items/weapons/weapon_Swinging_Ropeblade_1jOJHHKdtk3s2jaY.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 9, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Sword_of_Light___Flame_TVPCWnSELOVBv6G1.json b/src/packs/items/weapons/weapon_Sword_of_Light___Flame_TVPCWnSELOVBv6G1.json index 7a74cf3d..63112b3e 100644 --- a/src/packs/items/weapons/weapon_Sword_of_Light___Flame_TVPCWnSELOVBv6G1.json +++ b/src/packs/items/weapons/weapon_Sword_of_Light___Flame_TVPCWnSELOVBv6G1.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 11, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Talon_Blades_jlLtgK468rO5IssR.json b/src/packs/items/weapons/weapon_Talon_Blades_jlLtgK468rO5IssR.json index 13542a63..a7c96705 100644 --- a/src/packs/items/weapons/weapon_Talon_Blades_jlLtgK468rO5IssR.json +++ b/src/packs/items/weapons/weapon_Talon_Blades_jlLtgK468rO5IssR.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 7, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Thistlebow_I1nDGpulg29GpWOW.json b/src/packs/items/weapons/weapon_Thistlebow_I1nDGpulg29GpWOW.json index 65fef12a..18f635eb 100644 --- a/src/packs/items/weapons/weapon_Thistlebow_I1nDGpulg29GpWOW.json +++ b/src/packs/items/weapons/weapon_Thistlebow_I1nDGpulg29GpWOW.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 13, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Tower_Shield_C9aWpK1shVMWP4m5.json b/src/packs/items/weapons/weapon_Tower_Shield_C9aWpK1shVMWP4m5.json index d49b7de7..47043c54 100644 --- a/src/packs/items/weapons/weapon_Tower_Shield_C9aWpK1shVMWP4m5.json +++ b/src/packs/items/weapons/weapon_Tower_Shield_C9aWpK1shVMWP4m5.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": null, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Urok_Broadsword_zGm6Wa1fGF6cECY5.json b/src/packs/items/weapons/weapon_Urok_Broadsword_zGm6Wa1fGF6cECY5.json index 708e920f..6727c333 100644 --- a/src/packs/items/weapons/weapon_Urok_Broadsword_zGm6Wa1fGF6cECY5.json +++ b/src/packs/items/weapons/weapon_Urok_Broadsword_zGm6Wa1fGF6cECY5.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 3, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Wand_ItWisJFNGMNWeaCV.json b/src/packs/items/weapons/weapon_Wand_ItWisJFNGMNWeaCV.json index 35fc7866..8084f261 100644 --- a/src/packs/items/weapons/weapon_Wand_ItWisJFNGMNWeaCV.json +++ b/src/packs/items/weapons/weapon_Wand_ItWisJFNGMNWeaCV.json @@ -41,8 +41,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 1, @@ -68,7 +68,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Wand_of_Enthrallment_tP6vmnrmTq2h5sj7.json b/src/packs/items/weapons/weapon_Wand_of_Enthrallment_tP6vmnrmTq2h5sj7.json index f2b2f308..84c7b3f2 100644 --- a/src/packs/items/weapons/weapon_Wand_of_Enthrallment_tP6vmnrmTq2h5sj7.json +++ b/src/packs/items/weapons/weapon_Wand_of_Enthrallment_tP6vmnrmTq2h5sj7.json @@ -85,8 +85,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -112,7 +112,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Wand_of_Essek_ZrRGNjGCgZTTfgDG.json b/src/packs/items/weapons/weapon_Wand_of_Essek_ZrRGNjGCgZTTfgDG.json index 23fb1003..d788d008 100644 --- a/src/packs/items/weapons/weapon_Wand_of_Essek_ZrRGNjGCgZTTfgDG.json +++ b/src/packs/items/weapons/weapon_Wand_of_Essek_ZrRGNjGCgZTTfgDG.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 13, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_War_Scythe_z6yEdFYQJ5IzgTX3.json b/src/packs/items/weapons/weapon_War_Scythe_z6yEdFYQJ5IzgTX3.json index e80ddfad..0c57dd50 100644 --- a/src/packs/items/weapons/weapon_War_Scythe_z6yEdFYQJ5IzgTX3.json +++ b/src/packs/items/weapons/weapon_War_Scythe_z6yEdFYQJ5IzgTX3.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d8", "bonus": 5, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Warhammer_ZXh1GQahBiODfSTC.json b/src/packs/items/weapons/weapon_Warhammer_ZXh1GQahBiODfSTC.json index 50b1a829..a94950c7 100644 --- a/src/packs/items/weapons/weapon_Warhammer_ZXh1GQahBiODfSTC.json +++ b/src/packs/items/weapons/weapon_Warhammer_ZXh1GQahBiODfSTC.json @@ -49,8 +49,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d12", "bonus": 3, @@ -76,7 +76,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Whip_CmtWqw6DwoePnX7W.json b/src/packs/items/weapons/weapon_Whip_CmtWqw6DwoePnX7W.json index 2e548ab6..287d8afe 100644 --- a/src/packs/items/weapons/weapon_Whip_CmtWqw6DwoePnX7W.json +++ b/src/packs/items/weapons/weapon_Whip_CmtWqw6DwoePnX7W.json @@ -78,8 +78,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": null, @@ -105,7 +105,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Widogast_Pendant_8Z5QrThfwkYPXNco.json b/src/packs/items/weapons/weapon_Widogast_Pendant_8Z5QrThfwkYPXNco.json index a03efe6b..44e4dfd6 100644 --- a/src/packs/items/weapons/weapon_Widogast_Pendant_8Z5QrThfwkYPXNco.json +++ b/src/packs/items/weapons/weapon_Widogast_Pendant_8Z5QrThfwkYPXNco.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d10", "bonus": 5, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/items/weapons/weapon_Yutari_Bloodbow_0XpSBYXxtywvBFQi.json b/src/packs/items/weapons/weapon_Yutari_Bloodbow_0XpSBYXxtywvBFQi.json index d07a986e..17e02976 100644 --- a/src/packs/items/weapons/weapon_Yutari_Bloodbow_0XpSBYXxtywvBFQi.json +++ b/src/packs/items/weapons/weapon_Yutari_Bloodbow_0XpSBYXxtywvBFQi.json @@ -71,8 +71,8 @@ "useDefault": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "dice": "d6", "bonus": 4, @@ -98,7 +98,7 @@ }, "base": false } - ], + }, "includeBase": false }, "description": "", diff --git a/src/packs/subclasses/feature_Battle_Ritual_qqb5acyUSl1sCpWW.json b/src/packs/subclasses/feature_Battle_Ritual_qqb5acyUSl1sCpWW.json index 6dee9f9d..2b012aee 100644 --- a/src/packs/subclasses/feature_Battle_Ritual_qqb5acyUSl1sCpWW.json +++ b/src/packs/subclasses/feature_Battle_Ritual_qqb5acyUSl1sCpWW.json @@ -23,8 +23,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -49,7 +49,7 @@ }, "type": [] }, - { + "hope": { "value": { "custom": { "enabled": true, @@ -74,7 +74,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Clarity_of_Nature_etaQ01yGJhBLDUqZ.json b/src/packs/subclasses/feature_Clarity_of_Nature_etaQ01yGJhBLDUqZ.json index 09d6f953..886f0b61 100644 --- a/src/packs/subclasses/feature_Clarity_of_Nature_etaQ01yGJhBLDUqZ.json +++ b/src/packs/subclasses/feature_Clarity_of_Nature_etaQ01yGJhBLDUqZ.json @@ -22,8 +22,8 @@ "recovery": "longRest" }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -48,7 +48,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Dark_Cloud_frBTtNMX9Y2gkuPz.json b/src/packs/subclasses/feature_Dark_Cloud_frBTtNMX9Y2gkuPz.json index 95534b9f..d0bd5c22 100644 --- a/src/packs/subclasses/feature_Dark_Cloud_frBTtNMX9Y2gkuPz.json +++ b/src/packs/subclasses/feature_Dark_Cloud_frBTtNMX9Y2gkuPz.json @@ -22,7 +22,7 @@ "recovery": null }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Elemental_Aura_2JH9NaOh69yN80Gw.json b/src/packs/subclasses/feature_Elemental_Aura_2JH9NaOh69yN80Gw.json index 7dbae2f6..7a3ebd91 100644 --- a/src/packs/subclasses/feature_Elemental_Aura_2JH9NaOh69yN80Gw.json +++ b/src/packs/subclasses/feature_Elemental_Aura_2JH9NaOh69yN80Gw.json @@ -22,8 +22,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -48,7 +48,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -134,7 +134,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Elemental_Incarnation_f37TTgCc0Q3Ih1A1.json b/src/packs/subclasses/feature_Elemental_Incarnation_f37TTgCc0Q3Ih1A1.json index 30ffb50b..45ffde60 100644 --- a/src/packs/subclasses/feature_Elemental_Incarnation_f37TTgCc0Q3Ih1A1.json +++ b/src/packs/subclasses/feature_Elemental_Incarnation_f37TTgCc0Q3Ih1A1.json @@ -29,8 +29,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -56,7 +56,7 @@ } } } - ], + }, "includeBase": false }, "target": { @@ -148,8 +148,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -174,7 +174,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Gifted_Performer_99U7YWNCxFZHCiT0.json b/src/packs/subclasses/feature_Gifted_Performer_99U7YWNCxFZHCiT0.json index 33d5af92..f65cd041 100644 --- a/src/packs/subclasses/feature_Gifted_Performer_99U7YWNCxFZHCiT0.json +++ b/src/packs/subclasses/feature_Gifted_Performer_99U7YWNCxFZHCiT0.json @@ -21,8 +21,8 @@ "recovery": "longRest" }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -47,7 +47,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -115,8 +115,8 @@ "recovery": "longRest" }, "damage": { - "parts": [ - { + "parts": { + "hope": { "value": { "custom": { "enabled": true, @@ -141,7 +141,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Honed_Expertise_w1BwNKxbQOSizLmZ.json b/src/packs/subclasses/feature_Honed_Expertise_w1BwNKxbQOSizLmZ.json index 1fad2396..48973ad9 100644 --- a/src/packs/subclasses/feature_Honed_Expertise_w1BwNKxbQOSizLmZ.json +++ b/src/packs/subclasses/feature_Honed_Expertise_w1BwNKxbQOSizLmZ.json @@ -23,7 +23,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Natural_Evasion_TnuLBtHQGbqyzn82.json b/src/packs/subclasses/feature_Natural_Evasion_TnuLBtHQGbqyzn82.json index 628fabed..517e1fe1 100644 --- a/src/packs/subclasses/feature_Natural_Evasion_TnuLBtHQGbqyzn82.json +++ b/src/packs/subclasses/feature_Natural_Evasion_TnuLBtHQGbqyzn82.json @@ -31,7 +31,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Regeneration_KRyrbSLVGreIOTZe.json b/src/packs/subclasses/feature_Regeneration_KRyrbSLVGreIOTZe.json index b7c39f2a..ab748169 100644 --- a/src/packs/subclasses/feature_Regeneration_KRyrbSLVGreIOTZe.json +++ b/src/packs/subclasses/feature_Regeneration_KRyrbSLVGreIOTZe.json @@ -29,8 +29,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": false @@ -54,7 +54,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Revenge_oNfA5F9cKwNR7joq.json b/src/packs/subclasses/feature_Revenge_oNfA5F9cKwNR7joq.json index f663201d..b7e2e8b9 100644 --- a/src/packs/subclasses/feature_Revenge_oNfA5F9cKwNR7joq.json +++ b/src/packs/subclasses/feature_Revenge_oNfA5F9cKwNR7joq.json @@ -29,8 +29,8 @@ "recovery": null }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -55,7 +55,7 @@ } } } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Rousing_Speech_PCmYTX02JLzBpgml.json b/src/packs/subclasses/feature_Rousing_Speech_PCmYTX02JLzBpgml.json index f1596519..ab7b4972 100644 --- a/src/packs/subclasses/feature_Rousing_Speech_PCmYTX02JLzBpgml.json +++ b/src/packs/subclasses/feature_Rousing_Speech_PCmYTX02JLzBpgml.json @@ -21,8 +21,8 @@ "recovery": "longRest" }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -47,7 +47,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Sparing_Touch_GfOSgVJW8bS1OjNq.json b/src/packs/subclasses/feature_Sparing_Touch_GfOSgVJW8bS1OjNq.json index deb4af6b..86de0fe8 100644 --- a/src/packs/subclasses/feature_Sparing_Touch_GfOSgVJW8bS1OjNq.json +++ b/src/packs/subclasses/feature_Sparing_Touch_GfOSgVJW8bS1OjNq.json @@ -39,8 +39,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -65,7 +65,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { @@ -116,8 +116,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "stress": { "value": { "custom": { "enabled": true, @@ -142,7 +142,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Thrive_in_Chaos_1nmFmkNXY6OYyyju.json b/src/packs/subclasses/feature_Thrive_in_Chaos_1nmFmkNXY6OYyyju.json index bbdd8707..b65184e9 100644 --- a/src/packs/subclasses/feature_Thrive_in_Chaos_1nmFmkNXY6OYyyju.json +++ b/src/packs/subclasses/feature_Thrive_in_Chaos_1nmFmkNXY6OYyyju.json @@ -31,7 +31,7 @@ "consumeOnSuccess": false }, "damage": { - "parts": [], + "parts": {}, "includeBase": false }, "target": { diff --git a/src/packs/subclasses/feature_Warden_s_Protection_2F1bUFY80oce97C9.json b/src/packs/subclasses/feature_Warden_s_Protection_2F1bUFY80oce97C9.json index e80a770a..0c6ad3e3 100644 --- a/src/packs/subclasses/feature_Warden_s_Protection_2F1bUFY80oce97C9.json +++ b/src/packs/subclasses/feature_Warden_s_Protection_2F1bUFY80oce97C9.json @@ -23,8 +23,8 @@ "consumeOnSuccess": false }, "damage": { - "parts": [ - { + "parts": { + "hitPoints": { "value": { "custom": { "enabled": true, @@ -49,7 +49,7 @@ }, "type": [] } - ], + }, "includeBase": false }, "target": { diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index f5e92e2c..53e9ba20 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -333,6 +333,15 @@ legend { font-weight: bold; color: light-dark(@dark-blue, @golden); + + &.with-icon { + display: flex; + align-items: center; + + i { + padding: 0 4px; + } + } } input[type='text'], diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 96bb361c..6f159a2d 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -1,12 +1,12 @@
      - + {{#if (eq @root.source.type 'healing')}} {{localize "DAGGERHEART.GENERAL.healing"}} {{else}} {{localize "DAGGERHEART.GENERAL.damage"}} {{/if}} - {{#unless (eq path 'system.attack.')}}{{/unless}} + {{#unless (eq path 'system.attack.')}}{{/unless}}
      {{#if @root.hasBaseDamage}} @@ -16,75 +16,72 @@ {{formField directField value=source.direct name=(concat path "damage.direct") localize=true classes="checkbox"}} {{/unless}}
      - {{#each source.parts as |dmg index|}} - {{#if (and @root.hasBaseDamage @root.source.damage.includeBase)}} - {{setVar 'realIndex' (add index -1)}} - {{else}} - {{setVar 'realIndex' index}} - {{/if}} + {{#each source.parts as |dmg key|}}
      + + {{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}} + {{#unless (or dmg.base ../path)}} + + {{/unless}} + + {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} - {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." realIndex ".resultBased") localize=true classes="checkbox"}} + {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." key ".resultBased") localize=true classes="checkbox"}} {{/if}} {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
      {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}} - {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex path=../path}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=key path=../path}}
      {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}} - {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" realIndex=realIndex path=../path}} + {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=key path=../path}}
      {{else}} - - {{localize "DAGGERHEART.GENERAL.formula"}} - {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex path=../path}} -
      + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=key path=../path}} {{/if}} -
      - {{formField ../fields.applyTo value=dmg.applyTo name=(concat ../path "damage.parts." realIndex ".applyTo") localize=true}} - {{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}} - {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." realIndex ".type") localize=true}} - {{/if}} -
      + + {{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}} + {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." key ".type") localize=true}} + {{/if}} + {{#if ../horde}}
      {{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}
      - - {{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." realIndex ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} - {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." realIndex ".valueAlt.dice") classes="inline-child" localize=true}} - {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." realIndex ".valueAlt.bonus") localize=true classes="inline-child"}} + + {{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." key ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} + {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." key ".valueAlt.dice") classes="inline-child" localize=true}} + {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." key ".valueAlt.bonus") localize=true classes="inline-child"}}
      {{/if}} - + - {{#unless (or dmg.base ../path)}}
      {{/unless}} {{/each}} {{#*inline "formula"}} {{#unless dmg.base}} - {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." realIndex "." target ".custom.enabled") classes="checkbox" localize=true}} + {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." key "." target ".custom.enabled") classes="checkbox" localize=true}} {{/unless}} {{#if source.custom.enabled}} - {{formField fields.custom.fields.formula value=source.custom.formula name=(concat path "damage.parts." realIndex "." target ".custom.formula") localize=true}} + {{formField fields.custom.fields.formula value=source.custom.formula name=(concat path "damage.parts." key "." target ".custom.formula") localize=true}} {{else}}
      {{#unless @root.isNPC}} - {{formField fields.multiplier value=source.multiplier name=(concat path "damage.parts." realIndex "." target ".multiplier") localize=true}} + {{formField fields.multiplier value=source.multiplier name=(concat path "damage.parts." key "." target ".multiplier") localize=true}} {{/unless}} - {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." realIndex "." target ".flatMultiplier") localize=true }}{{/if}} - {{formField fields.dice value=source.dice name=(concat path "damage.parts." realIndex "." target ".dice") localize=true}} - {{formField fields.bonus value=source.bonus name=(concat path "damage.parts." realIndex "." target ".bonus") localize=true}} + {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." key "." target ".flatMultiplier") localize=true }}{{/if}} + {{formField fields.dice value=source.dice name=(concat path "damage.parts." key "." target ".dice") localize=true}} + {{formField fields.bonus value=source.bonus name=(concat path "damage.parts." key "." target ".bonus") localize=true}}
      {{/if}} {{#if @root.isNPC}} - + {{/if}} {{/inline}} \ No newline at end of file diff --git a/templates/sheets/items/weapon/header.hbs b/templates/sheets/items/weapon/header.hbs index 349a9516..9bbd9511 100644 --- a/templates/sheets/items/weapon/header.hbs +++ b/templates/sheets/items/weapon/header.hbs @@ -14,13 +14,13 @@ - {{localize (concat 'DAGGERHEART.CONFIG.Range.' source.system.attack.range '.name')}} - - {{#if source.system.attack.damage.parts.0.value.custom.enabled}} + {{#if source.system.attack.damage.parts.hitPoints.value.custom.enabled}} {{localize "DAGGERHEART.GENERAL.custom"}} {{else}} - {{source.system.attack.damage.parts.0.value.dice}}{{#if source.system.attack.damage.parts.0.value.bonus}} + {{source.system.attack.damage.parts.0.value.bonus}}{{/if}} + {{source.system.attack.damage.parts.hitPoints.value.dice}}{{#if source.system.attack.damage.parts.hitPoints.value.bonus}} + {{source.system.attack.damage.parts.hitPoints.value.bonus}}{{/if}} {{/if}} ( - {{#each source.system.attack.damage.parts.0.type}} + {{#each source.system.attack.damage.parts.hitPoints.type}} {{localize (concat 'DAGGERHEART.CONFIG.DamageType.' this '.abbreviation')}} {{/each}} ) diff --git a/templates/sheets/items/weapon/settings.hbs b/templates/sheets/items/weapon/settings.hbs index f9499221..e67e8dd7 100644 --- a/templates/sheets/items/weapon/settings.hbs +++ b/templates/sheets/items/weapon/settings.hbs @@ -19,24 +19,24 @@
      {{#with systemFields.attack.fields.damage.fields.parts.element.fields as | fields | }} - {{#with (lookup ../document.system.attack.damage.parts 0) as | source | }} + {{#with ../document.system.attack.damage.parts.hitPoints as | source | }} {{localize "DAGGERHEART.GENERAL.damage"}} {{localize "DAGGERHEART.ACTIONS.Config.general.customFormula"}} - {{formInput fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="system.attack.damage.parts.0.value.custom.enabled"}} + {{formInput fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="system.attack.damage.parts.hitPoints.value.custom.enabled"}} {{#if source.value.custom.enabled}} {{localize "DAGGERHEART.ACTIONS.Config.general.formula"}} - {{formInput fields.value.fields.custom.fields.formula value=source.value.custom.formula name="system.attack.damage.parts.0.value.custom.formula"}} + {{formInput fields.value.fields.custom.fields.formula value=source.value.custom.formula name="system.attack.damage.parts.hitPoints.value.custom.formula"}} {{else}} {{localize "DAGGERHEART.GENERAL.Dice.single"}} - {{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.parts.0.value.dice"}} + {{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.parts.hitPoints.value.dice"}} {{localize "DAGGERHEART.GENERAL.bonus"}} - {{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.parts.0.value.bonus" localize=true}} + {{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.parts.hitPoints.value.bonus" localize=true}} {{/if}} {{localize "DAGGERHEART.GENERAL.type"}} - {{formInput fields.type value=source.type name="system.attack.damage.parts.0.type" localize=true}} + {{formInput fields.type value=source.type name="system.attack.damage.parts.hitPoints.type" localize=true}} {{localize "DAGGERHEART.CONFIG.DamageType.direct.name"}} {{formInput @root.systemFields.attack.fields.damage.fields.direct value=@root.document.system.attack.damage.direct name="system.attack.damage.direct" localize=true}} - + {{/with}} {{/with}}
      From fcadf119b720cd249517fd3a55a3145fe66ac365 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:30:03 +0100 Subject: [PATCH 021/304] [V14] Emanation AutoSizing (#1686) * Added so that emanation templates auto size their base to any token at the spot its placed * Correct option useage * Also snap emanations when done from scene controls --------- Co-authored-by: Carlos Fernandez --- module/canvas/placeables/regionLayer.mjs | 49 ++++++++- module/canvas/placeables/templateLayer.mjs | 116 --------------------- 2 files changed, 48 insertions(+), 117 deletions(-) delete mode 100644 module/canvas/placeables/templateLayer.mjs diff --git a/module/canvas/placeables/regionLayer.mjs b/module/canvas/placeables/regionLayer.mjs index d50845b7..75c192a0 100644 --- a/module/canvas/placeables/regionLayer.mjs +++ b/module/canvas/placeables/regionLayer.mjs @@ -43,6 +43,53 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer { const hole = ui.controls.controls[this.options.name].tools.hole?.active ?? false; if (game.activeTool === 'inFront') return { type: 'cone', x: 0, y: 0, radius: 0, angle: 180, hole }; - return super._createDragShapeData(event); + const shape = super._createDragShapeData(event); + const token = shape?.type === 'emanation' && shape.base?.type === 'token' ? this.#findTokenInBounds(event.interactionData.origin) : null; + if (token) { + shape.base.width = token.width; + shape.base.height = token.height; + event.interactionData.origin = token.getCenterPoint(); + } + return shape; + } + + async placeRegion(data, options = {}) { + const preConfirm = ({ _event, document, _create, _options }) => { + const shape = document.shapes[0]; + const isEmanation = shape.type === 'emanation'; + if (isEmanation) { + const token = this.#findTokenInBounds(shape.base.origin); + if (!token) return options.preConfirm?.() ?? true; + const shapeData = shape.toObject(); + document.updateSource({ + shapes: [ + { + ...shapeData, + base: { + ...shapeData.base, + height: token.height, + width: token.width, + x: token.x, + y: token.y + } + } + ] + }); + } + + return options?.preConfirm?.() ?? true; + }; + + super.placeRegion(data, { ...options, preConfirm }); + } + + /** Searches for token at origin point, returning null if there are no tokens or multiple overlapping tokens */ + #findTokenInBounds(origin) { + const { x, y } = origin; + const gridSize = canvas.grid.size; + const inBounds = canvas.scene.tokens.filter(t => { + return x.between(t.x, t.x + t.width * gridSize) && y.between(t.y, t.y + t.height * gridSize); + }); + return inBounds.length === 1 ? inBounds[0] : null; } } diff --git a/module/canvas/placeables/templateLayer.mjs b/module/canvas/placeables/templateLayer.mjs deleted file mode 100644 index 55bd7eb2..00000000 --- a/module/canvas/placeables/templateLayer.mjs +++ /dev/null @@ -1,116 +0,0 @@ -export default class DhTemplateLayer extends foundry.canvas.layers.TemplateLayer { - static prepareSceneControls() { - const sc = foundry.applications.ui.SceneControls; - return { - name: 'templates', - order: 2, - title: 'CONTROLS.GroupMeasure', - icon: 'fa-solid fa-ruler-combined', - visible: game.user.can('REGION_CREATE'), - onChange: (event, active) => { - if (active) canvas.templates.activate(); - }, - onToolChange: () => canvas.templates.setAllRenderFlags({ refreshState: true }), - tools: { - circle: { - name: 'circle', - order: 1, - title: 'CONTROLS.MeasureCircle', - icon: 'fa-regular fa-circle', - toolclip: { - src: 'toolclips/tools/measure-circle.webm', - heading: 'CONTROLS.MeasureCircle', - items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete']) - } - }, - cone: { - name: 'cone', - order: 2, - title: 'CONTROLS.MeasureCone', - icon: 'fa-solid fa-angle-left', - toolclip: { - src: 'toolclips/tools/measure-cone.webm', - heading: 'CONTROLS.MeasureCone', - items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate']) - } - }, - inFront: { - name: 'inFront', - order: 3, - title: 'CONTROLS.inFront', - icon: 'fa-solid fa-eye', - toolclip: { - src: 'toolclips/tools/measure-cone.webm', - heading: 'CONTROLS.inFront', - items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate']) - } - }, - rect: { - name: 'rect', - order: 4, - title: 'CONTROLS.MeasureRect', - icon: 'fa-regular fa-square', - toolclip: { - src: 'toolclips/tools/measure-rect.webm', - heading: 'CONTROLS.MeasureRect', - items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate']) - } - }, - ray: { - name: 'ray', - order: 5, - title: 'CONTROLS.MeasureRay', - icon: 'fa-solid fa-up-down', - toolclip: { - src: 'toolclips/tools/measure-ray.webm', - heading: 'CONTROLS.MeasureRay', - items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate']) - } - }, - clear: { - name: 'clear', - order: 6, - title: 'CONTROLS.MeasureClear', - icon: 'fa-solid fa-trash', - visible: game.user.isGM, - onChange: () => canvas.templates.deleteAll(), - button: true - } - }, - activeTool: 'circle' - }; - } - - _onDragLeftStart(event) { - const interaction = event.interactionData; - - // Snap the origin to the grid - if (!event.shiftKey) interaction.origin = this.getSnappedPoint(interaction.origin); - - // Create a pending MeasuredTemplateDocument - const tool = game.activeTool === 'inFront' ? 'cone' : game.activeTool; - const previewData = { - user: game.user.id, - t: tool, - x: interaction.origin.x, - y: interaction.origin.y, - sort: Math.max(this.getMaxSort() + 1, 0), - distance: 1, - direction: 0, - fillColor: game.user.color || '#FF0000', - hidden: event.altKey - }; - const defaults = CONFIG.MeasuredTemplate.defaults; - if (game.activeTool === 'cone') previewData.angle = defaults.angle; - else if (game.activeTool === 'inFront') previewData.angle = 180; - else if (game.activeTool === 'ray') previewData.width = defaults.width * canvas.dimensions.distance; - const cls = foundry.utils.getDocumentClass('MeasuredTemplate'); - const doc = new cls(previewData, { parent: canvas.scene }); - - // Create a preview MeasuredTemplate object - const template = new this.constructor.placeableClass(doc); - doc._object = template; - interaction.preview = this.preview.addChild(template); - template.draw(); - } -} From 5c8d16e1008b956b58a71dcce84faf6c36cfb7fd Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 12 Mar 2026 21:35:26 +0100 Subject: [PATCH 022/304] Raised version --- module/data/actor/environment.mjs | 2 +- system.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs index c4a3f747..0aaf8eb0 100644 --- a/module/data/actor/environment.mjs +++ b/module/data/actor/environment.mjs @@ -37,7 +37,7 @@ export default class DhEnvironment extends BaseDataActor { potentialAdversaries: new fields.TypedObjectField( new fields.SchemaField({ label: new fields.StringField(), - adversaries: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { required: false, initial: [] }) + adversaries: new ForeignDocumentUUIDArrayField({ type: 'Actor' }) }) ), notes: new fields.HTMLField() diff --git a/system.json b/system.json index 14d61851..41e46edb 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.0.0", "compatibility": { "minimum": "14.355", - "verified": "14.355", + "verified": "14.356", "maximum": "14" }, "authors": [ From ee3bbaec53f487401f84ba4e872b512c890ad946 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 12 Mar 2026 22:12:45 +0100 Subject: [PATCH 023/304] Fixed drag/drop --- .../sheets/api/application-mixin.mjs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 8c96e0bd..129914df 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -74,6 +74,15 @@ export default function DHApplicationMixin(Base) { class DHSheetV2 extends HandlebarsApplicationMixin(Base) { #nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard']; + /** + * @param {DHSheetV2Configuration} [options={}] + */ + constructor(options = {}) { + super(options); + + this._setupDragDrop(); + } + /** * The default options for the sheet. * @type {DHSheetV2Configuration} @@ -165,7 +174,9 @@ export default function DHApplicationMixin(Base) { /**@inheritdoc */ _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); - // this._dragDrop.forEach(d => d.bind(htmlElement)); + + /* Core dragDrop from ActorDocument is always only 1. Possible we could refactor our own */ + if (Array.isArray(this._dragDrop)) this._dragDrop.forEach(d => d.bind(htmlElement)); // Handle delta inputs for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) { @@ -338,6 +349,26 @@ export default function DHApplicationMixin(Base) { /* Drag and Drop */ /* -------------------------------------------- */ + /** + * Creates drag-drop handlers from the configured options. + * @returns {foundry.applications.ux.DragDrop[]} + * @private + */ + _setupDragDrop() { + if (this._dragDrop) { + this._dragDrop.callbacks.dragStart = this._onDragStart; + this._dragDrop.callback.drop = this._onDrop; + } else { + this._dragDrop = this.options.dragDrop.map(d => { + d.callbacks = { + dragstart: this._onDragStart.bind(this), + drop: this._onDrop.bind(this) + }; + return new foundry.applications.ux.DragDrop.implementation(d); + }); + } + } + /** * Handle dragStart event. * @param {DragEvent} event From 92bcaf4962c4d12b341e67395d525352d720c18c Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 13 Mar 2026 18:09:19 -0400 Subject: [PATCH 024/304] Fix clean type in v14 --- module/data/fields/actionField.mjs | 4 ++-- module/data/fields/actorField.mjs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index 624985fc..20e4d6f0 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -87,10 +87,10 @@ export class ActionField extends foundry.data.fields.ObjectField { /* -------------------------------------------- */ /** @override */ - _cleanType(value, options) { + _cleanType(value, options, _state) { if (!(typeof value === 'object')) value = {}; const cls = this.getModel(value); - if (cls) return cls.cleanData(value, options); + if (cls) return cls.cleanData(value, options, _state); return value; } diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs index 1399fb32..69ba6bf1 100644 --- a/module/data/fields/actorField.mjs +++ b/module/data/fields/actorField.mjs @@ -52,8 +52,8 @@ class ResourcesField extends fields.TypedObjectField { return key in CONFIG.DH.RESOURCE[this.actorType].all; } - _cleanType(value, options) { - value = super._cleanType(value, options); + _cleanType(value, options, _state) { + value = super._cleanType(value, options, _state); // If not partial, ensure all data exists if (!options.partial) { From 1160f5134797681add35500fad396f89a1effa97 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 13 Mar 2026 18:47:55 -0400 Subject: [PATCH 025/304] Fix hasDamage checks on non-attack actions --- module/data/action/baseAction.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index eea0d6c4..75fc3981 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -354,11 +354,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } get hasDamage() { - return !foundry.utils.isEmpty(this.damage.parts) && this.type !== 'healing'; + return !foundry.utils.isEmpty(this.damage?.parts) && this.type !== 'healing'; } get hasHealing() { - return !foundry.utils.isEmpty(this.damage.parts) && this.type === 'healing'; + return !foundry.utils.isEmpty(this.damage?.parts) && this.type === 'healing'; } get hasSave() { From 7c0ab25e103d7f762863276d97c32b5581b26944 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 14 Mar 2026 00:10:48 +0100 Subject: [PATCH 026/304] Fixed contextmenu --- module/applications/ux/contextMenu.mjs | 40 -------------------------- 1 file changed, 40 deletions(-) diff --git a/module/applications/ux/contextMenu.mjs b/module/applications/ux/contextMenu.mjs index 6e70da0c..9a308667 100644 --- a/module/applications/ux/contextMenu.mjs +++ b/module/applications/ux/contextMenu.mjs @@ -52,46 +52,6 @@ * @extends {foundry.applications.ux.ContextMenu} */ export default class DHContextMenu extends foundry.applications.ux.ContextMenu { - /** - * @param {HTMLElement|jQuery} container - The HTML element that contains the context menu targets. - * @param {string} selector - A CSS selector which activates the context menu. - * @param {ContextMenuEntry[]} menuItems - An Array of entries to display in the menu - * @param {ContextMenuOptions} [options] - Additional options to configure the context menu. - */ - constructor(container, selector, menuItems, options) { - super(container, selector, menuItems, options); - - /** @deprecated since v13 until v15 */ - this.#jQuery = options.jQuery; - } - - /** - * Whether to pass jQuery objects or HTMLElement instances to callback. - * @type {boolean} - */ - #jQuery; - - /**@inheritdoc */ - activateListeners(menu) { - menu.addEventListener('click', this.#onClickItem.bind(this)); - } - - /** - * Handle click events on context menu items. - * @param {PointerEvent} event The click event - */ - #onClickItem(event) { - event.preventDefault(); - event.stopPropagation(); - const element = event.target.closest('.context-item'); - if (!element) return; - const item = this.menuItems.find(i => i.element === element); - item?.onClick(event, this.#jQuery ? $(this.target) : this.target); - this.close(); - } - - /* -------------------------------------------- */ - /** * Trigger a context menu event in response to a normal click on a additional options button. * @param {PointerEvent} event From 37b088fe7d59c25cc2fbfe738c7cbd4a6c3bd92b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 13 Mar 2026 20:00:05 -0400 Subject: [PATCH 027/304] Apply low performance styling to all daggerheart sheets --- styles/less/global/sheet.less | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index 1e7bad0a..6f77a481 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -14,11 +14,7 @@ body.game:is(.performance-low, .noblur) { .themed.theme-dark .application.daggerheart.sheet.dh-style, .themed.theme-dark.application.daggerheart.sheet.dh-style, &.theme-dark .application.daggerheart { - &.adversary, - &.character, - &.item { - background: @dark-blue; - } + background: @dark-blue; } } From 3031531b14808ed7570738c7691004901abfdb1a Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:31:15 +0100 Subject: [PATCH 028/304] [V14] TagTeamRoll Rework (#1732) * Initial rolls working * Fixed reroll * more * More work * Added results section * . * Visual improvements * . * Removed traces of old TagTeamRoll * Added initiator handling * Added updating for other players * Fixed sync start * Completed finish method * Damage reroll * Fixed localization * Fixed crit damage * Fixes * Added visual of advantage and disadvantage dice --- daggerheart.mjs | 11 + lang/en.json | 36 +- module/applications/dialogs/d20RollDialog.mjs | 12 - .../dialogs/rerollDamageDialog.mjs | 12 +- module/applications/dialogs/tagTeamDialog.mjs | 837 ++++++++++++------ .../applications/sheets/actors/character.mjs | 28 +- module/applications/sheets/actors/party.mjs | 11 +- module/applications/ui/chatLog.mjs | 33 +- module/canvas/placeables/regionLayer.mjs | 5 +- module/config/generalConfig.mjs | 15 + module/config/hooksConfig.mjs | 3 +- module/config/settingsConfig.mjs | 1 - module/data/_module.mjs | 2 +- module/data/action/attackAction.mjs | 3 +- module/data/action/baseAction.mjs | 15 +- module/data/actor/base.mjs | 13 - module/data/actor/environment.mjs | 4 - module/data/actor/party.mjs | 21 +- module/data/fields/action/damageField.mjs | 2 +- module/data/fields/action/effectsField.mjs | 2 +- module/data/fields/action/saveField.mjs | 2 +- .../data/fields/iterableTypedObjectField.mjs | 8 +- module/data/tagTeamData.mjs | 47 + module/data/tagTeamRoll.mjs | 20 - module/dice/damageRoll.mjs | 31 +- module/dice/dhRoll.mjs | 9 +- module/dice/dualityRoll.mjs | 34 +- module/documents/actor.mjs | 25 + module/documents/chatMessage.mjs | 8 - module/helpers/utils.mjs | 3 +- module/systemRegistration/handlebars.mjs | 1 + module/systemRegistration/migrations.mjs | 4 +- module/systemRegistration/settings.mjs | 8 +- module/systemRegistration/socket.mjs | 6 +- ...ure_Strange_Patterns_6YsfFjmCGuFYVhT4.json | 2 +- .../less/dialog/dice-roll/roll-selection.less | 23 - styles/less/dialog/index.less | 2 + .../tag-team-dialog/initialization.less | 59 ++ styles/less/dialog/tag-team-dialog/sheet.less | 321 ++++--- styles/less/sheets/actors/party/sheet.less | 10 +- templates/dialogs/dice-roll/header.hbs | 6 - templates/dialogs/tagTeamDialog.hbs | 110 --- .../dialogs/tagTeamDialog/initialization.hbs | 34 + .../parts/tagTeamDamageParts.hbs | 25 + .../dialogs/tagTeamDialog/tagTeamRoll.hbs | 173 ++++ .../sheets/actors/party/party-members.hbs | 2 +- 46 files changed, 1301 insertions(+), 738 deletions(-) create mode 100644 module/data/tagTeamData.mjs delete mode 100644 module/data/tagTeamRoll.mjs create mode 100644 styles/less/dialog/tag-team-dialog/initialization.less delete mode 100644 templates/dialogs/tagTeamDialog.hbs create mode 100644 templates/dialogs/tagTeamDialog/initialization.hbs create mode 100644 templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs create mode 100644 templates/dialogs/tagTeamDialog/tagTeamRoll.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index 7c72e421..0518a4cf 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -402,6 +402,17 @@ Hooks.on('chatMessage', (_, message) => { } }); +Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => { + if (data.openForAllPlayers && data.partyId) { + const party = game.actors.get(data.partyId); + if (!party) return; + + const dialog = new game.system.api.applications.dialogs.TagTeamDialog(party); + dialog.tabGroups.application = 'tagTeamRoll'; + await dialog.render({ force: true }); + } +}); + const updateActorsRangeDependentEffects = async token => { const rangeMeasurement = game.settings.get( CONFIG.DH.id, diff --git a/lang/en.json b/lang/en.json index e1011124..dfb0f7b9 100755 --- a/lang/en.json +++ b/lang/en.json @@ -677,16 +677,35 @@ }, "TagTeamSelect": { "title": "Tag Team Roll", + "FIELDS": { + "initiator": { + "memberId": { "label": "Initiating Character" }, + "cost": { "label": "Initiation Cost" } + } + }, "leaderTitle": "Initiating Character", "membersTitle": "Participants", "partyTeam": "Party Team", "hopeCost": "Hope Cost", "initiatingCharacter": "Initiating Character", + "selectParticipants": "Select the two participants", + "startTagTeamRoll": "Start Tag Team Roll", + "openDialogForAll": "Open Dialog For All", + "rollType": "Roll Type", + "makeYourRoll": "Make your roll", + "cancelTagTeamRoll": "Cancel Tag Team Roll", + "finishTagTeamRoll": "Finish Tag Team Roll", "linkMessageHint": "Make a roll from your character sheet to link it to the Tag Team Roll", "damageNotRolled": "Damage not rolled in chat message yet", "insufficientHope": "The initiating character doesn't have enough hope", - "createTagTeam": "Create TagTeam Roll", - "chatMessageRollTitle": "Roll" + "createTagTeam": "Create Tag Team Roll", + "chatMessageRollTitle": "Roll", + "cancelConfirmTitle": "Cancel Tag Team Roll", + "cancelConfirmText": "Are you sure you want to cancel the Tag Team Roll? This will close it for all other players too.", + "hints": { + "completeRolls": "Set up and complete the rolls for the characters", + "selectRoll": "Select which roll value to be used for the Tag Team" + } }, "TokenConfig": { "actorSizeUsed": "Actor size is set, determining the dimensions" @@ -1230,6 +1249,11 @@ "selectType": "Select Action Type", "selectAction": "Action Selection" }, + "TagTeamRollTypes": { + "trait": "Trait", + "ability": "Ability", + "damageAbility": "Damage Ability" + }, "TargetTypes": { "any": "Any", "friendly": "Friendly", @@ -1882,6 +1906,10 @@ } }, "GENERAL": { + "Ability": { + "single": "Ability", + "plural": "Abilities" + }, "Action": { "single": "Action", "plural": "Actions" @@ -2335,6 +2363,10 @@ "rerolled": "Rerolled", "rerollThing": "Reroll {thing}", "resource": "Resource", + "result": { + "single": "Result", + "plural": "Results" + }, "roll": "Roll", "rollAll": "Roll All", "rollDamage": "Roll Damage", diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 8e79ba58..0fdb1896 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -35,7 +35,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio updateIsAdvantage: this.updateIsAdvantage, selectExperience: this.selectExperience, toggleReaction: this.toggleReaction, - toggleTagTeamRoll: this.toggleTagTeamRoll, toggleSelectedEffect: this.toggleSelectedEffect, submitRoll: this.submitRoll }, @@ -133,12 +132,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio context.reactionOverride = this.reactionOverride; } - const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); - if (this.actor && tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) { - context.activeTagTeamRoll = true; - context.tagTeamSelected = this.config.tagTeamSelected; - } - return context; } @@ -215,11 +208,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio } } - static toggleTagTeamRoll() { - this.config.tagTeamSelected = !this.config.tagTeamSelected; - this.render(); - } - static toggleSelectedEffect(_event, button) { this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected; this.render(); diff --git a/module/applications/dialogs/rerollDamageDialog.mjs b/module/applications/dialogs/rerollDamageDialog.mjs index e1b75eb7..b821bd24 100644 --- a/module/applications/dialogs/rerollDamageDialog.mjs +++ b/module/applications/dialogs/rerollDamageDialog.mjs @@ -1,5 +1,3 @@ -import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; - const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) { @@ -123,16 +121,8 @@ export default class RerollDamageDialog extends HandlebarsApplicationMixin(Appli return acc; }, {}) }; + await this.message.update(update); - - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { - refreshType: RefreshType.TagTeamRoll - } - }); - await this.close(); } diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index c28b773c..bc35f360 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -1,5 +1,7 @@ +import { MemberData } from '../../data/tagTeamData.mjs'; import { getCritDamageBonus } from '../../helpers/utils.mjs'; -import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; +import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; +import Party from '../sheets/actors/party.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -7,15 +9,23 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio constructor(party) { super(); - this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); this.party = party; + this.partyMembers = party.system.partyMembers + .filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) + .map(member => ({ + ...member.toObject(), + uuid: member.uuid, + id: member.id, + selected: false + })); + this.intiator = null; + this.openForAllPlayers = true; - this.setupHooks = Hooks.on(socketEvent.Refresh, ({ refreshType }) => { - if (refreshType === RefreshType.TagTeamRoll) { - this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); - this.render(); - } - }); + this.tabGroups.application = Object.keys(party.system.tagTeam.members).length + ? 'tagTeamRoll' + : 'initialization'; + + Hooks.on(socketEvent.Refresh, this.tagTeamRefresh.bind()); } get title() { @@ -24,324 +34,633 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio static DEFAULT_OPTIONS = { tag: 'form', + id: 'TagTeamDialog', classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'], position: { width: 550, height: 'auto' }, actions: { - removeMember: TagTeamDialog.#removeMember, - unlinkMessage: TagTeamDialog.#unlinkMessage, - selectMessage: TagTeamDialog.#selectMessage, - createTagTeam: TagTeamDialog.#createTagTeam + toggleSelectMember: TagTeamDialog.#toggleSelectMember, + startTagTeamRoll: TagTeamDialog.#startTagTeamRoll, + makeRoll: TagTeamDialog.#makeRoll, + removeRoll: TagTeamDialog.#removeRoll, + rerollDice: TagTeamDialog.#rerollDice, + makeDamageRoll: TagTeamDialog.#makeDamageRoll, + removeDamageRoll: TagTeamDialog.#removeDamageRoll, + rerollDamageDice: TagTeamDialog.#rerollDamageDice, + selectRoll: TagTeamDialog.#selectRoll, + cancelRoll: TagTeamDialog.#onCancelRoll, + finishRoll: TagTeamDialog.#finishRoll }, form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false } }; static PARTS = { - application: { - id: 'tag-team-dialog', - template: 'systems/daggerheart/templates/dialogs/tagTeamDialog.hbs' + initialization: { + id: 'initialization', + template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/initialization.hbs' + }, + tagTeamRoll: { + id: 'tagTeamRoll', + template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs' } }; + /** @inheritdoc */ + static TABS = { + application: { + tabs: [{ id: 'initialization' }, { id: 'tagTeamRoll' }] + } + }; + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + for (const element of htmlElement.querySelectorAll('.roll-type-select')) + element.addEventListener('change', this.updateRollType.bind(this)); + } + async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.hopeCost = this.hopeCost; - context.data = this.data; - - context.memberOptions = this.party.filter(c => !this.data.members[c.id]); - context.selectedCharacterOptions = this.party.filter(c => this.data.members[c.id]); - - context.members = Object.keys(this.data.members).map(id => { - const roll = this.data.members[id].messageId ? game.messages.get(this.data.members[id].messageId) : null; - - context.usesDamage = - context.usesDamage === undefined - ? roll?.system.hasDamage - : context.usesDamage && roll?.system.hasDamage; - return { - character: this.party.find(x => x.id === id), - selected: this.data.members[id].selected, - roll: roll, - damageValues: roll - ? Object.keys(roll.system.damage).map(key => ({ - key: key, - name: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label), - total: roll.system.damage[key].total - })) - : null - }; - }); - - const initiatorChar = this.party.find(x => x.id === this.data.initiator.id); - context.initiator = { - character: initiatorChar, - cost: this.data.initiator.cost - }; - - const selectedMember = Object.values(context.members).find(x => x.selected && x.roll); - const selectedIsCritical = selectedMember?.roll?.system?.isCritical; - context.selectedData = { - result: selectedMember - ? `${selectedMember.roll.system.roll.total} ${selectedMember.roll.system.roll.result.label}` - : null, - damageValues: null - }; - - for (const member of Object.values(context.members)) { - if (!member.roll) continue; - if (context.usesDamage) { - if (!context.selectedData.damageValues) context.selectedData.damageValues = {}; - for (let damage of member.damageValues) { - const damageTotal = member.roll.system.isCritical - ? damage.total - : selectedIsCritical - ? damage.total + (await getCritDamageBonus(member.roll.system.damage[damage.key].formula)) - : damage.total; - if (context.selectedData.damageValues[damage.key]) { - context.selectedData.damageValues[damage.key].total += damageTotal; - } else { - context.selectedData.damageValues[damage.key] = { - ...foundry.utils.deepClone(damage), - total: damageTotal - }; - } - } - } - } - - context.showResult = Object.values(context.members).reduce((enabled, member) => { - if (!member.roll) return enabled; - if (context.usesDamage) { - enabled = enabled === null ? member.damageValues.length > 0 : enabled && member.damageValues.length > 0; - } else { - enabled = enabled === null ? Boolean(member.roll) : enabled && Boolean(member.roll); - } - - return enabled; - }, null); - - context.createDisabled = - !context.selectedData.result || - !this.data.initiator.id || - Object.keys(this.data.members).length === 0 || - Object.values(context.members).some(x => - context.usesDamage ? !x.damageValues || x.damageValues.length === 0 : !x.roll - ); + context.isEditable = this.getIsEditable(); return context; } - async updateSource(update) { - await this.data.updateSource(update); + async _preparePartContext(partId, context, options) { + const partContext = await super._preparePartContext(partId, context, options); + switch (partId) { + case 'initialization': + partContext.tagTeamFields = this.party.system.schema.fields.tagTeam.fields; + partContext.memberSelection = this.partyMembers; + const selectedMembers = partContext.memberSelection.filter(x => x.selected); - if (game.user.isGM) { - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, this.data.toObject()); - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); - await game.socket.emit(`system.${CONFIG.DH.id}`, { + partContext.allSelected = selectedMembers.length === 2; + partContext.canStartTagTeam = partContext.allSelected && this.initiator; + partContext.initiator = this.initiator; + partContext.initiatorOptions = selectedMembers.map(x => ({ value: x.id, label: x.name })); + partContext.initiatorDisabled = !selectedMembers.length; + partContext.openForAllPlayers = this.openForAllPlayers; + + break; + case 'tagTeamRoll': + partContext.fields = this.party.system.schema.fields.tagTeam.fields; + partContext.data = this.party.system.tagTeam; + partContext.rollTypes = CONFIG.DH.GENERAL.tagTeamRollTypes; + partContext.traitOptions = CONFIG.DH.ACTOR.abilities; + + const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); + const critSelected = !selectedRoll + ? undefined + : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false); + + partContext.members = {}; + for (const actorId in this.party.system.tagTeam.members) { + const data = this.party.system.tagTeam.members[actorId]; + const actor = game.actors.get(actorId); + + const rollOptions = []; + const damageRollOptions = []; + for (const item of actor.items) { + if (item.system.metadata.hasActions) { + const actions = [ + ...item.system.actions, + ...(item.system.attack ? [item.system.attack] : []) + ]; + for (const action of actions) { + if (action.hasRoll) { + const actionItem = { + value: action.uuid, + label: action.name, + group: item.name, + baseAction: action.baseAction + }; + rollOptions.push(actionItem); + if (action.hasDamage) damageRollOptions.push(actionItem); + } + } + } + } + + const damage = data.rollData?.options?.damage; + partContext.hasDamage |= Boolean(damage); + const critHitPointsDamage = await this.getCriticalDamage(damage); + + partContext.members[actorId] = { + ...data, + isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER), + key: actorId, + readyToRoll: Boolean(data.rollChoice), + hasRolled: Boolean(data.rollData), + rollOptions, + damageRollOptions, + damage: damage, + critDamage: critHitPointsDamage, + useCritDamage: + critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical) + }; + } + + partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members); + partContext.joinedRoll = await this.getJoinedRoll({ + overrideIsCritical: critSelected, + displayVersion: true + }); + + break; + } + + return partContext; + } + + static async updateData(_event, _, formData) { + const { initiator, openForAllPlayers, ...partyData } = foundry.utils.expandObject(formData.object); + this.initiator = initiator; + this.openForAllPlayers = openForAllPlayers !== undefined ? openForAllPlayers : this.openForAllPlayers; + + this.updatePartyData(partyData); + } + + async updatePartyData(updata, options = { render: true }) { + await this.party.update(updata); + + if (options.render) { + this.render(true); + game.socket.emit(`system.${CONFIG.DH.id}`, { action: socketEvent.Refresh, - data: { - refreshType: RefreshType.TagTeamRoll - } - }); - } else { - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateSetting, - uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, - update: this.data.toObject(), - refresh: { refreshType: RefreshType.TagTeamRoll } - } + data: { refreshType: RefreshType.TagTeamRoll, action: 'refresh' } }); } } - static async updateData(_event, _element, formData) { - const { selectedAddMember, initiator } = foundry.utils.expandObject(formData.object); - const update = { initiator: initiator }; - if (selectedAddMember) { - const member = await foundry.utils.fromUuid(selectedAddMember); - update[`members.${member.id}`] = { messageId: null }; - } - - await this.updateSource(update); - this.render(); - } - - static async #removeMember(_, button) { - const update = { [`members.${button.dataset.characterId}`]: _del }; - if (this.data.initiator.id === button.dataset.characterId) { - update.iniator = { id: null }; - } - - await this.updateSource(update); - } - - static async #unlinkMessage(_, button) { - await this.updateSource({ [`members.${button.id}.messageId`]: null }); - } - - static async #selectMessage(_, button) { - const member = this.data.members[button.id]; - const currentSelected = Object.keys(this.data.members).find(key => this.data.members[key].selected); - const curretSelectedUpdate = - currentSelected && currentSelected !== button.id ? { [`${currentSelected}`]: { selected: false } } : {}; - await this.updateSource({ - members: { - [`${button.id}`]: { selected: !member.selected }, - ...curretSelectedUpdate - } + getIsEditable() { + return this.party.system.partyMembers.some(actor => { + const selected = Boolean(this.party.system.tagTeam.members[actor.id]); + return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); }); } - static async #createTagTeam() { - const mainRollId = Object.keys(this.data.members).find(key => this.data.members[key].selected); - const mainRoll = game.messages.get(this.data.members[mainRollId].messageId); + tagTeamRefresh = ({ refreshType, action }) => { + if (refreshType !== RefreshType.TagTeamRoll) return; - if (this.data.initiator.cost) { - const initiator = this.party.find(x => x.id === this.data.initiator.id); - if (initiator.system.resources.hope.value < this.data.initiator.cost) { + switch (action) { + case 'startTagTeamRoll': + this.tabGroups.application = 'tagTeamRoll'; + break; + case 'refresh': + this.render(); + break; + case 'close': + this.close(); + break; + } + }; + + async close(options = {}) { + /* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */ + if (options.closeKey) return; + + Hooks.off(socketEvent.Refresh, this.tagTeamRefresh); + return super.close(options); + } + + checkInitiatorHopeError(initiator) { + if (initiator.cost && initiator.memberId) { + const actor = game.actors.get(initiator.memberId); + if (actor.system.resources.hope.value < initiator.cost) { return ui.notifications.warn( game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.insufficientHope') ); } } + } - const secondaryRolls = Object.keys(this.data.members) - .filter(key => key !== mainRollId) - .map(key => game.messages.get(this.data.members[key].messageId)); + //#region Initialization + static #toggleSelectMember(_, button) { + const member = this.partyMembers.find(x => x.id === button.dataset.id); + if (member.selected && this.initiator?.memberId === member.id) this.initiator = null; - const systemData = foundry.utils.deepClone(mainRoll).system.toObject(); - const criticalRoll = systemData.roll.isCritical; - for (let roll of secondaryRolls) { - if (roll.system.hasDamage) { - for (let key in roll.system.damage) { - var damage = roll.system.damage[key]; - const damageTotal = - !roll.system.isCritical && criticalRoll - ? (await getCritDamageBonus(damage.formula)) + damage.total - : damage.total; - const updatedDamageParts = damage.parts; - if (systemData.damage[key]) { - if (!roll.system.isCritical && criticalRoll) { - for (let part of updatedDamageParts) { - const criticalDamage = await getCritDamageBonus(part.formula); - if (criticalDamage) { - damage.formula = `${damage.formula} + ${criticalDamage}`; - part.formula = `${part.formula} + ${criticalDamage}`; - part.modifierTotal = part.modifierTotal + criticalDamage; - part.total += criticalDamage; - part.roll = new Roll(part.formula); - } - } - } + member.selected = !member.selected; + this.render(); + } - systemData.damage[key].formula = `${systemData.damage[key].formula} + ${damage.formula}`; - systemData.damage[key].total += damageTotal; - systemData.damage[key].parts = [...systemData.damage[key].parts, ...updatedDamageParts]; - } else { - systemData.damage[key] = { ...damage, total: damageTotal, parts: updatedDamageParts }; - } + static async #startTagTeamRoll() { + const error = this.checkInitiatorHopeError(this.initiator); + if (error) return error; + + await this.party.update({ + 'system.tagTeam': _replace( + new game.system.api.data.TagTeamData({ + ...this.party.system.tagTeam.toObject(), + initiator: this.initiator, + members: this.partyMembers.reduce((acc, member) => { + if (member.selected) + acc[member.id] = { + name: member.name, + img: member.img, + rollType: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id + }; + return acc; + }, {}) + }) + ) + }); + + const hookData = { openForAllPlayers: this.openForAllPlayers, partyId: this.party.id }; + Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, hookData); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.TagTeamStart, + data: hookData + }); + + this.render(); + } + //#endregion + //#region Tag Team Roll + + async getInfoTexts(members) { + let rollsAreFinished = true; + let rollIsSelected = false; + for (const member of Object.values(members)) { + const rollFinished = Boolean(member.rollData); + const damageFinished = + member.rollData?.options?.hasDamage !== undefined ? member.rollData.options.damage : true; + + rollsAreFinished = rollsAreFinished && rollFinished && damageFinished; + rollIsSelected = rollIsSelected || member.selected; + } + + let hint = null; + if (!rollsAreFinished) hint = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.hints.completeRolls'); + else if (!rollIsSelected) hint = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.hints.selectRoll'); + + return hint; + } + + async updateRollType(event) { + this.updatePartyData({ + [`system.tagTeam.members.${event.target.dataset.member}`]: { + rollType: event.target.value, + rollChoice: null + } + }); + } + + static async #removeRoll(_, button) { + this.updatePartyData({ + [`system.tagTeam.members.${button.dataset.member}`]: { + rollData: null, + rollChoice: null, + selected: false + } + }); + } + + static async #makeRoll(event, button) { + const { member } = button.dataset; + + let result = null; + switch (this.party.system.tagTeam.members[member].rollType) { + case CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id: + result = await this.makeTraitRoll(member); + break; + case CONFIG.DH.GENERAL.tagTeamRollTypes.ability.id: + case CONFIG.DH.GENERAL.tagTeamRollTypes.damageAbility.id: + result = await this.makeAbilityRoll(event, member); + break; + } + + if (!result) return; + + if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); + + const rollData = result.messageRoll.toJSON(); + delete rollData.options.messageRoll; + this.updatePartyData({ + [`system.tagTeam.members.${member}.rollData`]: rollData + }); + } + + async makeTraitRoll(memberKey) { + const actor = game.actors.find(x => x.id === memberKey); + if (!actor) return; + + const memberData = this.party.system.tagTeam.members[memberKey]; + return await actor.rollTrait(memberData.rollChoice, { + skips: { + createMessage: true, + resources: true, + triggers: true + } + }); + } + + async makeAbilityRoll(event, memberKey) { + const actor = game.actors.find(x => x.id === memberKey); + if (!actor) return; + + const memberData = this.party.system.tagTeam.members[memberKey]; + const action = await foundry.utils.fromUuid(memberData.rollChoice); + + return await action.use(event, { + skips: { + createMessage: true, + resources: true, + triggers: true + } + }); + } + + static async #rerollDice(_, button) { + const { member, diceType } = button.dataset; + const memberData = this.party.system.tagTeam.members[member]; + + const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 2 : 4; + + const { parsedRoll, newRoll } = await game.system.api.dice.DualityRoll.reroll( + memberData.rollData, + dieIndex, + diceType + ); + const rollData = parsedRoll.toJSON(); + this.updatePartyData({ + [`system.tagTeam.members.${member}.rollData`]: { + ...rollData, + options: { + ...rollData.options, + roll: newRoll } } + }); + } + + static async #makeDamageRoll(event, button) { + const { memberKey } = button.dataset; + const actor = game.actors.find(x => x.id === memberKey); + if (!actor) return; + + const memberData = this.party.system.tagTeam.members[memberKey]; + const action = await foundry.utils.fromUuid(memberData.rollChoice); + const config = { + ...memberData.rollData.options, + dialog: { + configure: !event.shiftKey + }, + skips: { + createMessage: true, + resources: true, + triggers: true + } + }; + + await action.workflow.get('damage').execute(config, null, true); + if (!config.damage) return; + + // const damage = config.roll.isCritical ? await this.getNonCriticalDamage(config, actor) : config.damage; + + const current = this.party.system.tagTeam.members[memberKey].rollData; + await this.updatePartyData({ + [`system.tagTeam.members.${memberKey}.rollData`]: { + ...current, + options: { + ...current.options, + damage: config.damage + } + } + }); + } + + static async #removeDamageRoll(_, button) { + const { memberKey } = button.dataset; + const current = this.party.system.tagTeam.members[memberKey].rollData; + this.updatePartyData({ + [`system.tagTeam.members.${memberKey}.rollData`]: { + ...current, + options: { + ...current.options, + damage: null + } + } + }); + } + + static async #rerollDamageDice(_, button) { + const { memberKey, damageKey, part, dice } = button.dataset; + const memberData = this.party.system.tagTeam.members[memberKey]; + const partData = memberData.rollData.options.damage[damageKey].parts[part]; + const activeDiceResultKey = Object.keys(partData.dice[dice].results).find( + index => partData.dice[dice].results[index].active + ); + const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll( + partData, + dice, + activeDiceResultKey + ); + + const rollData = this.party.system.tagTeam.members[memberKey].rollData; + rollData.options.damage[damageKey].parts = rollData.options.damage[damageKey].parts.map((damagePart, index) => { + if (index !== Number.parseInt(part)) return damagePart; + + return { + ...damagePart, + total: parsedRoll.total, + dice: rerolledDice + }; + }); + rollData.options.damage[damageKey].total = rollData.options.damage[damageKey].parts.reduce((acc, part) => { + acc += part.total; + return acc; + }, 0); + + this.updatePartyData({ + [`system.tagTeam.members.${memberKey}.rollData`]: rollData + }); + } + + async getCriticalDamage(damage) { + const newDamage = foundry.utils.deepClone(damage); + for (let key in newDamage) { + var damage = newDamage[key]; + damage.formula = ''; + damage.total = 0; + + for (let part of damage.parts) { + const criticalDamage = await getCritDamageBonus(part.formula); + if (criticalDamage) { + part.modifierTotal += criticalDamage; + part.total += criticalDamage; + part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`; + part.roll = new Roll(part.formula); + } + + damage.formula = [damage.formula, part.formula].filter(x => x).join(' + '); + damage.total += part.total; + } } - systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle'); + return newDamage; + } + + async getNonCriticalDamage(config) { + const newDamage = foundry.utils.deepClone(config.damage); + for (let key in newDamage) { + var damage = newDamage[key]; + damage.formula = ''; + damage.total = 0; + + for (let part of damage.parts) { + const critDamageBonus = await getCritDamageBonus(part.formula); + part.modifierTotal -= critDamageBonus; + part.total -= critDamageBonus; + part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`; + part.roll = new Roll(part.formula); + + damage.formula = [damage.formula, part.formula].filter(x => x).join(' + '); + damage.total += part.total; + } + } + + return newDamage; + } + + static async #selectRoll(_, button) { + const { memberKey } = button.dataset; + this.updatePartyData({ + [`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce( + (acc, [key, member]) => { + acc[key] = { selected: key === memberKey ? !member.selected : false }; + return acc; + }, + {} + ) + }); + } + + async getJoinedRoll({ overrideIsCritical, displayVersion } = {}) { + const memberValues = Object.values(this.party.system.tagTeam.members); + const selectedRoll = memberValues.find(x => x.selected); + let baseMainRoll = selectedRoll ?? memberValues[0]; + let baseSecondaryRoll = selectedRoll + ? memberValues.find(x => !x.selected) + : memberValues.length > 1 + ? memberValues[1] + : null; + + if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null; + + const mainRoll = new MemberData(baseMainRoll.toObject()); + const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData; + const systemData = mainRoll.rollData.options; + const isCritical = overrideIsCritical ?? systemData.roll.isCritical; + if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage); + + if (secondaryRollData?.options.hasDamage) { + const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical) + ? await this.getCriticalDamage(secondaryRollData.options.damage) + : secondaryRollData.options.damage; + if (systemData.damage) { + for (const key in secondaryDamage) { + const damage = secondaryDamage[key]; + systemData.damage[key].formula = [systemData.damage[key].formula, damage.formula] + .filter(x => x) + .join(' + '); + systemData.damage[key].total += damage.total; + systemData.damage[key].parts.push(...damage.parts); + } + } else { + systemData.damage = secondaryDamage; + } + } + + return mainRoll; + } + + static async #onCancelRoll(_event, _button, options = { confirm: true }) { + this.cancelRoll(options); + } + + async cancelRoll(options = { confirm: true }) { + if (options.confirm) { + const confirmed = await foundry.applications.api.DialogV2.confirm({ + window: { + title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelConfirmTitle') + }, + content: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelConfirmText') + }); + + if (!confirmed) return; + } + + await this.updatePartyData( + { + 'system.tagTeam': { + initiator: null, + members: _replace({}) + } + }, + { render: false } + ); + + this.close(); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.TagTeamRoll, action: 'close' } + }); + } + + static async #finishRoll() { + const error = this.checkInitiatorHopeError(this.party.system.tagTeam.initiator); + if (error) return error; + + const mainRoll = (await this.getJoinedRoll()).rollData; + + const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor); + mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle'); const cls = getDocumentClass('ChatMessage'), msgData = { type: 'dualityRoll', user: game.user.id, title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'), - speaker: cls.getSpeaker({ actor: this.party.find(x => x.id === mainRollId) }), - system: systemData, - rolls: mainRoll.rolls, + speaker: cls.getSpeaker({ actor: mainActor }), + system: mainRoll.options, + rolls: [mainRoll], sound: null, flags: { core: { RollTable: true } } }; await cls.create(msgData); + /* Handle resource updates from the finished TagTeamRoll */ + const tagTeamData = this.party.system.tagTeam; const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; - for (let memberId of Object.keys(this.data.members)) { + for (let memberId in tagTeamData.members) { const resourceUpdates = []; - const rollGivesHope = systemData.roll.isCritical || systemData.roll.result.duality === 1; - if (memberId === this.data.initiator.id) { - const value = this.data.initiator.cost + const rollGivesHope = mainRoll.options.roll.isCritical || mainRoll.options.roll.result.duality === 1; + if (memberId === tagTeamData.initiator.memberId) { + const value = tagTeamData.initiator.cost ? rollGivesHope - ? 1 - this.data.initiator.cost - : -this.data.initiator.cost + ? 1 - tagTeamData.initiator.cost + : -tagTeamData.initiator.cost : 1; resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true }); } else if (rollGivesHope) { resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true }); } - if (systemData.roll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); - if (systemData.roll.result.duality === -1) { + if (mainRoll.options.roll.isCritical) + resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); + if (mainRoll.options.roll.result.duality === -1) { fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1; } - this.party.find(x => x.id === memberId).modifyResource(resourceUpdates); + game.actors.get(memberId).modifyResource(resourceUpdates); } if (fearUpdate.value) { - this.party.find(x => x.id === mainRollId).modifyResource([fearUpdate]); + mainActor.modifyResource([fearUpdate]); } - /* Improve by fetching default from schema */ - const update = { members: [], initiator: { id: null, cost: 3 } }; - if (game.user.isGM) { - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, update); - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { - refreshType: RefreshType.TagTeamRoll - } - }); - } else { - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateSetting, - uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, - update: update, - refresh: { refreshType: RefreshType.TagTeamRoll } - } - }); - } + /* Fin */ + this.cancelRoll({ confirm: false }); } - static async assignRoll(char, message) { - const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); - const character = settings.members[char.id]; - if (!character) return; - - await settings.updateSource({ [`members.${char.id}.messageId`]: message.id }); - - if (game.user.isGM) { - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, settings); - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { - refreshType: RefreshType.TagTeamRoll - } - }); - } else { - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateSetting, - uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, - update: settings, - refresh: { refreshType: RefreshType.TagTeamRoll } - } - }); - } - } - - async close(options = {}) { - Hooks.off(socketEvent.Refresh, this.setupHooks); - await super.close(options); - } + //#endregion } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 14dd1ae7..db236197 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -1,6 +1,5 @@ import DHBaseActorSheet from '../api/base-actor.mjs'; import DhDeathMove from '../../dialogs/deathMove.mjs'; -import { abilities } from '../../../config/actorConfig.mjs'; import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; @@ -720,35 +719,16 @@ export default class CharacterSheet extends DHBaseActorSheet { * Rolls an attribute check based on the clicked button's dataset attribute. * @type {ApplicationClickAction} */ - static async #rollAttribute(event, button) { - const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label); - const config = { - event: event, - title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`, - headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: abilityLabel - }), - effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document), - roll: { - trait: button.dataset.attribute, - type: 'trait' - }, - hasRoll: true, - actionType: 'action', - headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`, - title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: abilityLabel - }) - }; - const result = await this.document.diceRoll(config); + static async #rollAttribute(_event, button) { + const result = await this.document.rollTrait(button.dataset.attribute); if (!result) return; /* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */ const costResources = result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) || {}; - config.resourceUpdates.addResources(costResources); - await config.resourceUpdates.updateResources(); + result.resourceUpdates.addResources(costResources); + await result.resourceUpdates.updateResources(); } //TODO: redo toggleEquipItem method diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 1b1722db..45bdb679 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -35,9 +35,7 @@ export default class Party extends DHBaseActorSheet { refeshActions: Party.#refeshActions, triggerRest: Party.#triggerRest, tagTeamRoll: Party.#tagTeamRoll, - groupRoll: Party.#groupRoll, - selectRefreshable: DaggerheartMenu.selectRefreshable, - refreshActors: DaggerheartMenu.refreshActors + groupRoll: Party.#groupRoll }, dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }] }; @@ -120,6 +118,7 @@ export default class Party extends DHBaseActorSheet { secrets: this.document.isOwner, relativeTo: this.document }); + context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator); } /** @@ -255,11 +254,7 @@ export default class Party extends DHBaseActorSheet { } static async #tagTeamRoll() { - new game.system.api.applications.dialogs.TagTeamDialog( - this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) - ).render({ - force: true - }); + new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true }); } static async #groupRoll(_params) { diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 2b489f58..e29498e6 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -190,7 +190,24 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const target = event.target.closest('[data-die-index]'); if (target.dataset.type === 'damage') { - game.system.api.dice.DamageRoll.reroll(target, message); + const { damageType, part, dice, result } = target.dataset; + const damagePart = message.system.damage[damageType].parts[part]; + const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result); + const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => { + if (index !== Number(part)) return damagePart; + return { + ...damagePart, + total: parsedRoll.total, + dice: rerolledDice + }; + }); + const updateMessage = game.messages.get(message._id); + await updateMessage.update({ + [`system.damage.${damageType}`]: { + total: parsedRoll.total, + parts: damageParts + } + }); } else { let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0]; const rollClass = @@ -204,20 +221,16 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); - const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message); + const { newRoll, parsedRoll } = await rollClass.reroll( + originalRoll_parsed, + target.dataset.dieIndex, + target.dataset.type + ); await game.messages.get(message._id).update({ 'system.roll': newRoll, 'rolls': [parsedRoll] }); - - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { - refreshType: RefreshType.TagTeamRoll - } - }); } } diff --git a/module/canvas/placeables/regionLayer.mjs b/module/canvas/placeables/regionLayer.mjs index 75c192a0..81fd96db 100644 --- a/module/canvas/placeables/regionLayer.mjs +++ b/module/canvas/placeables/regionLayer.mjs @@ -44,7 +44,10 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer { if (game.activeTool === 'inFront') return { type: 'cone', x: 0, y: 0, radius: 0, angle: 180, hole }; const shape = super._createDragShapeData(event); - const token = shape?.type === 'emanation' && shape.base?.type === 'token' ? this.#findTokenInBounds(event.interactionData.origin) : null; + const token = + shape?.type === 'emanation' && shape.base?.type === 'token' + ? this.#findTokenInBounds(event.interactionData.origin) + : null; if (token) { shape.base.width = token.width; shape.base.height = token.height; diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 31e78f0b..2b633644 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -959,6 +959,21 @@ export const sceneRangeMeasurementSetting = { } }; +export const tagTeamRollTypes = { + trait: { + id: 'trait', + label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.trait' + }, + ability: { + id: 'ability', + label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.ability' + }, + damageAbility: { + id: 'damageAbility', + label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.damageAbility' + } +}; + export const activeEffectModes = { custom: { id: 'custom', diff --git a/module/config/hooksConfig.mjs b/module/config/hooksConfig.mjs index 61ba594c..8d04be6d 100644 --- a/module/config/hooksConfig.mjs +++ b/module/config/hooksConfig.mjs @@ -1,4 +1,5 @@ export const hooksConfig = { effectDisplayToggle: 'DHEffectDisplayToggle', - lockedTooltipDismissed: 'DHLockedTooltipDismissed' + lockedTooltipDismissed: 'DHLockedTooltipDismissed', + tagTeamStart: 'DHTagTeamRollStart' }; diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 0b28f0ab..52a316cf 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -34,7 +34,6 @@ export const gameSettings = { LevelTiers: 'LevelTiers', Countdowns: 'Countdowns', LastMigrationVersion: 'LastMigrationVersion', - TagTeamRoll: 'TagTeamRoll', SpotlightRequestQueue: 'SpotlightRequestQueue', CompendiumBrowserSettings: 'CompendiumBrowserSettings' }; diff --git a/module/data/_module.mjs b/module/data/_module.mjs index 52fa689e..43ff7807 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -1,9 +1,9 @@ export { default as DhCombat } from './combat.mjs'; export { default as DhCombatant } from './combatant.mjs'; -export { default as DhTagTeamRoll } from './tagTeamRoll.mjs'; export { default as DhRollTable } from './rollTable.mjs'; export { default as RegisteredTriggers } from './registeredTriggers.mjs'; export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs'; +export { default as TagTeamData } from './tagTeamData.mjs'; export * as countdowns from './countdowns.mjs'; export * as actions from './action/_module.mjs'; diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index 3671613d..a2d47309 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -50,9 +50,8 @@ export default class DHAttackAction extends DHDamageAction { async use(event, options) { const result = await super.use(event, options); - if (!result.message) return; - if (result.message.system.action.roll?.type === 'attack') { + if (result.message?.system.action.roll?.type === 'attack') { const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id); } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 75fc3981..0cc1f8fa 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -207,10 +207,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * @param {Event} event Event from the button used to trigger the Action * @returns {object} */ - async use(event) { + async use(event, configOptions = {}) { if (!this.actor) throw new Error("An Action can't be used outside of an Actor context."); - let config = this.prepareConfig(event); + let config = this.prepareConfig(event, configOptions); if (!config) return; config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item); @@ -231,7 +231,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return; - if (this.chatDisplay && !config.actionChatMessageHandled) await this.toChat(); + if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) await this.toChat(); return config; } @@ -241,7 +241,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * @param {Event} event Event from the button used to trigger the Action * @returns {object} */ - prepareBaseConfig(event) { + prepareBaseConfig(event, configOptions = {}) { const isActor = this.item instanceof CONFIG.Actor.documentClass; const actionTitle = game.i18n.localize(this.name); const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `; @@ -268,7 +268,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel data: this.getRollData(), evaluate: this.hasRoll, resourceUpdates: new ResourceUpdateMap(this.actor), - targetUuid: this.targetUuid + targetUuid: this.targetUuid, + ...configOptions }; DHBaseAction.applyKeybindings(config); @@ -280,8 +281,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * @param {Event} event Event from the button used to trigger the Action * @returns {object} */ - prepareConfig(event) { - const config = this.prepareBaseConfig(event); + prepareConfig(event, configOptions = {}) { + const config = this.prepareBaseConfig(event, configOptions); for (const clsField of Object.values(this.schema.fields)) { if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false; } diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 9f571edb..89ba5db2 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -189,19 +189,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { return true; } - async _preDelete() { - /* Clear all partyMembers from tagTeam setting.*/ - /* Revisit this when tagTeam is improved for many parties */ - if (this.parent.parties.size > 0) { - const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); - await tagTeam.updateSource({ - initiator: this.parent.id === tagTeam.initiator ? null : tagTeam.initiator, - members: Object.keys(tagTeam.members).find(x => x === this.parent.id) ? { [this.parent.id]: _del } : {} - }); - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam); - } - } - async _preUpdate(changes, options, userId) { const allowed = await super._preUpdate(changes, options, userId); if (allowed === false) return; diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs index 0aaf8eb0..e06f038c 100644 --- a/module/data/actor/environment.mjs +++ b/module/data/actor/environment.mjs @@ -75,10 +75,6 @@ export default class DhEnvironment extends BaseDataActor { ); scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => { Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene }); - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.TagTeamRoll } - }); }); } } diff --git a/module/data/actor/party.mjs b/module/data/actor/party.mjs index 3eddf235..2c797803 100644 --- a/module/data/actor/party.mjs +++ b/module/data/actor/party.mjs @@ -1,5 +1,6 @@ import BaseDataActor from './base.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; +import TagTeamData from '../tagTeamData.mjs'; export default class DhParty extends BaseDataActor { /**@inheritdoc */ @@ -14,7 +15,8 @@ export default class DhParty extends BaseDataActor { handfuls: new fields.NumberField({ initial: 1, integer: true }), bags: new fields.NumberField({ initial: 0, integer: true }), chests: new fields.NumberField({ initial: 0, integer: true }) - }) + }), + tagTeam: new fields.EmbeddedDataField(TagTeamData) }; } @@ -40,23 +42,6 @@ export default class DhParty extends BaseDataActor { } } - async _preDelete() { - /* Clear all partyMembers from tagTeam setting.*/ - /* Revisit this when tagTeam is improved for many parties */ - const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); - await tagTeam.updateSource({ - initiator: this.partyMembers.some(x => x.id === tagTeam.initiator) ? null : tagTeam.initiator, - members: Object.keys(tagTeam.members).reduce((acc, key) => { - if (this.partyMembers.find(x => x.id === key)) { - acc[key] = _del; - } - - return acc; - }, {}) - }); - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam); - } - _onDelete(options, userId) { super._onDelete(options, userId); diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index e79a91a2..b7ef852e 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -50,9 +50,9 @@ export default class DamageField extends fields.SchemaField { formulas = DamageField.formatFormulas.call(this, formulas, config); const damageConfig = { + dialog: {}, ...config, roll: formulas, - dialog: {}, data: this.getRollData() }; delete damageConfig.evaluate; diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index 6afd470b..1a003e2b 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -27,7 +27,7 @@ export default class EffectsField extends fields.ArrayField { static async execute(config, targets = null, force = false) { if (!config.hasEffect) return; let message = config.message ?? ui.chat.collection.get(config.parent?._id); - if (!message) { + if (!message && !config.skips.createMessage) { const roll = new CONFIG.Dice.daggerheart.DHRoll(''); roll._evaluated = true; message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); diff --git a/module/data/fields/action/saveField.mjs b/module/data/fields/action/saveField.mjs index c9030036..0629353e 100644 --- a/module/data/fields/action/saveField.mjs +++ b/module/data/fields/action/saveField.mjs @@ -38,7 +38,7 @@ export default class SaveField extends fields.SchemaField { if (!config.hasSave) return; let message = config.message ?? ui.chat.collection.get(config.parent?._id); - if (!message) { + if (!message && !config.skips.createMessage) { const roll = new CONFIG.Dice.daggerheart.DHRoll(''); roll._evaluated = true; message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); diff --git a/module/data/fields/iterableTypedObjectField.mjs b/module/data/fields/iterableTypedObjectField.mjs index d50d7dbf..d360b641 100644 --- a/module/data/fields/iterableTypedObjectField.mjs +++ b/module/data/fields/iterableTypedObjectField.mjs @@ -16,12 +16,12 @@ export default class IterableTypedObjectField extends foundry.data.fields.TypedO } } -/** - * The prototype of an iterable object. +/** + * The prototype of an iterable object. * This allows the functionality of a class but also allows foundry.utils.getType() to return "Object" instead of "Unknown". */ const IterableObjectPrototype = { - [Symbol.iterator]: function*() { + [Symbol.iterator]: function* () { for (const value of Object.values(this)) { yield value; } @@ -29,4 +29,4 @@ const IterableObjectPrototype = { map: function (func) { return Array.from(this, func); } -}; \ No newline at end of file +}; diff --git a/module/data/tagTeamData.mjs b/module/data/tagTeamData.mjs new file mode 100644 index 00000000..25158606 --- /dev/null +++ b/module/data/tagTeamData.mjs @@ -0,0 +1,47 @@ +export default class TagTeamData extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + initiator: new fields.SchemaField( + { + memberId: new fields.StringField({ + required: true, + label: 'DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.memberId.label' + }), + cost: new fields.NumberField({ + integer: true, + initial: 3, + label: 'DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.cost.label' + }) + }, + { nullable: true, initial: null } + ), + members: new fields.TypedObjectField(new fields.EmbeddedDataField(MemberData)) + }; + } +} + +export class MemberData extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + name: new fields.StringField({ required: true }), + img: new fields.StringField({ required: true }), + rollType: new fields.StringField({ + required: true, + choices: CONFIG.DH.GENERAL.tagTeamRollTypes, + initial: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id, + label: 'Roll Type' + }), + rollChoice: new fields.StringField({ nullable: true, initial: null }), + rollData: new fields.JSONField({ nullable: true, initial: null }), + selected: new fields.BooleanField({ initial: false }) + }; + } + + get roll() { + return this.rollData ? CONFIG.Dice.daggerheart.DualityRoll.fromData(this.rollData) : null; + } +} diff --git a/module/data/tagTeamRoll.mjs b/module/data/tagTeamRoll.mjs deleted file mode 100644 index de71a11b..00000000 --- a/module/data/tagTeamRoll.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { DhCharacter } from './actor/_module.mjs'; - -export default class DhTagTeamRoll extends foundry.abstract.DataModel { - static defineSchema() { - const fields = foundry.data.fields; - - return { - initiator: new fields.SchemaField({ - id: new fields.StringField({ nullable: true, initial: null }), - cost: new fields.NumberField({ integer: true, min: 0, initial: 3 }) - }), - members: new fields.TypedObjectField( - new fields.SchemaField({ - messageId: new fields.StringField({ required: true, nullable: true, initial: null }), - selected: new fields.BooleanField({ required: true, initial: false }) - }) - ) - }; - } -} diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index b322aae7..8cd3caac 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -1,6 +1,5 @@ import DamageDialog from '../applications/dialogs/damageDialog.mjs'; import { parseRallyDice } from '../helpers/utils.mjs'; -import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs'; import DHRoll from './dhRoll.mjs'; export default class DamageRoll extends DHRoll { @@ -281,10 +280,7 @@ export default class DamageRoll extends DHRoll { return mods; } - static async reroll(target, message) { - const { damageType, part, dice, result } = target.dataset; - const rollPart = message.system.damage[damageType].parts[part]; - + static async reroll(rollPart, dice, result) { let diceIndex = 0; let parsedRoll = game.system.api.dice.DamageRoll.fromData({ ...rollPart.roll, @@ -353,29 +349,6 @@ export default class DamageRoll extends DHRoll { }; }); - const updateMessage = game.messages.get(message._id); - const damageParts = updateMessage.system.damage[damageType].parts.map((damagePart, index) => { - if (index !== Number(part)) return damagePart; - return { - ...rollPart, - total: parsedRoll.total, - dice: rerolledDice - }; - }); - await updateMessage.update({ - [`system.damage.${damageType}`]: { - ...updateMessage, - total: parsedRoll.total, - parts: damageParts - } - }); - - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { - refreshType: RefreshType.TagTeamRoll - } - }); + return { parsedRoll, rerolledDice }; } } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index ec2ea0c7..b827b490 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -21,6 +21,9 @@ export default class DHRoll extends Roll { static async build(config = {}, message = {}) { const roll = await this.buildConfigure(config, message); if (!roll) return; + + if (config.skips?.createMessage) config.messageRoll = roll; + await this.buildEvaluate(roll, config, (message = {})); await this.buildPost(roll, config, (message = {})); return config; @@ -30,12 +33,6 @@ export default class DHRoll extends Roll { config.hooks = [...this.getHooks(), '']; config.dialog ??= {}; - const actorIdSplit = config.source?.actor?.split('.'); - if (actorIdSplit) { - const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); - config.tagTeamSelected = Boolean(tagTeamSettings.members[actorIdSplit[actorIdSplit.length - 1]]); - } - for (const hook of config.hooks) { if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null; } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index eb15fa5e..03035f68 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -374,9 +374,9 @@ export default class DualityRoll extends D20Roll { } } - static async reroll(rollString, target, message) { - let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false }); - const term = parsedRoll.terms[target.dataset.dieIndex]; + static async reroll(rollBase, dieIndex, diceType) { + let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollBase, evaluated: false }); + const term = parsedRoll.terms[dieIndex]; await term.reroll(`/r1=${term.total}`); const result = await parsedRoll.evaluate(); @@ -393,35 +393,35 @@ export default class DualityRoll extends D20Roll { options: { appearance: {} } }; - const diceSoNicePresets = await getDiceSoNicePresets(result, `d${term._faces}`, `d${term._faces}`); - const type = target.dataset.type; - if (diceSoNicePresets[type]) { - diceSoNiceRoll.dice[0].options = diceSoNicePresets[type]; + const diceSoNicePresets = await getDiceSoNicePresets(`d${term._faces}`, `d${term._faces}`); + if (diceSoNicePresets[diceType]) { + diceSoNiceRoll.dice[0].options = diceSoNicePresets[diceType]; } await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); + } else { + foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); } const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, { - targets: message.system.targets, + targets: parsedRoll.options.targets ?? [], roll: { - advantage: message.system.roll.advantage?.type, - difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null + advantage: parsedRoll.options.roll.advantage?.type, + difficulty: parsedRoll.options.roll.difficulty ? Number(parsedRoll.options.roll.difficulty) : null } }); const extraIndex = newRoll.advantage ? 3 : 2; newRoll.extra = newRoll.extra.slice(extraIndex); - const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); - - const actor = message.system.source.actor ? await foundry.utils.fromUuid(message.system.source.actor) : null; + const actor = parsedRoll.options.source.actor + ? await foundry.utils.fromUuid(parsedRoll.options.source.actor) + : null; const config = { - source: { actor: message.system.source.actor ?? '' }, - targets: message.system.targets, - tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id), + source: { actor: parsedRoll.options.source.actor ?? '' }, + targets: parsedRoll.targets, roll: newRoll, - rerolledRoll: message.system.roll, + rerolledRoll: parsedRoll.roll, resourceUpdates: new ResourceUpdateMap(actor) }; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index f0538bff..6c434007 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -4,6 +4,7 @@ import DHFeature from '../data/item/feature.mjs'; import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs'; import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs'; import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; +import { abilities } from '../config/actorConfig.mjs'; export default class DhpActor extends Actor { parties = new Set(); @@ -509,6 +510,30 @@ export default class DhpActor extends Actor { return await rollClass.build(config); } + async rollTrait(trait, options = {}) { + const abilityLabel = game.i18n.localize(abilities[trait].label); + const config = { + event: event, + title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.name}`, + headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { + ability: abilityLabel + }), + effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this), + roll: { + trait: trait, + type: 'trait' + }, + hasRoll: true, + actionType: 'action', + headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.name}`, + title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { + ability: abilityLabel + }), + ...options + }; + return await this.diceRoll(config); + } + get rollClass() { return CONFIG.Dice.daggerheart[['character', 'companion'].includes(this.type) ? 'DualityRoll' : 'D20Roll']; } diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 53921329..8b094678 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -177,14 +177,6 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(actor, item); await this.system.action.workflow.get('damage')?.execute(config, this._id, true); } - - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { - refreshType: RefreshType.TagTeamRoll - } - }); } async onApplyDamage(event) { diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index bb05fc39..59ebbbb3 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -528,7 +528,8 @@ export function expireActiveEffects(actor, allowedTypes = null) { export async function getCritDamageBonus(formula) { const critRoll = new Roll(formula); - return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0); + await critRoll.evaluate(); + return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.results.filter(r => r.active).length, 0); } export function htmlToText(html) { diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index f51e1035..4ad98b06 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -39,6 +39,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs', 'systems/daggerheart/templates/dialogs/downtime/activities.hbs', + 'systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs', 'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs', 'systems/daggerheart/templates/ui/chat/parts/roll-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/description-part.hbs', diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index 4216c38f..f8795386 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -193,7 +193,7 @@ export async function runMigrations() { } if (foundry.utils.isNewerVersion('1.2.7', lastMigrationVersion)) { - const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); + const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll'); const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator); const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => { if (!game.actors.some(actor => actor.id === id)) { @@ -206,7 +206,7 @@ export async function runMigrations() { initiator: initatorMissing ? null : tagTeam.initiator, members: missingMembers }); - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam); + await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam); lastMigrationVersion = '1.2.7'; } diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index e7ec37f5..658d2bd1 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -15,7 +15,7 @@ import { DhMetagamingSettings, DhVariantRuleSettings } from '../applications/settings/_module.mjs'; -import { CompendiumBrowserSettings, DhTagTeamRoll } from '../data/_module.mjs'; +import { CompendiumBrowserSettings } from '../data/_module.mjs'; export const registerDHSettings = () => { registerMenuSettings(); @@ -157,12 +157,6 @@ const registerNonConfigSettings = () => { type: DhCountdowns }); - game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, { - scope: 'world', - config: false, - type: DhTagTeamRoll - }); - game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings, { scope: 'world', config: false, diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index 173ef02b..fb152959 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -15,6 +15,9 @@ export function handleSocketEvent({ action = null, data = {} } = {}) { case socketEvent.DowntimeTrigger: Party.downtimeMoveQuery(data); break; + case socketEvent.TagTeamStart: + Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data); + break; } } @@ -22,7 +25,8 @@ export const socketEvent = { GMUpdate: 'DhGMUpdate', Refresh: 'DhRefresh', DhpFearUpdate: 'DhFearUpdate', - DowntimeTrigger: 'DowntimeTrigger' + DowntimeTrigger: 'DowntimeTrigger', + TagTeamStart: 'DhTagTeamStart' }; export const GMUpdateEvent = { diff --git a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json index ce1f499f..6939ff7f 100644 --- a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json +++ b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json @@ -85,7 +85,7 @@ { "trigger": "dualityRoll", "triggeringActorType": "self", - "command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n
      ${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}
      \n
      ${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}
      \n
      ${game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsActionExplanation')}
      \n
      \n \n \n
      \n
      `;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;" + "command": "/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n
      ${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}
      \n
      ${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}
      \n
      ${game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsActionExplanation')}
      \n
      \n \n \n
      \n
      `;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;" } ] } diff --git a/styles/less/dialog/dice-roll/roll-selection.less b/styles/less/dialog/dice-roll/roll-selection.less index 7fdae77a..a1a01e6b 100644 --- a/styles/less/dialog/dice-roll/roll-selection.less +++ b/styles/less/dialog/dice-roll/roll-selection.less @@ -69,29 +69,6 @@ background: light-dark(@dark-blue-40, @golden-40); } } - - .tag-team-controller { - display: flex; - align-items: center; - border-radius: 5px; - width: fit-content; - gap: 5px; - cursor: pointer; - padding: 5px; - background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); - - .label { - font-style: normal; - font-weight: 400; - font-size: var(--font-size-14); - line-height: 17px; - } - - &.selected { - background: light-dark(@dark-blue-40, @golden-40); - } - } } .roll-dialog-container { diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index 0c70df9f..73738eaa 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -32,6 +32,8 @@ @import './reroll-dialog/sheet.less'; @import './group-roll/group-roll.less'; + +@import './tag-team-dialog/initialization.less'; @import './tag-team-dialog/sheet.less'; @import './image-select/sheet.less'; diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less new file mode 100644 index 00000000..30676f82 --- /dev/null +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -0,0 +1,59 @@ +.daggerheart.dialog.dh-style.views.tag-team-dialog { + .initialization-container { + h2 { + text-align: center; + } + + .members-container { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 8px; + + .member-container { + position: relative; + display: flex; + justify-content: center; + + &.inactive { + opacity: 0.4; + } + + .member-name { + position: absolute; + } + } + } + + .initiator-container { + margin-top: 8px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + + &.inactive { + opacity: 0.4; + } + } + + footer { + margin-top: 8px; + display: flex; + gap: 8px; + + button { + flex: 1; + } + + .finish-tools { + flex: none; + display: flex; + align-items: center; + gap: 4px; + + &.inactive { + opacity: 0.4; + } + } + } + } +} diff --git a/styles/less/dialog/tag-team-dialog/sheet.less b/styles/less/dialog/tag-team-dialog/sheet.less index 767c66ca..e72b4956 100644 --- a/styles/less/dialog/tag-team-dialog/sheet.less +++ b/styles/less/dialog/tag-team-dialog/sheet.less @@ -1,178 +1,251 @@ .daggerheart.dialog.dh-style.views.tag-team-dialog { - .tag-team-container { + .tag-team-roll-container { display: flex; flex-direction: column; gap: 16px; - .tag-team-data-container { - display: flex; - align-items: center; - gap: 8px; - - .form-group { - flex: 0; - - label { - white-space: nowrap; - } - - &.flex-group { - flex: 1; - } - } + &.inactive { + opacity: 0.4; + pointer-events: none; } - .title-row { + .team-container { display: flex; - align-items: center; - gap: 8px; + gap: 16px; - h2 { - text-align: start; - } - - select { - flex: 1; - } - } - - .participants-container { - margin-top: 8px; - display: flex; - flex-direction: column; - gap: 4px; - - .participant-outer-container { - padding: 8px; + .member-container { display: flex; flex-direction: column; - gap: 4px; - cursor: pointer; - border-radius: 6px; + justify-content: space-between; + gap: 8px; + flex: 1; - &.selected, - &:hover { - background-color: light-dark(@golden-40, @golden-40); + &.inactive { + opacity: 0.4; + pointer-events: none; } - .participant-container { + .data-container { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + } + + .member-info { display: flex; align-items: center; justify-content: space-between; - gap: 8px; + width: 100%; - .participant-inner-container { + img { + height: 64px; + border-radius: 6px; + border: 1px solid light-dark(@dark-blue, @golden); + } + + .member-name { flex: 1; - display: flex; - align-items: center; - gap: 4px; - - img { - height: 48px; - width: 48px; - border-radius: 50%; - } - - .participant-labels { - display: flex; - flex-direction: column; - gap: 2px; - - .participant-label-title { - font-size: 18px; - } - - .participant-label-info { - display: flex; - gap: 4px; - - .participant-label-info-part { - border: 1px solid light-dark(white, white); - border-radius: 4px; - padding: 2px 4px; - background-color: light-dark(@beige-80, @soft-white-shadow); - color: white; - } - } - } + text-align: center; + font-size: var(--font-size-18); } } - .participant-empty-roll-container { - border: 1px dashed white; - padding: 8px 2px; - text-align: center; - font-style: italic; + .roll-setup { + width: 100%; } - .participant-roll-outer-container { + .roll-container { display: flex; flex-direction: column; - gap: 2px; - color: light-dark(@dark-blue, @golden); + } - h4 { - text-align: center; + .roll-title { + font-size: var(--font-size-20); + font-weight: bold; + color: light-dark(@dark-blue, @golden); + text-align: center; + display: flex; + align-items: center; + gap: 8px; + + &::before, + &::after { color: light-dark(@dark-blue, @golden); + content: ''; + flex: 1; + height: 2px; } - .participant-roll-container { + &::before { + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, light-dark(@dark-blue, @golden) 100%); + } + + &::after { + background: linear-gradient(90deg, light-dark(@dark-blue, @golden) 0%, rgba(0, 0, 0, 0) 100%); + } + } + + .roll-tools { + display: flex; + gap: 4px; + align-items: center; + + img { + height: 16px; + } + + a { + display: flex; + font-size: 16px; + + &:hover { + text-shadow: none; + filter: drop-shadow(0 0 8px var(--golden)); + } + } + } + + .roll-data { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + + &.hope { + --text-color: @golden; + --bg-color: @golden-40; + } + + &.fear { + --text-color: @chat-blue; + --bg-color: @chat-blue-40; + } + + &.critical { + --text-color: @chat-purple; + --bg-color: @chat-purple-40; + } + + .duality-label { + color: var(--text-color); + font-size: var(--font-size-20); + font-weight: bold; + text-align: center; + + .unused-damage { + text-decoration: line-through; + } + } + + .roll-dice-container { display: flex; align-items: center; justify-content: center; - white-space: nowrap; - - .participant-roll-text-container { - padding: 0 8px; - white-space: nowrap; - display: flex; - } - } - - .damage-values-container { - display: flex; - justify-content: space-around; + flex-wrap: wrap; gap: 8px; - .damage-container { - border: 1px solid light-dark(white, white); - border-radius: 6px; - padding: 0 4px; + .roll-dice { + position: relative; display: flex; - gap: 4px; + align-items: center; + justify-content: center; + + .dice-label { + position: absolute; + color: white; + font-size: 1rem; + paint-order: stroke fill; + -webkit-text-stroke: 2px black; + } + + img { + height: 32px; + } + } + + .roll-operator { + font-size: var(--font-size-24); + } + + .roll-value { + font-size: 18px; + } + } + + .roll-total { + background: var(--bg-color); + color: var(--text-color); + border-radius: 4px; + padding: 3px; + } + } + + .select-roll-button { + margin-top: 8px; + + i { + color: light-dark(@dark-blue, @golden); + font-size: 48px; + + &.inactive { + opacity: 0.4; } } } } } - h2 { - text-align: center; - } - - .result-container { - display: grid; - grid-template-columns: 1fr 1fr; + .results-container { + display: flex; + flex-direction: column; align-items: center; - gap: 8px; + gap: 4px; + border: 2px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + padding: 8px 10px; - .result-damages-container { + .result-container-label { + font-size: var(--font-size-24); + font-weight: bold; + } + + .results-inner-container { display: flex; - flex-wrap: wrap; - gap: 4px; + justify-content: space-between; + gap: 8px; + width: 100%; - .result-damage-container { - border: 1px solid light-dark(white, white); - border-radius: 6px; - padding: 0 4px; + .result-section-label { + font-size: var(--font-size-20); + } + + .result-container { + width: 100%; + text-align: center; + + .result-info { + display: flex; + gap: 4px; + align-items: center; + justify-content: center; + } } } } - .roll-leader-container { + .finish-container { + gap: 16px; display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; + grid-template-columns: 1fr 1fr 1fr; + + .finish-button { + grid-column: span 2; + } + } + + .hint { + text-align: center; } } } diff --git a/styles/less/sheets/actors/party/sheet.less b/styles/less/sheets/actors/party/sheet.less index 2d1344e8..6b51de53 100644 --- a/styles/less/sheets/actors/party/sheet.less +++ b/styles/less/sheets/actors/party/sheet.less @@ -7,8 +7,12 @@ background-image: url('../assets/parchments/dh-parchment-dark.png'); } }, { - &.party { + &.sheet.actor.dh-style.party { background: url('../assets/parchments/dh-parchment-light.png'); + + .tab .actions-section .active-action { + animation: glow-dark 0.75s infinite alternate; + } } }); @@ -40,6 +44,10 @@ font-size: 12px; } } + + .active-action { + animation: glow 0.75s infinite alternate; + } } } } diff --git a/templates/dialogs/dice-roll/header.hbs b/templates/dialogs/dice-roll/header.hbs index 0cb58a01..486fcc8f 100644 --- a/templates/dialogs/dice-roll/header.hbs +++ b/templates/dialogs/dice-roll/header.hbs @@ -8,10 +8,4 @@ {{/if}} - {{#if (and @root.hasRoll @root.activeTagTeamRoll)}} -
      - - {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.title"}} -
      - {{/if}} \ No newline at end of file diff --git a/templates/dialogs/tagTeamDialog.hbs b/templates/dialogs/tagTeamDialog.hbs deleted file mode 100644 index 3c96a573..00000000 --- a/templates/dialogs/tagTeamDialog.hbs +++ /dev/null @@ -1,110 +0,0 @@ -
      -
      -
      - {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.partyTeam"}} - -
      - - -
      - -
      - -
      - {{#each members as |member|}} -
      -
      -
      - -
      -
      {{member.character.name}}
      -
      - {{#if member.character.system.class.value}} -
      {{member.character.system.class.value.name}}
      - {{/if}} - {{#if member.system.multiclass.value}} -
      {{member.character.system.multiclass.value.name}}
      - {{/if}} -
      -
      -
      - -
      - {{#if member.roll}} -
      -
      -

      - - {{member.roll.system.title}} -

      -
      -
      - -
      - {{member.roll.system.roll.total}} - {{localize "DAGGERHEART.GENERAL.withThing" thing=member.roll.system.roll.result.label}} -
      - -
      - {{#if member.roll.system.hasDamage}} -

      {{localize "DAGGERHEART.GENERAL.damage"}}

      -
      - {{#if member.damageValues}} - {{#each member.damageValues as |damage|}} -
      -
      {{damage.name}}
      -
      {{damage.total}}
      -
      - {{/each}} - {{else}} - {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.damageNotRolled"}} - {{/if}} -
      - {{/if}} -
      - {{else}} -
      - {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.linkMessageHint"}} -
      - {{/if}} -
      - {{/each}} -
      -
      - -
      -

      - {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.initiatingCharacter"}} - -

      -

      - {{localize "DAGGERHEART.GENERAL.Cost.single"}} - -

      -
      - {{#if showResult}} - {{#if selectedData.result}} -
      -

      {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.title"}}: {{selectedData.result}}

      - {{#if usesDamage}} -
      - - {{#each selectedData.damageValues as |damage|}} -
      - {{damage.name}} - {{damage.total}} -
      - {{/each}} -
      - {{/if}} -
      - {{/if}} - {{/if}} - - -
      -
      \ No newline at end of file diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs new file mode 100644 index 00000000..60b11c7e --- /dev/null +++ b/templates/dialogs/tagTeamDialog/initialization.hbs @@ -0,0 +1,34 @@ +
      +

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

      +
      + {{#each memberSelection as |member|}} + + {{member.name}} + + + {{/each}} +
      + +
      +
      + +
      + +
      +
      + {{formGroup tagTeamFields.initiator.fields.cost name="initiator.cost" value=initiator.cost disabled=initiatorDisabled localize=true }} +
      + +
      + +
      + {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.openDialogForAll"}} + +
      +
      +
      \ No newline at end of file diff --git a/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs b/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs new file mode 100644 index 00000000..058777a5 --- /dev/null +++ b/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs @@ -0,0 +1,25 @@ +{{#each damage as |damage key|}} +
      +
      + {{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}: + {{damage.total}} +
      + {{#each damage.parts as |part|}} +
      + {{#each part.dice as |dice index|}} + + {{dice.total}} + + + {{#unless @last}} + + + {{/unless}} + {{/each}} + {{#if part.modifierTotal}} + {{#if (gte part.modifierTotal 0)}}+{{else}}-{{/if}} + {{positive part.modifierTotal}} + {{/if}} +
      + {{/each}} +
      +{{/each}} \ No newline at end of file diff --git a/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs b/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs new file mode 100644 index 00000000..32464000 --- /dev/null +++ b/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs @@ -0,0 +1,173 @@ +
      +
      +
      + {{#each members as |member key|}} +
      +
      +
      + + {{member.name}} +
      +
      +
      +
      + + +
      +
      + + {{#if (eq member.rollType 'trait')}} +
      +
      + + +
      +
      + {{else if (eq member.rollType 'abilityDamage')}} +
      +
      + + +
      +
      + {{else}} +
      +
      + + +
      +
      + {{/if}} +
      + + {{#if member.readyToRoll}} +
      + + {{localize "DAGGERHEART.GENERAL.roll"}} +
      + + + + + {{#if member.hasRolled}} + + + + {{/if}} +
      +
      + + {{#if member.rollData}} + {{#with member.rollData.options.roll}} +
      +
      {{this.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=this.result.label}}
      +
      + + {{this.hope.value}} + + + + + + {{this.fear.value}} + + + {{#if this.advantage}} + {{#if (eq this.advantage.type 1)}}+{{else}}-{{/if}} + + {{this.advantage.value}} + + + {{/if}} + {{#if (gte this.modifierTotal 0)}}+{{else}}-{{/if}} + {{positive this.modifierTotal}} +
      +
      + {{/with}} + {{else}} + {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}} + {{/if}} +
      + {{/if}} + + {{#if member.rollData.options.hasDamage}} +
      + + {{localize "DAGGERHEART.GENERAL.damage"}} +
      + + + + + {{#if damage}} + + + + {{/if}} +
      +
      + {{#if damage}} + {{#if useCritDamage}} + {{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=critDamage isCritical=true }} + {{else}} + {{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=damage }} + {{/if}} + {{else}} + {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}} + {{/if}} +
      + {{/if}} +
      + + {{#if member.hasRolled}} + + + + {{/if}} +
      + {{/each}} +
      + +
      + {{localize "DAGGERHEART.GENERAL.result.plural"}} +
      + {{#if hintText}} +
      {{localize hintText}}
      + {{else}} + {{#if joinedRoll.rollData}} +
      + +
      +
      {{joinedRoll.rollData.options.roll.total}}
      +
      {{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.rollData.options.roll.result.label}}
      +
      +
      + {{/if}} + {{#if hasDamage}} +
      + + {{#each joinedRoll.rollData.options.damage as |damage key|}} +
      +
      {{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}
      +
      {{damage.total}}
      +
      + {{/each}} +
      + {{/if}} + {{/if}} +
      +
      + +
      + + +
      +
      +
      \ No newline at end of file diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs index b5903cfc..b3dd53e6 100644 --- a/templates/sheets/actors/party/party-members.hbs +++ b/templates/sheets/actors/party/party-members.hbs @@ -5,7 +5,7 @@ >
      - From ad8caabf719c0f1cc9cf4551d1c9ebae33dc5aaa Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 16 Mar 2026 20:29:12 +0100 Subject: [PATCH 029/304] TagTeam Fixes --- module/applications/dialogs/tagTeamDialog.mjs | 26 ++++++++++++------- module/data/action/baseAction.mjs | 4 +-- .../dialogs/tagTeamDialog/tagTeamRoll.hbs | 4 +-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index bc35f360..ddaabcb4 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -1,6 +1,6 @@ import { MemberData } from '../../data/tagTeamData.mjs'; import { getCritDamageBonus } from '../../helpers/utils.mjs'; -import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; +import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import Party from '../sheets/actors/party.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -133,8 +133,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio group: item.name, baseAction: action.baseAction }; - rollOptions.push(actionItem); + if (action.hasDamage) damageRollOptions.push(actionItem); + else rollOptions.push(actionItem); } } } @@ -179,16 +180,23 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio this.updatePartyData(partyData); } - async updatePartyData(updata, options = { render: true }) { - await this.party.update(updata); - - if (options.render) { - this.render(true); + async updatePartyData(update, options = { render: true }) { + const gmUpdate = async update => { + await this.party.update(update); + this.render(); game.socket.emit(`system.${CONFIG.DH.id}`, { action: socketEvent.Refresh, data: { refreshType: RefreshType.TagTeamRoll, action: 'refresh' } }); - } + }; + + await emitAsGM( + GMUpdateEvent.UpdateDocument, + gmUpdate, + update, + this.party.uuid, + options.render ? { refreshType: RefreshType.TagTeamRoll, action: 'refresh' } : undefined + ); } getIsEditable() { @@ -414,8 +422,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio await action.workflow.get('damage').execute(config, null, true); if (!config.damage) return; - // const damage = config.roll.isCritical ? await this.getNonCriticalDamage(config, actor) : config.damage; - const current = this.party.system.tagTeam.members[memberKey].rollData; await this.updatePartyData({ [`system.tagTeam.members.${memberKey}.rollData`]: { diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 0cc1f8fa..1fa1e7c0 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -355,11 +355,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } get hasDamage() { - return !foundry.utils.isEmpty(this.damage?.parts) && this.type !== 'healing'; + return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type !== 'healing'; } get hasHealing() { - return !foundry.utils.isEmpty(this.damage?.parts) && this.type === 'healing'; + return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type === 'healing'; } get hasSave() { diff --git a/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs b/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs index 32464000..30369d52 100644 --- a/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs +++ b/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs @@ -27,7 +27,7 @@
      - {{else if (eq member.rollType 'abilityDamage')}} + {{else if (eq member.rollType 'damageAbility')}}
      @@ -79,7 +79,7 @@ {{this.fear.value}} - {{#if this.advantage}} + {{#if this.advantage.type}} {{#if (eq this.advantage.type 1)}}+{{else}}-{{/if}} {{this.advantage.value}} From 15fc879f9bf928dcbe43a00d89463ed9bdc72d37 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 18 Mar 2026 18:34:11 -0400 Subject: [PATCH 030/304] Fix damage icon when retrieving tooltip for attack (#1736) --- module/helpers/handlebarsHelper.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index 1c47f8dc..7f30d970 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -49,7 +49,8 @@ export default class RegisterHandlebarsHelpers { } static damageSymbols(damageParts) { - const symbols = [...new Set(damageParts.map(x => x.type))].map(p => CONFIG.DH.GENERAL.damageTypes[p].icon); + const allTypes = [...new Set([...damageParts].flatMap(x => Array.from(x.type)))]; + const symbols = allTypes.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon); return new Handlebars.SafeString(Array.from(symbols).map(symbol => ``)); } From b3e9c3fd9f13da00b3ff7e683806de0c01936c05 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 20 Mar 2026 19:46:30 -0400 Subject: [PATCH 031/304] Use ActiveEffect Config for settings as well (#1741) --- .../applications/sheets-configs/_module.mjs | 1 - .../sheets-configs/action-settings-config.mjs | 2 +- .../sheets-configs/activeEffectConfig.mjs | 26 ++ .../setting-active-effect-config.mjs | 223 ------------------ .../sheets-configs/setting-feature-config.mjs | 2 +- 5 files changed, 28 insertions(+), 226 deletions(-) delete mode 100644 module/applications/sheets-configs/setting-active-effect-config.mjs diff --git a/module/applications/sheets-configs/_module.mjs b/module/applications/sheets-configs/_module.mjs index d3fb3c39..4b83a042 100644 --- a/module/applications/sheets-configs/_module.mjs +++ b/module/applications/sheets-configs/_module.mjs @@ -3,7 +3,6 @@ export { default as ActionSettingsConfig } from './action-settings-config.mjs'; export { default as CharacterSettings } from './character-settings.mjs'; export { default as AdversarySettings } from './adversary-settings.mjs'; export { default as CompanionSettings } from './companion-settings.mjs'; -export { default as SettingActiveEffectConfig } from './setting-active-effect-config.mjs'; export { default as SettingFeatureConfig } from './setting-feature-config.mjs'; export { default as EnvironmentSettings } from './environment-settings.mjs'; export { default as ActiveEffectConfig } from './activeEffectConfig.mjs'; diff --git a/module/applications/sheets-configs/action-settings-config.mjs b/module/applications/sheets-configs/action-settings-config.mjs index 91b85802..9cb866bc 100644 --- a/module/applications/sheets-configs/action-settings-config.mjs +++ b/module/applications/sheets-configs/action-settings-config.mjs @@ -55,7 +55,7 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig { static async editEffect(event) { const id = event.target.closest('[data-effect-id]')?.dataset?.effectId; - const updatedEffect = await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure( + const updatedEffect = await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting( this.getEffectDetails(id) ); if (!updatedEffect) return; diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 2bd7d5b9..6e3fb1bd 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -247,4 +247,30 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac return submitData; } + + /** @inheritDoc */ + _processSubmitData(event, form, submitData, options) { + if (this.options.isSetting) { + // Settings should update source instead + this.document.updateSource(submitData); + this.render(); + } else { + return super._processSubmitData(event, form, submitData, options); + } + } + + /** Creates an active effect config for a setting */ + static async configureSetting(effect, options = {}) { + const document = new CONFIG.ActiveEffect.documentClass({ ...foundry.utils.duplicate(effect), _id: effect.id }); + return new Promise(resolve => { + const app = new this({ document, ...options, isSetting: true }); + app.addEventListener('close', () => { + const newEffect = app.document.toObject(true); + newEffect.id = newEffect._id; + delete newEffect._id; + resolve(newEffect); + }, { once: true }); + app.render({ force: true }); + }); + } } diff --git a/module/applications/sheets-configs/setting-active-effect-config.mjs b/module/applications/sheets-configs/setting-active-effect-config.mjs deleted file mode 100644 index 12ac90d1..00000000 --- a/module/applications/sheets-configs/setting-active-effect-config.mjs +++ /dev/null @@ -1,223 +0,0 @@ -import autocomplete from 'autocompleter'; - -const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; - -export default class SettingActiveEffectConfig extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(effect) { - super({}); - - this.effect = foundry.utils.deepClone(effect); - this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices(); - } - - static DEFAULT_OPTIONS = { - classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'standard-form'], - tag: 'form', - position: { - width: 560 - }, - form: { - submitOnChange: false, - closeOnSubmit: false, - handler: SettingActiveEffectConfig.#onSubmit - }, - actions: { - editImage: SettingActiveEffectConfig.#editImage, - addChange: SettingActiveEffectConfig.#addChange, - deleteChange: SettingActiveEffectConfig.#deleteChange - } - }; - - static PARTS = { - header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' }, - tabs: { template: 'templates/generic/tab-navigation.hbs' }, - details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] }, - settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' }, - changes: { - template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs', - scrollable: ['ol[data-changes]'] - }, - footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' } - }; - - static TABS = { - sheet: { - tabs: [ - { id: 'details', icon: 'fa-solid fa-book' }, - { id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' }, - { id: 'changes', icon: 'fa-solid fa-gears' } - ], - initial: 'details', - labelPrefix: 'EFFECT.TABS' - } - }; - - /**@inheritdoc */ - async _onFirstRender(context, options) { - await super._onFirstRender(context, options); - } - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.source = this.effect; - context.fields = game.system.api.documents.DhActiveEffect.schema.fields; - context.systemFields = game.system.api.data.activeEffects.BaseEffect._schema.fields; - - return context; - } - - _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}`.replaceAll( - ' ', - ' ' - ); - 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(), - customize: function (_input, _inputRect, container) { - container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ; - }, - minLength: 0 - }); - }); - } - - async _preparePartContext(partId, context) { - if (partId in context.tabs) context.tab = context.tabs[partId]; - switch (partId) { - case 'details': - context.statuses = CONFIG.statusEffects.map(s => ({ value: s.id, label: game.i18n.localize(s.name) })); - context.isActorEffect = false; - context.isItemEffect = true; - const useGeneric = game.settings.get( - CONFIG.DH.id, - CONFIG.DH.SETTINGS.gameSettings.appearance - ).showGenericStatusEffects; - if (!useGeneric) { - context.statuses = [ - ...context.statuses, - Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({ - value: status.id, - label: game.i18n.localize(status.name) - })) - ]; - } - break; - case 'changes': - context.modes = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((modes, [key, value]) => { - modes[value] = game.i18n.localize(`EFFECT.MODE_${key}`); - return modes; - }, {}); - - context.priorities = ActiveEffectConfig.DEFAULT_PRIORITIES; - break; - } - - return context; - } - - static async #onSubmit(_event, _form, formData) { - this.data = foundry.utils.expandObject(formData.object); - this.close(); - } - - /** - * Edit a Document image. - * @this {DocumentSheetV2} - * @type {ApplicationClickAction} - */ - static async #editImage(_event, target) { - if (target.nodeName !== 'IMG') { - throw new Error('The editImage action is available only for IMG elements.'); - } - - const attr = target.dataset.edit; - const current = foundry.utils.getProperty(this.effect, attr); - const fp = new FilePicker.implementation({ - current, - type: 'image', - callback: path => (target.src = path), - position: { - top: this.position.top + 40, - left: this.position.left + 10 - } - }); - - await fp.browse(); - } - - /** - * Add a new change to the effect's changes array. - * @this {ActiveEffectConfig} - * @type {ApplicationClickAction} - */ - static async #addChange() { - const { changes, ...rest } = foundry.utils.expandObject(new FormDataExtended(this.form).object); - const updatedChanges = Object.values(changes ?? {}); - updatedChanges.push({}); - - this.effect = { ...rest, changes: updatedChanges }; - this.render(); - } - - /** - * Delete a change from the effect's changes array. - * @this {ActiveEffectConfig} - * @type {ApplicationClickAction} - */ - static async #deleteChange(event) { - const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object); - const updatedChanges = Object.values(submitData.changes); - const row = event.target.closest('li'); - const index = Number(row.dataset.index) || 0; - updatedChanges.splice(index, 1); - - this.effect = { ...submitData, changes: updatedChanges }; - this.render(); - } - - static async configure(effect, options = {}) { - return new Promise(resolve => { - const app = new this(effect, options); - app.addEventListener('close', () => resolve(app.data), { once: true }); - app.render({ force: true }); - }); - } -} diff --git a/module/applications/sheets-configs/setting-feature-config.mjs b/module/applications/sheets-configs/setting-feature-config.mjs index fb790f7f..f90bb52f 100644 --- a/module/applications/sheets-configs/setting-feature-config.mjs +++ b/module/applications/sheets-configs/setting-feature-config.mjs @@ -147,7 +147,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App const effectIndex = this.move.effects.findIndex(x => x.id === id); const effect = this.move.effects[effectIndex]; const updatedEffect = - await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect); + await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(effect); if (!updatedEffect) return; await this.updateMove({ From 461247b285cb0b92b2717d708ee5d84a5b4915c5 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 21 Mar 2026 00:52:37 +0100 Subject: [PATCH 032/304] Raised version --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index 41e46edb..9a78bf50 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.0.0", "compatibility": { "minimum": "14.355", - "verified": "14.356", + "verified": "14.357", "maximum": "14" }, "authors": [ From a3f515cf6d001896c11744c42c19ec511073bf53 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 21 Mar 2026 11:39:18 +0100 Subject: [PATCH 033/304] Fixed TokenConfig error:ing on save --- module/applications/sheets-configs/token-config-mixin.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module/applications/sheets-configs/token-config-mixin.mjs b/module/applications/sheets-configs/token-config-mixin.mjs index 5242d797..bed7f628 100644 --- a/module/applications/sheets-configs/token-config-mixin.mjs +++ b/module/applications/sheets-configs/token-config-mixin.mjs @@ -67,9 +67,9 @@ export default function DHTokenConfigMixin(Base) { changes.height = tokenSize; } - const deletions = { actorId: _del, actorLink: _del }; - const mergeOptions = { inplace: false, performDeletions: true }; - this._preview.updateSource(foundry.utils.mergeObject(changes, deletions, mergeOptions)); + // const deletions = { actorId: _del }; + // const mergeOptions = { inplace: false, performDeletions: true, actorLink: false }; + this._preview.updateSource(changes); if (this._preview?.object?.destroyed === false) { this._preview.object.initializeSources(); From ef53a7c561af2f9a09e90a631e046468b2972e51 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 22 Mar 2026 01:57:46 +0100 Subject: [PATCH 034/304] [V14] 1354 - Armor Effect (#1652) * Initial * progress * Working armor application * . * Added a updateArmorValue function that updates armoreffects according to an auto order * . * Added createDialog * . * Updated Armor SRD * . * Fixed character sheet armor update * Updated itemconfig * Actions now use createDialog for effects * . * . * Fixed ArmorEffect max being a string * Fixed SRD armor effects * Finally finished the migration ._. * SRD finalization * Added ArmoreEffect.armorInteraction option * Added ArmorManagement menu * Fixed DamageReductionDialog * Fixed ArmorManagement pip syle * feat: add style to armors tooltip, add a style to make armor slot label more clear that was a button and add a tooltip location * . * Removed tooltip on manageArmor * Fixes * Fixed Downtime armor repair * Removed ArmorScore from character data model and instead adding it in basePrep * [Feature] ArmorEffect reworked into ChangeType on BaseEffect (#1739) * Initial * . * Single armor rework start * More fixes * Fixed DamageReductionDialog * Removed last traces of ArmorEffect * . * Corrected the SRD to use base effects again * Removed bare bones armor item * [V14] Refactor ArmorChange schema and fix some bugs (#1742) * Refactor ArmorChange schema and fix some bugs * Add current back to schema * Fixed so changing armor values and taking damage works again * Fixed so that scrolltexts for armor changes work again * Removed old marks on armor.system * Restored damageReductionDialog armorScore.value * Use toggle for css class addition/removal * Fix armor change type choices * Added ArmorChange DamageThresholds --------- Co-authored-by: WBHarry * [V14] Armor System ArmorScore (#1744) * Readded so that armor items have their system defined armor instead of using an ActiveEffect * Consolidate armor source retrieval * Fix regression with updating armor when sources are disabled * Simplify armor pip update * Use helper in damage reduction dialog * . * Corrected SRD Armor Items --------- Co-authored-by: Carlos Fernandez * Updated migrations * Migrations are now not horrible =D --------- Co-authored-by: Murilo Brito Co-authored-by: Carlos Fernandez Co-authored-by: Carlos Fernandez --- daggerheart.mjs | 3 +- lang/en.json | 22 +- .../dialogs/damageReductionDialog.mjs | 142 ++++++++---- module/applications/dialogs/downtime.mjs | 2 +- .../sheets-configs/action-config.mjs | 5 +- .../sheets-configs/activeEffectConfig.mjs | 91 ++++++-- .../applications/sheets/actors/character.mjs | 120 +++++++++- module/applications/sheets/actors/party.mjs | 13 +- .../sheets/api/application-mixin.mjs | 2 + module/applications/sheets/api/base-actor.mjs | 2 +- module/applications/sheets/items/armor.mjs | 9 + module/applications/ui/_module.mjs | 1 + module/applications/ui/progress.mjs | 27 +++ module/config/generalConfig.mjs | 17 +- module/config/itemConfig.mjs | 44 ++-- module/data/action/baseAction.mjs | 22 +- module/data/activeEffect/_module.mjs | 1 + module/data/activeEffect/baseEffect.mjs | 85 ++++++-- .../data/activeEffect/changeTypes/_module.mjs | 9 + .../data/activeEffect/changeTypes/armor.mjs | 206 ++++++++++++++++++ module/data/actor/character.mjs | 124 +++++++++-- module/data/item/armor.mjs | 31 +-- module/data/item/base.mjs | 9 +- module/documents/activeEffect.mjs | 12 +- module/documents/actor.mjs | 42 ++-- module/documents/item.mjs | 10 + module/helpers/utils.mjs | 64 ++++++ module/systemRegistration/handlebars.mjs | 3 +- module/systemRegistration/migrations.mjs | 95 ++++++++ .../domainCard_Armorer_cy8GjBPGc9w9RaGO.json | 45 ++-- ...omainCard_Bare_Bones_l5D9kq901JDESaXw.json | 47 +++- ...mainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json | 16 +- ...inCard_Valor_Touched_k1AtYd3lSchIymBr.json | 42 ++-- ...nced_Chainmail_Armor_LzLOJ9EVaHWAjoq9.json | 4 + ...ced_Full_Plate_Armor_crIbCb9NZ4K0VpoU.json | 4 + ...anced_Gambeson_Armor_epkAmlZVk7HOfUUT.json | 4 + ...vanced_Leather_Armor_itSOp2GCyem0f7oM.json | 4 + .../armor_Bare_Bones_ITAjcigTcUw5pMCN.json | 75 ------- ..._Bellamoi_Fine_Armor_WuoVwZA53XRAIt6d.json | 4 + ...rmor_Bladefare_Armor_mNN6pvcsS10ChrWF.json | 4 + ...rmor_Chainmail_Armor_haULhuEg37zUUvhb.json | 4 + ...mor_Channeling_Armor_vMJxEWz1srfwMsoj.json | 4 + ...or_Dragonscale_Armor_mdQ69eFHyAQUDmE7.json | 4 + ...or_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json | 4 + ...lundrian_Chain_Armor_Q6LxmtFetDDkoZVZ.json | 4 + ...mor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json | 4 + ...Full_Fortified_Armor_7emTSt6nhZuTlvt5.json | 4 + ...mor_Full_Plate_Armor_UdUJNa31WxFW2noa.json | 4 + ...armor_Gambeson_Armor_yJFp1bfpecDcStVK.json | 4 + ...mor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json | 4 + ...oved_Chainmail_Armor_K5WkjS0NGqHYmhU3.json | 4 + ...ved_Full_Plate_Armor_9f7RozpPTqrzJS1m.json | 4 + ...roved_Gambeson_Armor_jphnMZjnS2FkOH3s.json | 4 + ...proved_Leather_Armor_t91M61pSCMKStTNt.json | 4 + ...ee_Breastplate_Armor_tzZntboNtHL5C6VM.json | 4 + .../armor_Leather_Armor_nibfdNtp2PtxvbVz.json | 4 + ...dary_Chainmail_Armor_EsIN5OLKe9ZYFNXZ.json | 4 + ...ary_Full_Plate_Armor_SXWjUR2aUR6bYvdl.json | 4 + ...ndary_Gambeson_Armor_c6tMXz4rPf9ioQrf.json | 4 + ...endary_Leather_Armor_Tptgl5WOj76TyFn7.json | 4 + ...armor_Monett_s_Cloak_AQzU2RsqS5V5bd1v.json | 4 + ...armor_Rosewild_Armor_tN8kAeBvNKM3EBFo.json | 4 + ...nes_of_Fortification_P4qAEDJUoNLgVRsA.json | 4 + ...netan_Floating_Armor_tHlBUDQC24YMZqd6.json | 4 + ...mor_Savior_Chainmail_8X16lJQ3xltTwynm.json | 4 + ...r_Spiked_Plate_Armor_QjwsIhXKqnlvRBMv.json | 4 + ...mor_Tyris_Soft_Armor_PSW3BxCGmtLeWOxM.json | 4 + ...r_Veritas_Opal_Armor_OvzgUTYy2RCN85vV.json | 4 + .../folders_Special_tI3bfr6Sgi16Z7zm.json | 12 - ...dvanced_Round_Shield_hiEOGF2reabGLUoi.json | 39 ++-- ...dvanced_Tower_Shield_OfOzQbs4hg6QbfTG.json | 72 ++++-- ...mproved_Round_Shield_DlinEBGZfIlvreO3.json | 39 ++-- ...mproved_Tower_Shield_bxt3NsbMqTSdI5ab.json | 72 ++++-- .../weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json | 33 ++- ...gendary_Round_Shield_A28WL9E2lJ3iLZHW.json | 39 ++-- ...gendary_Tower_Shield_MaJIROht7A9LxIZx.json | 72 ++++-- .../weapon_Round_Shield_mxwWKDujgsRcZWPT.json | 39 ++-- ...weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json | 71 ++++-- .../weapon_Tower_Shield_C9aWpK1shVMWP4m5.json | 72 ++++-- .../damage-reduction-container.less | 26 ++- .../sheets/activeEffects/activeEffects.less | 65 ++++++ .../less/sheets/actors/character/sidebar.less | 50 ++++- styles/less/ux/index.less | 1 + styles/less/ux/tooltip/armorManagement.less | 63 +++++- templates/dialogs/damageReduction.hbs | 56 ++--- templates/sheets/activeEffect/change.hbs | 4 +- templates/sheets/activeEffect/changes.hbs | 7 + .../activeEffect/typeChanges/armorChange.hbs | 28 +++ templates/sheets/actors/character/effects.hbs | 1 + templates/sheets/actors/character/sidebar.hbs | 30 +-- templates/sheets/actors/party/resources.hbs | 11 +- templates/sheets/items/armor/header.hbs | 2 +- templates/sheets/items/armor/settings.hbs | 4 +- templates/ui/tooltip/armorManagement.hbs | 24 +- 94 files changed, 1961 insertions(+), 545 deletions(-) create mode 100644 module/applications/ui/progress.mjs create mode 100644 module/data/activeEffect/changeTypes/_module.mjs create mode 100644 module/data/activeEffect/changeTypes/armor.mjs delete mode 100644 src/packs/items/armors/armor_Bare_Bones_ITAjcigTcUw5pMCN.json delete mode 100644 src/packs/items/armors/folders_Special_tI3bfr6Sgi16Z7zm.json create mode 100644 templates/sheets/activeEffect/typeChanges/armorChange.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index 0518a4cf..2eb5109f 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -43,6 +43,7 @@ CONFIG.Item.dataModels = models.items.config; CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect; CONFIG.ActiveEffect.dataModels = models.activeEffects.config; +CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeEffects }; CONFIG.Combat.documentClass = documents.DhpCombat; CONFIG.Combat.dataModels = { base: models.DhCombat }; @@ -211,6 +212,7 @@ Hooks.once('init', () => { SYSTEM.id, applications.sheetConfigs.ActiveEffectConfig, { + types: ['base', 'beastform', 'horde'], makeDefault: true, label: sheetLabel('DOCUMENT.ActiveEffect') } @@ -268,7 +270,6 @@ Hooks.on('setup', () => { ...damageThresholds, 'proficiency', 'evasion', - 'armorScore', 'scars', 'levelData.level.current' ] diff --git a/lang/en.json b/lang/en.json index dfb0f7b9..5fc2cef9 100755 --- a/lang/en.json +++ b/lang/en.json @@ -451,7 +451,7 @@ "text": "Are you sure you want to delete {name}?" }, "DamageReduction": { - "armorMarks": "Armor Marks", + "maxUseableArmor": "Useable Armor Slots", "armorWithStress": "Spend 1 stress to use an extra mark", "thresholdImmunities": "Threshold Immunities", "stress": "Stress", @@ -794,6 +794,11 @@ "bruiser": "for each Bruiser adversary.", "solo": "for each Solo adversary." }, + "ArmorInteraction": { + "none": { "label": "Ignores Armor" }, + "active": { "label": "Active w/ Armor" }, + "inactive": { "label": "Inactive w/ Armor" } + }, "ArmorFeature": { "burning": { "name": "Burning", @@ -1882,6 +1887,17 @@ "name": "Healing Roll" } }, + "ChangeTypes": { + "armor": { + "newArmorEffect": "Armor Effect", + "FIELDS": { + "interaction": { + "label": "Armor Interaction", + "hint": "Does the character wearing armor suppress this effect?" + } + } + } + }, "Duration": { "passive": "Passive", "temporary": "Temporary" @@ -2291,6 +2307,7 @@ "duality": "Duality", "dualityDice": "Duality Dice", "dualityRoll": "Duality Roll", + "effect": "Effect", "enabled": "Enabled", "evasion": "Evasion", "equipment": "Equipment", @@ -3097,6 +3114,9 @@ "knowTheTide": "Know The Tide gained a token", "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}" }, + "Progress": { + "migrationLabel": "Performing system migration. Please wait and do not close Foundry." + }, "Sidebar": { "actorDirectory": { "tier": "Tier {tier} {type}", diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index cd0a5cf7..930ca1a1 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -1,4 +1,4 @@ -import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs'; +import { damageKeyToNumber, getArmorSources, getDamageLabel } from '../../helpers/utils.mjs'; const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; @@ -10,6 +10,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.reject = reject; this.actor = actor; this.damage = damage; + this.damageType = damageType; this.rulesDefault = game.settings.get( CONFIG.DH.id, @@ -20,14 +21,20 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.rulesDefault ); - const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true); - const availableArmor = actor.system.armorScore - actor.system.armor.system.marks.value; - const maxArmorMarks = canApplyArmor ? availableArmor : 0; + const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled); + const armor = orderedArmorSources.reduce((acc, { document }) => { + const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData; + acc.push({ + effect: document, + marks: [...Array(max).keys()].reduce((acc, _, index) => { + const spent = index < current; + acc[foundry.utils.randomID()] = { selected: false, disabled: spent, spent }; + return acc; + }, {}) + }); - const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => { - acc[foundry.utils.randomID()] = { selected: false }; return acc; - }, {}); + }, []); const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce( (acc, _) => { acc[foundry.utils.randomID()] = { selected: false }; @@ -121,13 +128,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap context.thresholdImmunities = Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null; - const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } = + const { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor } = this.getDamageInfo(); - context.armorScore = this.actor.system.armorScore; + context.armorScore = this.actor.system.armorScore.max; context.armorMarks = currentMarks; - context.basicMarksUsed = - selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value; const stressReductionStress = this.availableStressReductions ? stressReductions.reduce((acc, red) => acc + red.cost, 0) @@ -141,16 +146,30 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } : null; - const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value; - context.marks = { - armor: Object.keys(this.marks.armor).reduce((acc, key, index) => { - const mark = this.marks.armor[key]; - if (!this.rulesOn || index + 1 <= maxArmor) acc[key] = mark; + context.maxArmorUsed = maxArmorUsed; + context.availableArmor = availableArmor; + context.basicMarksUsed = availableArmor === 0 || selectedStressMarks.length; - return acc; - }, {}), + const armorSources = []; + for (const source of this.marks.armor) { + const parent = source.effect.origin + ? await foundry.utils.fromUuid(source.effect.origin) + : source.effect.parent; + + const useEffectName = parent.type === 'armor' || parent instanceof Actor; + const label = useEffectName ? source.effect.name : parent.name; + armorSources.push({ + label: label, + uuid: source.effect.uuid, + marks: source.marks + }); + } + context.marks = { + armor: armorSources, stress: this.marks.stress }; + + context.usesStressArmor = Object.keys(context.marks.stress).length; context.availableStressReductions = this.availableStressReductions; context.damage = getDamageLabel(this.damage); @@ -167,27 +186,31 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } getDamageInfo = () => { - const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected); + const selectedArmorMarks = this.marks.armor.flatMap(x => Object.values(x.marks).filter(x => x.selected)); const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected); const stressReductions = this.availableStressReductions ? Object.values(this.availableStressReductions).filter(red => red.selected) : []; - const currentMarks = - this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length; + const currentMarks = this.actor.system.armorScore.value + selectedArmorMarks.length; + + const maxArmorUsed = this.actor.system.rules.damageReduction.maxArmorMarked.value + selectedStressMarks.length; + const availableArmor = + maxArmorUsed - + this.marks.armor.reduce((acc, source) => { + acc += Object.values(source.marks).filter(x => x.selected).length; + return acc; + }, 0); const armorMarkReduction = selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark; - let currentDamage = Math.max( - this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length, - 0 - ); + let currentDamage = Math.max(this.damage - armorMarkReduction - stressReductions.length, 0); if (this.reduceSeverity) { currentDamage = Math.max(currentDamage - this.reduceSeverity, 0); } if (this.thresholdImmunities[currentDamage]) currentDamage = 0; - return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage }; + return { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor }; }; static toggleRules() { @@ -195,13 +218,10 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value; this.marks = { - armor: Object.keys(this.marks.armor).reduce((acc, key, index) => { - const mark = this.marks.armor[key]; + armor: this.marks.armor.map((mark, index) => { const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor; - acc[key] = { ...mark, selected: keepSelectValue ? mark.selected : false }; - - return acc; - }, {}), + return { ...mark, selected: keepSelectValue ? mark.selected : false }; + }), stress: this.marks.stress }; @@ -209,8 +229,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } static setMarks(_, target) { - const currentMark = this.marks[target.dataset.type][target.dataset.key]; - const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo(); + const currentMark = foundry.utils.getProperty(this.marks, target.dataset.path); + const { selectedStressMarks, stressReductions, currentDamage, availableArmor } = this.getDamageInfo(); if (!currentMark.selected && currentDamage === 0) { ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone')); @@ -218,12 +238,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } if (this.rulesOn) { - if (!currentMark.selected && currentMarks === this.actor.system.armorScore) { + if (target.dataset.type === 'armor' && !currentMark.selected && !availableArmor) { ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks')); return; } } + const stressUsed = selectedStressMarks.length; + if (target.dataset.type === 'armor' && stressUsed) { + const updateResult = this.updateStressArmor(target.dataset.id, !currentMark.selected); + if (updateResult === false) return; + } + if (currentMark.selected) { const currentDamageLabel = getDamageLabel(currentDamage); for (let reduction of stressReductions) { @@ -232,8 +258,16 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } } - if (target.dataset.type === 'armor' && selectedStressMarks.length > 0) { - selectedStressMarks.forEach(mark => (mark.selected = false)); + if (target.dataset.type === 'stress' && currentMark.armorMarkId) { + for (const source of this.marks.armor) { + const match = Object.keys(source.marks).find(key => key === currentMark.armorMarkId); + if (match) { + source.marks[match].selected = false; + break; + } + } + + currentMark.armorMarkId = null; } } @@ -241,6 +275,25 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.render(); } + updateStressArmor(armorMarkId, select) { + let stressMarkKey = null; + if (select) { + stressMarkKey = Object.keys(this.marks.stress).find( + key => this.marks.stress[key].selected && !this.marks.stress[key].armorMarkId + ); + } else { + stressMarkKey = Object.keys(this.marks.stress).find( + key => this.marks.stress[key].armorMarkId === armorMarkId + ); + if (!stressMarkKey) + stressMarkKey = Object.keys(this.marks.stress).find(key => this.marks.stress[key].selected); + } + + if (!stressMarkKey) return false; + + this.marks.stress[stressMarkKey].armorMarkId = select ? armorMarkId : null; + } + static useStressReduction(_, target) { const damageValue = Number(target.dataset.reduction); const stressReduction = this.availableStressReductions[damageValue]; @@ -279,11 +332,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } static async takeDamage() { - const { selectedArmorMarks, selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo(); - const armorSpent = selectedArmorMarks.length + selectedStressMarks.length; - const stressSpent = selectedStressMarks.length + stressReductions.reduce((acc, red) => acc + red.cost, 0); + const { selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo(); + const armorChanges = this.marks.armor.reduce((acc, source) => { + const amount = Object.values(source.marks).filter(x => x.selected).length; + if (amount) acc.push({ uuid: source.effect.uuid, amount }); - this.resolve({ modifiedDamage: currentDamage, armorSpent, stressSpent }); + return acc; + }, []); + const stressSpent = + selectedStressMarks.filter(x => x.armorMarkId).length + + stressReductions.reduce((acc, red) => acc + red.cost, 0); + + this.resolve({ modifiedDamage: currentDamage, armorChanges, stressSpent }); await this.close(true); } diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 52cced3e..3475dee7 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -203,7 +203,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV const msg = { user: game.user.id, system: { - moves: moves, + moves: moves.map(move => ({ ...move, actions: Array.from(move.actions) })), actor: this.actor.uuid }, speaker: cls.getSpeaker(), diff --git a/module/applications/sheets-configs/action-config.mjs b/module/applications/sheets-configs/action-config.mjs index 0dbc377a..e75e16ab 100644 --- a/module/applications/sheets-configs/action-config.mjs +++ b/module/applications/sheets-configs/action-config.mjs @@ -24,9 +24,12 @@ export default class DHActionConfig extends DHActionBaseConfig { const effectData = this._addEffectData.bind(this)(); const data = this.action.toObject(); - const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { + const created = await game.system.api.documents.DhActiveEffect.createDialog(effectData, { + parent: this.action.item, render: false }); + if (!created) return; + data.effects.push({ _id: created._id }); this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); this.action.item.effects.get(created._id).sheet.render(true); diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 6e3fb1bd..9f970e1d 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -150,6 +150,14 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac minLength: 0 }); }); + + htmlElement + .querySelector('.armor-change-checkbox') + ?.addEventListener('change', this.armorChangeToggle.bind(this)); + + htmlElement + .querySelector('.armor-damage-thresholds-checkbox') + ?.addEventListener('change', this.armorDamageThresholdToggle.bind(this)); } async _prepareContext(options) { @@ -186,21 +194,66 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac })); break; case 'changes': - const fields = this.document.system.schema.fields.changes.element.fields; - partContext.changes = await Promise.all( - foundry.utils - .deepClone(context.source.changes) - .map((c, i) => this._prepareChangeContext(c, i, fields)) - ); + const singleTypes = ['armor']; + const typedChanges = context.source.changes.reduce((acc, change, index) => { + if (singleTypes.includes(change.type)) { + acc[change.type] = { ...change, index }; + } + return acc; + }, {}); + partContext.changes = partContext.changes.filter(c => !!c); + partContext.typedChanges = typedChanges; break; } return partContext; } - _prepareChangeContext(change, index, fields) { + armorChangeToggle(event) { + if (event.target.checked) { + this.addArmorChange(); + } else { + this.removeTypedChange(event.target.dataset.index); + } + } + + /* Could be generalised if needed later */ + addArmorChange() { + const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form)); + const changes = Object.values(submitData.system?.changes ?? {}); + changes.push(game.system.api.data.activeEffects.changeTypes.armor.getInitialValue()); + return this.submit({ updateData: { system: { changes } } }); + } + + removeTypedChange(indexString) { + const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form)); + const changes = Object.values(submitData.system.changes); + const index = Number(indexString); + changes.splice(index, 1); + return this.submit({ updateData: { system: { changes } } }); + } + + armorDamageThresholdToggle(event) { + const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form)); + const changes = Object.values(submitData.system?.changes ?? {}); + const index = Number(event.target.dataset.index); + if (event.target.checked) { + changes[index].value.damageThresholds = { major: 0, severe: 0 }; + } else { + changes[index].value.damageThresholds = null; + } + + return this.submit({ updateData: { system: { changes } } }); + } + + /** @inheritdoc */ + _renderChange(context) { + const { change, index, defaultPriority } = context; + if (!(change.type in CONFIG.DH.GENERAL.baseActiveEffectModes)) return null; + + const changeTypesSchema = this.document.system.schema.fields.changes.element.types; + const fields = context.fields ?? (changeTypesSchema[change.type] ?? changeTypesSchema.add).fields; if (typeof change.value !== 'string') change.value = JSON.stringify(change.value); - const defaultPriority = game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type]?.defaultPriority; Object.assign( change, ['key', 'type', 'value', 'priority'].reduce((paths, fieldName) => { @@ -220,7 +273,11 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac change, index, defaultPriority, - fields + fields, + types: Object.keys(CONFIG.DH.GENERAL.baseActiveEffectModes).reduce((r, key) => { + r[key] = CONFIG.DH.GENERAL.baseActiveEffectModes[key].label; + return r; + }, {}) } ) ); @@ -264,12 +321,16 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac const document = new CONFIG.ActiveEffect.documentClass({ ...foundry.utils.duplicate(effect), _id: effect.id }); return new Promise(resolve => { const app = new this({ document, ...options, isSetting: true }); - app.addEventListener('close', () => { - const newEffect = app.document.toObject(true); - newEffect.id = newEffect._id; - delete newEffect._id; - resolve(newEffect); - }, { once: true }); + app.addEventListener( + 'close', + () => { + const newEffect = app.document.toObject(true); + newEffect.id = newEffect._id; + delete newEffect._id; + resolve(newEffect); + }, + { once: true } + ); app.render({ force: true }); }); } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index db236197..f2686fdd 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs'; import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; -import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs'; +import { getArmorSources, getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -34,7 +34,8 @@ export default class CharacterSheet extends DHBaseActorSheet { cancelBeastform: CharacterSheet.#cancelBeastform, toggleResourceManagement: CharacterSheet.#toggleResourceManagement, useDowntime: this.useDowntime, - viewParty: CharacterSheet.#viewParty + viewParty: CharacterSheet.#viewParty, + toggleArmorMangement: CharacterSheet.#toggleArmorManagement }, window: { resizable: true, @@ -638,12 +639,12 @@ export default class CharacterSheet extends DHBaseActorSheet { } async updateArmorMarks(event) { - const armor = this.document.system.armor; - if (!armor) return; + const inputValue = Number(event.currentTarget.value); + const { value, max } = this.document.system.armorScore; + const changeValue = Math.min(inputValue - value, max - value); - 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 }); + event.currentTarget.value = inputValue < 0 ? 0 : value + changeValue; + this.document.system.updateArmorValue({ value: changeValue }); } /* -------------------------------------------- */ @@ -803,10 +804,13 @@ export default class CharacterSheet extends DHBaseActorSheet { * Toggles ArmorScore resource value. * @type {ApplicationClickAction} */ - static async #toggleArmor(_, button, element) { - const ArmorValue = Number.parseInt(button.dataset.value); - const newValue = this.document.system.armor.system.marks.value >= ArmorValue ? ArmorValue - 1 : ArmorValue; - await this.document.system.armor.update({ 'system.marks.value': newValue }); + static async #toggleArmor(_, button, _element) { + const { value, max } = this.document.system.armorScore; + const inputValue = Number.parseInt(button.dataset.value); + const newValue = value >= inputValue ? inputValue - 1 : inputValue; + const changeValue = Math.min(newValue - value, max - value); + + this.document.system.updateArmorValue({ value: changeValue }); } /** @@ -932,6 +936,99 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } + static async #toggleArmorManagement(_event, target) { + const existingTooltip = document.body.querySelector('.locked-tooltip .armor-management-container'); + if (existingTooltip) { + game.tooltip.dismissLockedTooltips(); + return; + } + + const armorSources = getArmorSources(this.document) + .filter(s => !s.disabled) + .toReversed() + .map(({ name, document, data }) => ({ + ...data, + uuid: document.uuid, + name + })); + if (!armorSources.length) return; + + const useResourcePips = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.appearance + ).useResourcePips; + const html = document.createElement('div'); + html.innerHTML = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/armorManagement.hbs`, + { + sources: armorSources, + useResourcePips + } + ); + + game.tooltip.dismissLockedTooltips(); + game.tooltip.activate(target, { + html, + locked: true, + cssClass: 'bordered-tooltip', + direction: 'DOWN' + }); + + html.querySelectorAll('.armor-slot').forEach(element => { + element.addEventListener('click', CharacterSheet.armorSourcePipUpdate); + }); + } + + static async armorSourceInput(event) { + const effect = await foundry.utils.fromUuid(event.target.dataset.uuid); + const value = Math.max(Math.min(Number.parseInt(event.target.value), effect.system.armorData.max), 0); + event.target.value = value; + const progressBar = event.target.closest('.status-bar.armor-slots').querySelector('progress'); + progressBar.value = value; + } + + /** Update specific armor source */ + static async armorSourcePipUpdate(event) { + const target = event.target.closest('.armor-slot'); + const { uuid, value } = target.dataset; + const document = await foundry.utils.fromUuid(uuid); + + let inputValue = Number.parseInt(value); + let decreasing = false; + let newCurrent = 0; + + if (document.type === 'armor') { + decreasing = document.system.armor.current >= inputValue; + newCurrent = decreasing ? inputValue - 1 : inputValue; + await document.update({ 'system.armor.current': newCurrent }); + } else if (document.system.armorData) { + const { current } = document.system.armorData; + decreasing = current >= inputValue; + newCurrent = decreasing ? inputValue - 1 : inputValue; + + const newChanges = document.system.changes.map(change => ({ + ...change, + value: change.type === 'armor' ? { ...change.value, current: newCurrent } : change.value + })); + + await document.update({ 'system.changes': newChanges }); + } else { + return; + } + + const container = target.closest('.slot-bar'); + for (const armorSlot of container.querySelectorAll('.armor-slot i')) { + const index = Number.parseInt(armorSlot.dataset.index); + if (decreasing && index >= newCurrent) { + armorSlot.classList.remove('fa-shield'); + armorSlot.classList.add('fa-shield-halved'); + } else if (!decreasing && index < newCurrent) { + armorSlot.classList.add('fa-shield'); + armorSlot.classList.remove('fa-shield-halved'); + } + } + } + static async #toggleResourceManagement(event, button) { event.stopPropagation(); const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container'); @@ -965,7 +1062,6 @@ export default class CharacterSheet extends DHBaseActorSheet { ); const target = button.closest('.resource-section'); - game.tooltip.dismissLockedTooltips(); game.tooltip.activate(target, { html, diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 45bdb679..c5e77112 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -189,11 +189,14 @@ export default class Party extends DHBaseActorSheet { * Toggles a armor slot resource value. * @type {ApplicationClickAction} */ - static async #toggleArmorSlot(_, target, element) { - const armorItem = await foundry.utils.fromUuid(target.dataset.itemUuid); - const armorValue = Number.parseInt(target.dataset.value); - const newValue = armorItem.system.marks.value >= armorValue ? armorValue - 1 : armorValue; - await armorItem.update({ 'system.marks.value': newValue }); + static async #toggleArmorSlot(_, target) { + const actor = game.actors.get(target.dataset.actorId); + const { value, max } = actor.system.armorScore; + const inputValue = Number.parseInt(target.dataset.value); + const newValue = value >= inputValue ? inputValue - 1 : inputValue; + const changeValue = Math.min(newValue - value, max - value); + + await actor.system.updateArmorValue({ value: changeValue }); this.render(); } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 89941399..83313454 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -749,11 +749,13 @@ export default function DHApplicationMixin(Base) { const cls = type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass); + const data = { name: cls.defaultName({ type, parent }), type, system: systemData }; + if (inVault) data['system.inVault'] = true; if (disabled) data.disabled = true; if (type === 'domainCard' && parent?.system.domains?.length) { diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 6f994faf..4b0fd7d9 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -160,7 +160,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { inactives: [] }; - for (const effect of this.actor.allApplicableEffects()) { + for (const effect of this.actor.allApplicableEffects({ noTransferArmor: true })) { const list = effect.active ? context.effects.actives : context.effects.inactives; list.push(effect); } diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 2550b415..54685fee 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -47,6 +47,15 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) { return context; } + async updateArmorEffect(event) { + const value = Number.parseInt(event.target.value); + const armorEffect = this.document.system.armorEffect; + if (Number.isNaN(value) || !armorEffect) return; + + await armorEffect.system.armorChange.updateArmorMax(value); + this.render(); + } + /** * Callback function used by `tagifyElement`. * @param {Array} selectedOptions - The currently selected tag objects. diff --git a/module/applications/ui/_module.mjs b/module/applications/ui/_module.mjs index 8c5c020e..80d3ebe4 100644 --- a/module/applications/ui/_module.mjs +++ b/module/applications/ui/_module.mjs @@ -7,3 +7,4 @@ export { default as DhFearTracker } from './fearTracker.mjs'; export { default as DhHotbar } from './hotbar.mjs'; export { default as DhSceneNavigation } from './sceneNavigation.mjs'; export { ItemBrowser } from './itemBrowser.mjs'; +export { default as DhProgress } from './progress.mjs'; diff --git a/module/applications/ui/progress.mjs b/module/applications/ui/progress.mjs new file mode 100644 index 00000000..2fb1b445 --- /dev/null +++ b/module/applications/ui/progress.mjs @@ -0,0 +1,27 @@ +export default class DhProgress { + #notification; + + constructor({ max, label = '' }) { + this.max = max; + this.label = label; + this.#notification = ui.notifications.info(this.label, { progress: true }); + } + + updateMax(newMax) { + this.max = newMax; + } + + advance({ by = 1, label = this.label } = {}) { + if (this.value === this.max) return; + this.value = (this.value ?? 0) + Math.abs(by); + this.#notification.update({ message: label, pct: this.value / this.max }); + } + + close({ label = '' } = {}) { + this.#notification.update({ message: label, pct: 1 }); + } + + static createMigrationProgress(max = 0) { + return new DhProgress({ max, label: game.i18n.localize('DAGGERHEART.UI.Progress.migrationLabel') }); + } +} diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 2b633644..c15cae56 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -974,7 +974,7 @@ export const tagTeamRollTypes = { } }; -export const activeEffectModes = { +export const baseActiveEffectModes = { custom: { id: 'custom', priority: 0, @@ -1012,6 +1012,21 @@ export const activeEffectModes = { } }; +export const activeEffectModes = { + armor: { + id: 'armor', + priority: 20, + label: 'TYPES.ActiveEffect.armor' + }, + ...baseActiveEffectModes +}; + +export const activeEffectArmorInteraction = { + none: { id: 'none', label: 'DAGGERHEART.CONFIG.ArmorInteraction.none.label' }, + active: { id: 'active', label: 'DAGGERHEART.CONFIG.ArmorInteraction.active.label' }, + inactive: { id: 'inactive', label: 'DAGGERHEART.CONFIG.ArmorInteraction.inactive.label' } +}; + export const activeEffectDurations = { temporary: { id: 'temporary', diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index b424c707..a3e785c3 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -489,15 +489,18 @@ export const weaponFeatures = { description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description', img: 'icons/skills/melee/shield-block-bash-blue.webp', changes: [ - { - key: 'system.armorScore', - mode: 2, - value: 'ITEM.@system.tier + 1' - }, { key: 'system.evasion', mode: 2, value: '-1' + }, + { + key: 'Armor', + type: 'armor', + typeData: { + type: 'armor', + max: 'ITEM.@system.tier + 1' + } } ] } @@ -789,11 +792,6 @@ export const weaponFeatures = { description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description', img: 'icons/skills/melee/sword-shield-stylized-white.webp', changes: [ - { - key: 'system.armorScore', - mode: 2, - value: '1' - }, { key: 'system.bonuses.damage.primaryWeapon.bonus', mode: 2, @@ -808,6 +806,22 @@ export const weaponFeatures = { type: 'withinRange' } } + }, + { + name: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.name', + description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description', + img: 'icons/skills/melee/sword-shield-stylized-white.webp', + changes: [ + { + key: 'Armor', + type: 'armor', + value: 0, + typeData: { + type: 'armor', + max: 1 + } + } + ] } ] }, @@ -1191,9 +1205,13 @@ export const weaponFeatures = { img: 'icons/skills/melee/shield-block-gray-orange.webp', changes: [ { - key: 'system.armorScore', - mode: 2, - value: 'ITEM.@system.tier' + key: 'Armor', + type: 'armor', + value: 0, + typeData: { + type: 'armor', + max: 'ITEM.@system.tier' + } } ] } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 1fa1e7c0..01139b30 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -298,17 +298,19 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel static async getEffects(actor, effectParent) { if (!actor) return []; - return Array.from(await actor.allApplicableEffects()).filter(effect => { - /* Effects on weapons only ever apply for the weapon itself */ - if (effect.parent.type === 'weapon') { - /* Unless they're secondary - then they apply only to other primary weapons */ - if (effect.parent.system.secondary) { - if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false; - } else if (effectParent?.id !== effect.parent.id) return false; - } + return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).filter( + effect => { + /* Effects on weapons only ever apply for the weapon itself */ + if (effect.parent.type === 'weapon') { + /* Unless they're secondary - then they apply only to other primary weapons */ + if (effect.parent.system.secondary) { + if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false; + } else if (effectParent?.id !== effect.parent.id) return false; + } - return !effect.isSuppressed; - }); + return !effect.isSuppressed; + } + ); } /** diff --git a/module/data/activeEffect/_module.mjs b/module/data/activeEffect/_module.mjs index 1a50088a..3c933a9c 100644 --- a/module/data/activeEffect/_module.mjs +++ b/module/data/activeEffect/_module.mjs @@ -1,6 +1,7 @@ import BaseEffect from './baseEffect.mjs'; import BeastformEffect from './beastformEffect.mjs'; import HordeEffect from './hordeEffect.mjs'; +export { changeTypes, changeEffects } from './changeTypes/_module.mjs'; export { BaseEffect, BeastformEffect, HordeEffect }; diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index 98a961d7..c00b8cf7 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -12,26 +12,41 @@ * "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility) */ +import { getScrollTextData } from '../../helpers/utils.mjs'; +import { changeTypes } from './_module.mjs'; + export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { static defineSchema() { const fields = foundry.data.fields; + const baseChanges = Object.keys(CONFIG.DH.GENERAL.baseActiveEffectModes).reduce((r, type) => { + r[type] = new fields.SchemaField({ + key: new fields.StringField({ required: true }), + type: new fields.StringField({ + required: true, + choices: [type], + initial: type, + validate: BaseEffect.#validateType + }), + value: new fields.AnyField({ + required: true, + nullable: true, + serializable: true, + initial: '' + }), + phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), + priority: new fields.NumberField() + }); + return r; + }, {}); + return { ...super.defineSchema(), changes: new fields.ArrayField( - new fields.SchemaField({ - key: new fields.StringField({ required: true }), - type: new fields.StringField({ - required: true, - blank: false, - choices: CONFIG.DH.GENERAL.activeEffectModes, - initial: CONFIG.DH.GENERAL.activeEffectModes.add.id, - validate: BaseEffect.#validateType - }), - value: new fields.AnyField({ required: true, nullable: true, serializable: true, initial: '' }), - phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), - priority: new fields.NumberField() - }) + new fields.TypedSchemaField( + { ...changeTypes, ...baseChanges }, + { initial: baseChanges.add.getInitialValue() } + ) ), duration: new fields.SchemaField({ type: new fields.StringField({ @@ -86,6 +101,23 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { return true; } + get isSuppressed() { + for (const change of this.changes) { + if (change.isSuppressed) return true; + } + } + + get armorChange() { + return this.changes.find(x => x.type === CONFIG.DH.GENERAL.activeEffectModes.armor.id); + } + + get armorData() { + const armorChange = this.armorChange; + if (!armorChange) return null; + + return armorChange.getArmorData(); + } + static getDefaultObject() { return { name: 'New Effect', @@ -105,4 +137,31 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { } }; } + + async _preUpdate(changed, options, userId) { + const allowed = await super._preUpdate(changed, options, userId); + if (allowed === false) return false; + + const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + if ( + autoSettings.resourceScrollTexts && + this.parent.actor?.type === 'character' && + this.parent.actor.system.resources.armor + ) { + const newArmorTotal = (changed.system?.changes ?? []).reduce((acc, change) => { + if (change.type === 'armor') acc += change.value.current; + return acc; + }, this.parent.actor.system.armor?.system?.armor?.current ?? 0); + + const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor'); + options.scrollingTextData = [armorData]; + } + } + + _onUpdate(changed, options, userId) { + super._onUpdate(changed, options, userId); + + if (this.parent.actor && options.scrollingTextData) + this.parent.actor.queueScrollText(options.scrollingTextData); + } } diff --git a/module/data/activeEffect/changeTypes/_module.mjs b/module/data/activeEffect/changeTypes/_module.mjs new file mode 100644 index 00000000..cf872304 --- /dev/null +++ b/module/data/activeEffect/changeTypes/_module.mjs @@ -0,0 +1,9 @@ +import Armor from './armor.mjs'; + +export const changeEffects = { + armor: Armor.changeEffect +}; + +export const changeTypes = { + armor: Armor +}; diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs new file mode 100644 index 00000000..f400d41b --- /dev/null +++ b/module/data/activeEffect/changeTypes/armor.mjs @@ -0,0 +1,206 @@ +import { itemAbleRollParse } from '../../../helpers/utils.mjs'; + +const fields = foundry.data.fields; + +export default class ArmorChange extends foundry.abstract.DataModel { + static defineSchema() { + return { + type: new fields.StringField({ required: true, choices: ['armor'], initial: 'armor' }), + priority: new fields.NumberField(), + phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), + value: new fields.SchemaField({ + current: new fields.NumberField({ integer: true, min: 0, initial: 0 }), + max: new fields.StringField({ + required: true, + nullable: false, + initial: '1', + label: 'DAGGERHEART.GENERAL.max' + }), + damageThresholds: new fields.SchemaField( + { + major: new fields.StringField({ + initial: '0', + label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold' + }), + severe: new fields.StringField({ + initial: '0', + label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold' + }) + }, + { nullable: true, initial: null } + ), + interaction: new fields.StringField({ + required: true, + choices: CONFIG.DH.GENERAL.activeEffectArmorInteraction, + initial: CONFIG.DH.GENERAL.activeEffectArmorInteraction.none.id, + label: 'DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.interaction.label', + hint: 'DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.interaction.hint' + }) + }) + }; + } + + static changeEffect = { + label: 'Armor', + defaultPriority: 20, + handler: (actor, change, _options, _field, replacementData) => { + const parsedMax = itemAbleRollParse(change.value.max, actor, change.effect.parent); + game.system.api.documents.DhActiveEffect.applyChange( + actor, + { + ...change, + key: 'system.armorScore.value', + type: CONFIG.DH.GENERAL.activeEffectModes.add.id, + value: change.value.current + }, + replacementData + ); + game.system.api.documents.DhActiveEffect.applyChange( + actor, + { + ...change, + key: 'system.armorScore.max', + type: CONFIG.DH.GENERAL.activeEffectModes.add.id, + value: parsedMax + }, + replacementData + ); + + if (change.value.damageThresholds) { + const getThresholdValue = value => { + const parsed = itemAbleRollParse(value, actor, change.effect.parent); + const roll = new Roll(parsed).evaluateSync(); + return roll ? (roll.isDeterministic ? roll.total : null) : null; + }; + const major = getThresholdValue(change.value.damageThresholds.major); + const severe = getThresholdValue(change.value.damageThresholds.severe); + + if (major) { + game.system.api.documents.DhActiveEffect.applyChange( + actor, + { + ...change, + key: 'system.damageThresholds.major', + type: CONFIG.DH.GENERAL.activeEffectModes.override.id, + priority: 50, + value: major + }, + replacementData + ); + } + + if (severe) { + game.system.api.documents.DhActiveEffect.applyChange( + actor, + { + ...change, + key: 'system.damageThresholds.severe', + type: CONFIG.DH.GENERAL.activeEffectModes.override.id, + priority: 50, + value: severe + }, + replacementData + ); + } + } + + return {}; + }, + render: null + }; + + get isSuppressed() { + switch (this.value.interaction) { + case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id: + return !this.parent.parent?.actor.system.armor; + case CONFIG.DH.GENERAL.activeEffectArmorInteraction.inactive.id: + return Boolean(this.parent.parent?.actor.system.armor); + default: + return false; + } + } + + static getInitialValue() { + return { + type: CONFIG.DH.GENERAL.activeEffectModes.armor.id, + value: { + current: 0, + max: 0 + }, + phase: 'initial', + priority: 20 + }; + } + + static getDefaultArmorEffect() { + return { + name: game.i18n.localize('DAGGERHEART.EFFECTS.ChangeTypes.armor.newArmorEffect'), + img: 'icons/equipment/chest/breastplate-helmet-metal.webp', + system: { + changes: [ArmorChange.getInitialValue()] + } + }; + } + + /* Helpers */ + + getArmorData() { + const actor = this.parent.parent?.actor?.type === 'character' ? this.parent.parent.actor : null; + const maxParse = actor ? itemAbleRollParse(this.value.max, actor, this.parent.parent.parent) : null; + const maxRoll = maxParse ? new Roll(maxParse).evaluateSync() : null; + const maxEvaluated = maxRoll ? (maxRoll.isDeterministic ? maxRoll.total : null) : null; + + return { + current: this.value.current, + max: maxEvaluated ?? this.value.max + }; + } + + async updateArmorMax(newMax) { + const newChanges = [ + ...this.parent.changes.map(change => ({ + ...change, + value: + change.type === 'armor' + ? { + ...change.value, + current: Math.min(change.value.current, newMax), + max: newMax + } + : change.value + })) + ]; + await this.parent.parent.update({ 'system.changes': newChanges }); + } + + static orderEffectsForAutoChange(armorEffects, increasing) { + const getEffectWeight = effect => { + switch (effect.parent.type) { + case 'class': + case 'subclass': + case 'ancestry': + case 'community': + case 'feature': + case 'domainCard': + return 2; + case 'armor': + return 3; + case 'loot': + case 'consumable': + return 4; + case 'weapon': + return 5; + case 'character': + return 6; + default: + return 1; + } + }; + + return armorEffects + .filter(x => !x.disabled && !x.isSuppressed) + .sort((a, b) => + increasing ? getEffectWeight(b) - getEffectWeight(a) : getEffectWeight(a) - getEffectWeight(b) + ); + } +} diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index d5481cc5..55c1c7d8 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -6,6 +6,7 @@ import DhCreature from './creature.mjs'; import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { ActionField } from '../fields/actionField.mjs'; import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs'; +import { getArmorSources } from '../../helpers/utils.mjs'; export default class DhCharacter extends DhCreature { /**@override */ @@ -41,17 +42,16 @@ export default class DhCharacter extends DhCreature { 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, - label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold' - }), major: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold' + }), + severe: new fields.NumberField({ + integer: true, + initial: 0, + label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold' }) }), experiences: new fields.TypedObjectField( @@ -465,6 +465,101 @@ export default class DhCharacter extends DhCreature { } } + async updateArmorValue({ value: armorChange = 0, clear = false }) { + if (armorChange === 0 && !clear) return; + + const increasing = armorChange >= 0; + let remainingChange = Math.abs(armorChange); + const orderedSources = getArmorSources(this.parent).filter(s => !s.disabled); + + const handleArmorData = (embeddedUpdates, doc, armorData) => { + let usedArmorChange = 0; + if (clear) { + usedArmorChange -= armorData.current; + } else { + if (increasing) { + const remainingArmor = armorData.max - armorData.current; + usedArmorChange = Math.min(remainingChange, remainingArmor); + remainingChange -= usedArmorChange; + } else { + const changeChange = Math.min(armorData.current, remainingChange); + usedArmorChange -= changeChange; + remainingChange -= changeChange; + } + } + + if (!usedArmorChange) return usedArmorChange; + else { + if (!embeddedUpdates[doc.id]) embeddedUpdates[doc.id] = { doc: doc, updates: [] }; + + return usedArmorChange; + } + }; + + const armorUpdates = []; + const effectUpdates = []; + for (const { document: armorSource } of orderedSources) { + const usedArmorChange = handleArmorData( + armorSource.type === 'armor' ? armorUpdates : effectUpdates, + armorSource.parent, + armorSource.type === 'armor' ? armorSource.system.armor : armorSource.system.armorData + ); + if (!usedArmorChange) continue; + + if (armorSource.type === 'armor') { + armorUpdates[armorSource.parent.id].updates.push({ + '_id': armorSource.id, + 'system.armor.current': armorSource.system.armor.current + usedArmorChange + }); + } else { + effectUpdates[armorSource.parent.id].updates.push({ + '_id': armorSource.id, + 'system.changes': armorSource.system.changes.map(change => ({ + ...change, + value: + change.type === 'armor' + ? { + ...change.value, + current: armorSource.system.armorChange.value.current + usedArmorChange + } + : change.value + })) + }); + } + + if (remainingChange === 0 && !clear) break; + } + + const armorUpdateValues = Object.values(armorUpdates); + for (const [index, { doc, updates }] of armorUpdateValues.entries()) + await doc.updateEmbeddedDocuments('Item', updates, { render: index === armorUpdateValues.length - 1 }); + + const effectUpdateValues = Object.values(effectUpdates); + for (const [index, { doc, updates }] of effectUpdateValues.entries()) + await doc.updateEmbeddedDocuments('ActiveEffect', updates, { + render: index === effectUpdateValues.length - 1 + }); + } + + async updateArmorEffectValue({ uuid, value }) { + const source = await foundry.utils.fromUuid(uuid); + if (source.type === 'armor') { + await source.update({ + 'system.armor.current': source.system.armor.current + value + }); + } else { + const effectValue = source.system.armorChange.value; + await source.update({ + 'system.changes': [ + { + ...source.system.armorChange, + value: { ...effectValue, current: effectValue.current + value } + } + ] + }); + } + } + get sheetLists() { const ancestryFeatures = [], communityFeatures = [], @@ -588,6 +683,10 @@ export default class DhCharacter extends DhCreature { prepareBaseData() { super.prepareBaseData(); + this.armorScore = { + max: this.armor?.system.armor.max ?? 0, + value: this.armor?.system.armor.current ?? 0 + }; this.evasion += this.class.value?.system?.evasion ?? 0; const currentLevel = this.levelData.level.current; @@ -637,14 +736,12 @@ export default class DhCharacter extends DhCreature { } } - const armor = this.armor; - this.armorScore = armor ? armor.system.baseScore : 0; this.damageThresholds = { - major: armor - ? armor.system.baseThresholds.major + this.levelData.level.current + major: this.armor + ? this.armor.system.baseThresholds.major + this.levelData.level.current : this.levelData.level.current, - severe: armor - ? armor.system.baseThresholds.severe + this.levelData.level.current + severe: this.armor + ? this.armor.system.baseThresholds.severe + this.levelData.level.current : this.levelData.level.current * 2 }; @@ -679,9 +776,8 @@ export default class DhCharacter extends DhCreature { this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait; this.resources.armor = { + ...this.armorScore, label: 'DAGGERHEART.GENERAL.armor', - value: this.armor?.system?.marks?.value ?? 0, - max: this.armorScore, isReversed: true }; diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 264d7f93..b0e4847f 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -19,7 +19,14 @@ export default class DHArmor extends AttachableItem { ...super.defineSchema(), tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }), equipped: new fields.BooleanField({ initial: false }), - baseScore: new fields.NumberField({ integer: true, initial: 0 }), + armor: new fields.SchemaField({ + current: new fields.NumberField({ integer: true, min: 0, initial: 0 }), + max: new fields.NumberField({ required: true, integer: true, initial: 0 }) + }), + baseThresholds: new fields.SchemaField({ + major: new fields.NumberField({ integer: true, initial: 0 }), + severe: new fields.NumberField({ integer: true, initial: 0 }) + }), armorFeatures: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ @@ -28,14 +35,7 @@ export default class DHArmor extends AttachableItem { effectIds: new fields.ArrayField(new fields.StringField({ required: true })), actionIds: new fields.ArrayField(new fields.StringField({ required: true })) }) - ), - marks: new fields.SchemaField({ - value: new fields.NumberField({ initial: 0, integer: true }) - }), - baseThresholds: new fields.SchemaField({ - major: new fields.NumberField({ integer: true, initial: 0 }), - severe: new fields.NumberField({ integer: true, initial: 0 }) - }) + ) }; } @@ -151,13 +151,20 @@ export default class DHArmor extends AttachableItem { } } + /** @inheritDoc */ + static migrateDocumentData(source) { + if (!source.system.armor) { + source.system.armor = { current: source.system.marks?.value ?? 0, max: source.system.baseScore ?? 0 }; + } + } + /** * Generates a list of localized tags based on this item's type-specific properties. * @returns {string[]} An array of localized tag strings. */ _getTags() { const tags = [ - `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`, + `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`, `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}` ]; @@ -169,9 +176,7 @@ export default class DHArmor extends AttachableItem { * @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects. */ _getLabels() { - const labels = []; - if (this.baseScore) - labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`); + const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`]; return labels; } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 5a16927a..21a11149 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -222,9 +222,14 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); const armorChanged = - changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value; + changed.system?.armor?.current !== undefined && changed.system.armor.current !== this.armor.current; if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') { - const armorData = getScrollTextData(this.parent.parent, changed.system.marks, 'armor'); + const armorChangeValue = changed.system.armor.current - this.armor.current; + const armorData = getScrollTextData( + this.parent.parent, + { value: armorChangeValue + this.parent.parent.system.armorScore.value }, + 'armor' + ); options.scrollingTextData = [armorData]; } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index f8b19a3a..07c68518 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -1,5 +1,5 @@ import { itemAbleRollParse } from '../helpers/utils.mjs'; -import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs'; +import { RefreshType } from '../systemRegistration/socket.mjs'; export default class DhActiveEffect extends foundry.documents.ActiveEffect { /* -------------------------------------------- */ @@ -8,6 +8,8 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /**@override */ get isSuppressed() { + if (this.system.isSuppressed === true) return true; + // If this is a copied effect from an attachment, never suppress it // (These effects have attachmentSource metadata) if (this.flags?.daggerheart?.attachmentSource) { @@ -15,7 +17,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { } // Then apply the standard suppression rules - if (['weapon', 'armor'].includes(this.parent?.type)) { + if (['weapon', 'armor'].includes(this.parent?.type) && this.transfer) { return !this.parent.system.equipped; } @@ -76,7 +78,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { throw new Error('The array of sub-types to restrict to must not be empty.'); } - const creatableEffects = ['base']; + const creatableEffects = types || ['base']; const documentTypes = this.TYPES.filter(type => creatableEffects.includes(type)).map(type => { const labelKey = `TYPES.ActiveEffect.${type}`; const label = game.i18n.has(labelKey) ? game.i18n.localize(labelKey) : type; @@ -161,9 +163,9 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { super.applyChangeField(model, change, field); } - _applyLegacy(actor, change, changes) { + static _applyChangeUnguided(actor, change, changes, options) { change.value = DhActiveEffect.getChangeValue(actor, change, change.effect); - super._applyLegacy(actor, change, changes); + super._applyChangeUnguided(actor, change, changes, options); } static getChangeValue(model, change, effect) { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 6c434007..023beaa0 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -598,8 +598,7 @@ export default class DhpActor extends Actor { const availableStress = this.system.resources.stress.max - this.system.resources.stress.value; const canUseArmor = - this.system.armor && - this.system.armor.system.marks.value < this.system.armorScore && + this.system.armorScore.value < this.system.armorScore.max && type.every(t => this.system.armorApplicableDamageTypes[t] === true); const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => { const rule = stressDamageReduction[x]; @@ -639,12 +638,7 @@ export default class DhpActor extends Actor { const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); if (hpDamage?.value) { hpDamage.value = this.convertDamageToThreshold(hpDamage.value); - if ( - this.type === 'character' && - !isDirect && - this.system.armor && - this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes) - ) { + if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) { const armorSlotResult = await this.owner.query( 'armorSlot', { @@ -657,12 +651,10 @@ export default class DhpActor extends Actor { } ); if (armorSlotResult) { - const { modifiedDamage, armorSpent, stressSpent } = armorSlotResult; + const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult; updates.find(u => u.key === 'hitPoints').value = modifiedDamage; - if (armorSpent) { - const armorUpdate = updates.find(u => u.key === 'armor'); - if (armorUpdate) armorUpdate.value += armorSpent; - else updates.push({ value: armorSpent, key: 'armor' }); + for (const armorChange of armorChanges) { + updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid }); } if (stressSpent) { const stressUpdate = updates.find(u => u.key === 'stress'); @@ -809,12 +801,8 @@ export default class DhpActor extends Actor { ); break; case 'armor': - if (this.system.armor?.system?.marks) { - updates.armor.resources['system.marks.value'] = Math.max( - Math.min(valueFunc(this.system.armor.system.marks, r), this.system.armorScore), - 0 - ); - } + if (!r.uuid) this.system.updateArmorValue(r); + else this.system.updateArmorEffectValue(r); break; default: if (this.system.resources?.[r.key]) { @@ -1030,4 +1018,20 @@ export default class DhpActor extends Actor { return allTokens; } + + /**@inheritdoc */ + *allApplicableEffects({ noSelfArmor, noTransferArmor } = {}) { + for (const effect of this.effects) { + if (!noSelfArmor || effect.type !== 'armor') yield effect; + } + for (const item of this.items) { + for (const effect of item.effects) { + if (effect.transfer && (!noTransferArmor || effect.type !== 'armor')) yield effect; + } + } + } + + applyActiveEffects(phase) { + super.applyActiveEffects(phase); + } } diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 67f7d253..56048a81 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -230,4 +230,14 @@ export default class DHItem extends foundry.documents.Item { async _preDelete() { this.deleteTriggers(); } + + /** @inheritDoc */ + static migrateData(source) { + const documentClass = game.system.api.data.items[`DH${source.type?.capitalize()}`]; + if (documentClass?.migrateDocumentData) { + documentClass.migrateDocumentData(source); + } + + return super.migrateData(source); + } } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 59ebbbb3..9038bb08 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -743,3 +743,67 @@ export function getUnusedDamageTypes(parts) { return acc; }, []); } + +/** Returns resolved armor sources ordered by application order */ +export function getArmorSources(actor) { + const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData); + if (actor.system.armor) rawArmorSources.push(actor.system.armor); + + const data = rawArmorSources.map(doc => { + // Get the origin item. Since the actor is already loaded, it should already be cached + // Consider the relative function versions if this causes an issue + const isItem = doc instanceof Item; + const origin = isItem ? doc : doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc.parent; + return { + origin, + name: origin.name, + document: doc, + data: doc.system.armor ?? doc.system.armorData, + disabled: !!doc.disabled || !!doc.isSuppressed + }; + }); + + return sortBy(data, ({ origin }) => { + switch (origin?.type) { + case 'class': + case 'subclass': + case 'ancestry': + case 'community': + case 'feature': + case 'domainCard': + return 2; + case 'loot': + case 'consumable': + return 3; + case 'character': + return 4; + case 'weapon': + return 5; + case 'armor': + return 6; + default: + return 1; + } + }); +} + +/** + * Returns an array sorted by a function that returns a thing to compare, or an array to compare in order + * Similar to lodash's sortBy function. + */ +export function sortBy(arr, fn) { + const directCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0); + const cmp = (a, b) => { + const resultA = fn(a); + const resultB = fn(b); + if (Array.isArray(resultA) && Array.isArray(resultB)) { + for (let idx = 0; idx < Math.min(resultA.length, resultB.length); idx++) { + const result = directCompare(resultA[idx], resultB[idx]); + if (result !== 0) return result; + } + return 0; + } + return directCompare(resultA, resultB); + }; + return arr.sort(cmp); +} diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 4ad98b06..36df8b54 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -48,6 +48,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', 'systems/daggerheart/templates/scene/dh-config.hbs', - 'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs' + 'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs', + 'systems/daggerheart/templates/sheets/activeEffect/typeChanges/armorChange.hbs' ]); }; diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index f8795386..458ee6ef 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -246,6 +246,101 @@ export async function runMigrations() { lastMigrationVersion = '1.6.0'; } + + if (foundry.utils.isNewerVersion('2.0.0', lastMigrationVersion)) { + const progress = game.system.api.applications.ui.DhProgress.createMigrationProgress(0); + const progressBuffer = 50; + + //#region Data Setup + const lockedPacks = []; + const itemPacks = game.packs.filter(x => x.metadata.type === 'Item'); + const actorPacks = game.packs.filter(x => x.metadata.type === 'Actor'); + + const getIndexes = async (packs, type) => { + const indexes = []; + for (const pack of packs) { + const indexValues = pack.index.values().reduce((acc, index) => { + if (!type || index.type === type) acc.push(index.uuid); + return acc; + }, []); + + if (indexValues.length && pack.locked) { + lockedPacks.push(pack.collection); + await pack.configure({ locked: false }); + } + + indexes.push(...indexValues); + } + + return indexes; + }; + + const itemEntries = await getIndexes(itemPacks); + const characterEntries = await getIndexes(actorPacks, 'character'); + + const worldItems = game.items; + const worldCharacters = game.actors.filter(x => x.type === 'character'); + + /* The async fetches are the mainstay of time. Leaving 1 progress for the sync logic */ + const newMax = itemEntries.length + characterEntries.length + progressBuffer; + progress.updateMax(newMax); + + const compendiumItems = []; + for (const entry of itemEntries) { + const item = await foundry.utils.fromUuid(entry); + compendiumItems.push(item); + progress.advance(); + } + + const compendiumCharacters = []; + for (const entry of characterEntries) { + const character = await foundry.utils.fromUuid(entry); + compendiumCharacters.push(character); + progress.advance(); + } + //#endregion + + /* Migrate existing effects modifying armor, creating new Armor Effects instead */ + const migrateEffects = async entity => { + for (const effect of entity.effects) { + if (effect.system.changes.every(x => x.key !== 'system.armorScore')) continue; + + effect.update({ + 'system.changes': effect.system.changes.map(change => ({ + ...change, + type: change.key === 'system.armorScore' ? 'armor' : change.type, + value: change.key === 'system.armorScore' ? { current: 0, max: change.value } : change.value + })) + }); + } + }; + + /* Migrate existing armors effects */ + const migrateItems = async items => { + for (const item of items) { + await migrateEffects(item); + } + }; + + await migrateItems([...compendiumItems, ...worldItems]); + progress.advance({ by: progressBuffer / 2 }); + + for (const actor of [...compendiumCharacters, ...worldCharacters]) { + await migrateEffects(actor); + await migrateItems(actor.items); + } + + progress.advance({ by: progressBuffer / 2 }); + + for (let packId of lockedPacks) { + const pack = game.packs.get(packId); + await pack.configure({ locked: true }); + } + + progress.close(); + + lastMigrationVersion = '2.0.0'; + } //#endregion await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion); diff --git a/src/packs/domains/domainCard_Armorer_cy8GjBPGc9w9RaGO.json b/src/packs/domains/domainCard_Armorer_cy8GjBPGc9w9RaGO.json index 059fb24c..cad6012e 100644 --- a/src/packs/domains/domainCard_Armorer_cy8GjBPGc9w9RaGO.json +++ b/src/packs/domains/domainCard_Armorer_cy8GjBPGc9w9RaGO.json @@ -92,34 +92,28 @@ "name": "Armorer", "type": "base", "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "1", + "interaction": "active" + } + } + ] }, - "_id": "cED730OjuMW5haJR", + "_id": "tJw2JIPcT9hEMRXg", "img": "icons/tools/hand/hammer-and-nail.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      While you’re wearing armor, gain a +1 bonus to your Armor Score.

      ", + "description": "

      While you’re wearing armor, gain a +1 bonus to your Armor Score.

      ", "origin": null, "tint": "#ffffff", "transfer": true, @@ -129,7 +123,10 @@ "_stats": { "compendiumSource": null }, - "_key": "!items.effects!cy8GjBPGc9w9RaGO.cED730OjuMW5haJR" + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!cy8GjBPGc9w9RaGO.tJw2JIPcT9hEMRXg" } ], "ownership": { diff --git a/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json b/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json index 3b1ea76a..098f5f4c 100644 --- a/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json +++ b/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json @@ -19,7 +19,52 @@ } }, "flags": {}, - "effects": [], + "effects": [ + { + "name": "Bare Bones", + "type": "base", + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "3 + @system.traits.strength.value", + "interaction": "inactive", + "damageThresholds": { + "major": "9 + (@tier - 1) * 5 + max(0, (@tier -2) * 2 )", + "severe": "19 + (@tier - 1) * 5 + max(0, (@tier -2) * 2 )" + } + } + } + ] + }, + "_id": "FCsgz7Tdsw6QUzBs", + "img": "icons/magic/control/buff-strength-muscle-damage-orange.webp", + "disabled": false, + "start": null, + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "description": "

      You have a base Armor Score of 3 + your Strength.

      ", + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "showIcon": 1, + "folder": null, + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "_key": "!items.effects!l5D9kq901JDESaXw.FCsgz7Tdsw6QUzBs" + } + ], "ownership": { "default": 0, "MQSznptE5yLT7kj8": 3 diff --git a/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json b/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json index 7977e56a..fa247c89 100644 --- a/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json +++ b/src/packs/domains/domainCard_Book_of_Ava_YtZzYBtR0yLPPA93.json @@ -105,7 +105,7 @@ }, "effects": [ { - "_id": "LdcT1nrkd5ORCU4n", + "_id": "ptYT10JZ2WJHvFMd", "onSave": false } ], @@ -252,7 +252,7 @@ "img": "icons/magic/defensive/shield-barrier-glowing-triangle-blue.webp", "origin": "Compendium.daggerheart.domains.Item.YtZzYBtR0yLPPA93", "transfer": false, - "_id": "LdcT1nrkd5ORCU4n", + "_id": "ptYT10JZ2WJHvFMd", "type": "base", "system": { "rangeDependence": { @@ -263,10 +263,12 @@ }, "changes": [ { - "key": "system.armorScore", - "value": 1, - "priority": null, - "type": "add" + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "1" + } } ], "duration": { @@ -298,7 +300,7 @@ }, "showIcon": 1, "folder": null, - "_key": "!items.effects!YtZzYBtR0yLPPA93.LdcT1nrkd5ORCU4n" + "_key": "!items.effects!YtZzYBtR0yLPPA93.ptYT10JZ2WJHvFMd" } ], "ownership": { diff --git a/src/packs/domains/domainCard_Valor_Touched_k1AtYd3lSchIymBr.json b/src/packs/domains/domainCard_Valor_Touched_k1AtYd3lSchIymBr.json index 84ef1025..61c7ace8 100644 --- a/src/packs/domains/domainCard_Valor_Touched_k1AtYd3lSchIymBr.json +++ b/src/packs/domains/domainCard_Valor_Touched_k1AtYd3lSchIymBr.json @@ -93,32 +93,25 @@ "name": "Valor-Touched", "type": "base", "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "1" + } + } + ] }, - "_id": "H9lgIqqp1imSNOv9", + "_id": "Ma8Zp005QYKPWIEN", "img": "icons/magic/control/control-influence-rally-purple.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "
      • +1 bonus to your Armor Score

      • When you mark 1 or more Hit Points without marking an Armor Slot, clear an Armor Slot.

      ", "origin": null, @@ -130,7 +123,10 @@ "_stats": { "compendiumSource": null }, - "_key": "!items.effects!k1AtYd3lSchIymBr.H9lgIqqp1imSNOv9" + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!k1AtYd3lSchIymBr.Ma8Zp005QYKPWIEN" } ], "ownership": { diff --git a/src/packs/items/armors/armor_Advanced_Chainmail_Armor_LzLOJ9EVaHWAjoq9.json b/src/packs/items/armors/armor_Advanced_Chainmail_Armor_LzLOJ9EVaHWAjoq9.json index 174f20c8..4566396a 100644 --- a/src/packs/items/armors/armor_Advanced_Chainmail_Armor_LzLOJ9EVaHWAjoq9.json +++ b/src/packs/items/armors/armor_Advanced_Chainmail_Armor_LzLOJ9EVaHWAjoq9.json @@ -5,6 +5,10 @@ "_id": "LzLOJ9EVaHWAjoq9", "img": "icons/equipment/chest/breastplate-banded-steel-gold.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Advanced_Full_Plate_Armor_crIbCb9NZ4K0VpoU.json b/src/packs/items/armors/armor_Advanced_Full_Plate_Armor_crIbCb9NZ4K0VpoU.json index dbc9d29f..52adc7aa 100644 --- a/src/packs/items/armors/armor_Advanced_Full_Plate_Armor_crIbCb9NZ4K0VpoU.json +++ b/src/packs/items/armors/armor_Advanced_Full_Plate_Armor_crIbCb9NZ4K0VpoU.json @@ -5,6 +5,10 @@ "_id": "crIbCb9NZ4K0VpoU", "img": "icons/equipment/chest/breastplate-layered-steel-grey.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Advanced_Gambeson_Armor_epkAmlZVk7HOfUUT.json b/src/packs/items/armors/armor_Advanced_Gambeson_Armor_epkAmlZVk7HOfUUT.json index c9ffc8a3..36edec39 100644 --- a/src/packs/items/armors/armor_Advanced_Gambeson_Armor_epkAmlZVk7HOfUUT.json +++ b/src/packs/items/armors/armor_Advanced_Gambeson_Armor_epkAmlZVk7HOfUUT.json @@ -5,6 +5,10 @@ "_id": "epkAmlZVk7HOfUUT", "img": "icons/equipment/chest/breastplate-purple.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Advanced_Leather_Armor_itSOp2GCyem0f7oM.json b/src/packs/items/armors/armor_Advanced_Leather_Armor_itSOp2GCyem0f7oM.json index 4e1927e3..3e5dbd3b 100644 --- a/src/packs/items/armors/armor_Advanced_Leather_Armor_itSOp2GCyem0f7oM.json +++ b/src/packs/items/armors/armor_Advanced_Leather_Armor_itSOp2GCyem0f7oM.json @@ -5,6 +5,10 @@ "_id": "itSOp2GCyem0f7oM", "img": "icons/equipment/chest/breastplate-layered-leather-blue.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Bare_Bones_ITAjcigTcUw5pMCN.json b/src/packs/items/armors/armor_Bare_Bones_ITAjcigTcUw5pMCN.json deleted file mode 100644 index 5158b100..00000000 --- a/src/packs/items/armors/armor_Bare_Bones_ITAjcigTcUw5pMCN.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "folder": "tI3bfr6Sgi16Z7zm", - "name": "Bare Bones", - "type": "armor", - "_id": "ITAjcigTcUw5pMCN", - "img": "icons/magic/control/buff-strength-muscle-damage.webp", - "system": { - "description": "

      When you choose not to equip armor, you have a base Armor Score of 3 + your Strength and use the following as your base damage thresholds:

      • Tier 1: 9/19
      • Tier 2: 11/24
      • Tier 3: 13/31
      • Tier 4: 15/38
      ", - "actions": {}, - "attached": [], - "tier": 1, - "equipped": false, - "baseScore": 3, - "armorFeatures": [], - "marks": { - "value": 0 - }, - "baseThresholds": { - "major": 9, - "severe": 19 - } - }, - "effects": [ - { - "name": "Bare Bones", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "8ze88zUwdkQSKKJq", - "img": "icons/magic/control/buff-strength-muscle-damage.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "@system.traits.strength.value", - "priority": 21 - } - ], - "disabled": false, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      When you choose not to equip armor, you have a base Armor Score of 3 + your Strength and use the following as your base damage thresholds:

      • Tier 1: 9/19
      • Tier 2: 11/24
      • Tier 3: 13/31
      • Tier 4: 15/38
      ", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!ITAjcigTcUw5pMCN.8ze88zUwdkQSKKJq" - } - ], - "sort": 0, - "ownership": { - "default": 0, - "MQSznptE5yLT7kj8": 3 - }, - "flags": {}, - "_key": "!items!ITAjcigTcUw5pMCN" -} diff --git a/src/packs/items/armors/armor_Bellamoi_Fine_Armor_WuoVwZA53XRAIt6d.json b/src/packs/items/armors/armor_Bellamoi_Fine_Armor_WuoVwZA53XRAIt6d.json index ce4e35fd..c470de87 100644 --- a/src/packs/items/armors/armor_Bellamoi_Fine_Armor_WuoVwZA53XRAIt6d.json +++ b/src/packs/items/armors/armor_Bellamoi_Fine_Armor_WuoVwZA53XRAIt6d.json @@ -5,6 +5,10 @@ "_id": "WuoVwZA53XRAIt6d", "img": "icons/equipment/chest/breastplate-layered-gold.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Bladefare_Armor_mNN6pvcsS10ChrWF.json b/src/packs/items/armors/armor_Bladefare_Armor_mNN6pvcsS10ChrWF.json index 8b276d5f..4ee73939 100644 --- a/src/packs/items/armors/armor_Bladefare_Armor_mNN6pvcsS10ChrWF.json +++ b/src/packs/items/armors/armor_Bladefare_Armor_mNN6pvcsS10ChrWF.json @@ -5,6 +5,10 @@ "_id": "mNN6pvcsS10ChrWF", "img": "icons/equipment/chest/breastplate-collared-steel-grey.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Chainmail_Armor_haULhuEg37zUUvhb.json b/src/packs/items/armors/armor_Chainmail_Armor_haULhuEg37zUUvhb.json index f7526e96..4f0719a7 100644 --- a/src/packs/items/armors/armor_Chainmail_Armor_haULhuEg37zUUvhb.json +++ b/src/packs/items/armors/armor_Chainmail_Armor_haULhuEg37zUUvhb.json @@ -5,6 +5,10 @@ "_id": "haULhuEg37zUUvhb", "img": "icons/equipment/chest/breastplate-scale-grey.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Channeling_Armor_vMJxEWz1srfwMsoj.json b/src/packs/items/armors/armor_Channeling_Armor_vMJxEWz1srfwMsoj.json index a4bd0fea..e805d5d1 100644 --- a/src/packs/items/armors/armor_Channeling_Armor_vMJxEWz1srfwMsoj.json +++ b/src/packs/items/armors/armor_Channeling_Armor_vMJxEWz1srfwMsoj.json @@ -5,6 +5,10 @@ "_id": "vMJxEWz1srfwMsoj", "img": "icons/equipment/chest/robe-collared-blue.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Dragonscale_Armor_mdQ69eFHyAQUDmE7.json b/src/packs/items/armors/armor_Dragonscale_Armor_mdQ69eFHyAQUDmE7.json index 5b39e41d..4b270234 100644 --- a/src/packs/items/armors/armor_Dragonscale_Armor_mdQ69eFHyAQUDmE7.json +++ b/src/packs/items/armors/armor_Dragonscale_Armor_mdQ69eFHyAQUDmE7.json @@ -5,6 +5,10 @@ "_id": "mdQ69eFHyAQUDmE7", "img": "icons/equipment/chest/breastplate-rivited-red.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": { "J1MCpcfXByKaSSgx": { diff --git a/src/packs/items/armors/armor_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json b/src/packs/items/armors/armor_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json index 588d9cf9..6d223ada 100644 --- a/src/packs/items/armors/armor_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json +++ b/src/packs/items/armors/armor_Dunamis_Silkchain_hAY6UgdGT7dj22Pr.json @@ -5,6 +5,10 @@ "_id": "hAY6UgdGT7dj22Pr", "img": "icons/equipment/chest/robe-layered-red.webp", "system": { + "armor": { + "current": 0, + "max": 7 + }, "description": "", "actions": { "8PD5JQuS05IA6HJT": { diff --git a/src/packs/items/armors/armor_Elundrian_Chain_Armor_Q6LxmtFetDDkoZVZ.json b/src/packs/items/armors/armor_Elundrian_Chain_Armor_Q6LxmtFetDDkoZVZ.json index d63ce4df..1cf74e2e 100644 --- a/src/packs/items/armors/armor_Elundrian_Chain_Armor_Q6LxmtFetDDkoZVZ.json +++ b/src/packs/items/armors/armor_Elundrian_Chain_Armor_Q6LxmtFetDDkoZVZ.json @@ -5,6 +5,10 @@ "_id": "Q6LxmtFetDDkoZVZ", "img": "icons/equipment/chest/breastplate-sculpted-green.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json b/src/packs/items/armors/armor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json index 15b07474..616bbadf 100644 --- a/src/packs/items/armors/armor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json +++ b/src/packs/items/armors/armor_Emberwoven_Armor_bcQUh4QG3qFX0Vx6.json @@ -5,6 +5,10 @@ "_id": "bcQUh4QG3qFX0Vx6", "img": "icons/equipment/chest/breastplate-layered-gilded-orange.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": { "L8mHf4A8SylyxsMH": { diff --git a/src/packs/items/armors/armor_Full_Fortified_Armor_7emTSt6nhZuTlvt5.json b/src/packs/items/armors/armor_Full_Fortified_Armor_7emTSt6nhZuTlvt5.json index 8eb964cc..9f2d7ece 100644 --- a/src/packs/items/armors/armor_Full_Fortified_Armor_7emTSt6nhZuTlvt5.json +++ b/src/packs/items/armors/armor_Full_Fortified_Armor_7emTSt6nhZuTlvt5.json @@ -5,6 +5,10 @@ "_id": "7emTSt6nhZuTlvt5", "img": "icons/equipment/chest/breastplate-layered-steel.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Full_Plate_Armor_UdUJNa31WxFW2noa.json b/src/packs/items/armors/armor_Full_Plate_Armor_UdUJNa31WxFW2noa.json index 1ea120ed..7701d063 100644 --- a/src/packs/items/armors/armor_Full_Plate_Armor_UdUJNa31WxFW2noa.json +++ b/src/packs/items/armors/armor_Full_Plate_Armor_UdUJNa31WxFW2noa.json @@ -5,6 +5,10 @@ "_id": "UdUJNa31WxFW2noa", "img": "icons/equipment/chest/breastplate-collared-steel.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Gambeson_Armor_yJFp1bfpecDcStVK.json b/src/packs/items/armors/armor_Gambeson_Armor_yJFp1bfpecDcStVK.json index 1c775402..0ede5b60 100644 --- a/src/packs/items/armors/armor_Gambeson_Armor_yJFp1bfpecDcStVK.json +++ b/src/packs/items/armors/armor_Gambeson_Armor_yJFp1bfpecDcStVK.json @@ -5,6 +5,10 @@ "_id": "yJFp1bfpecDcStVK", "img": "icons/equipment/chest/vest-leather-tattered-white.webp", "system": { + "armor": { + "current": 0, + "max": 3 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json b/src/packs/items/armors/armor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json index 82b4f791..85ad1d6a 100644 --- a/src/packs/items/armors/armor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json +++ b/src/packs/items/armors/armor_Harrowbone_Armor_dvyQeUVRLc9y6rnt.json @@ -5,6 +5,10 @@ "_id": "dvyQeUVRLc9y6rnt", "img": "icons/equipment/chest/breastplate-gorget-steel.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": { "IzM88FIxQ35P5VB2": { diff --git a/src/packs/items/armors/armor_Improved_Chainmail_Armor_K5WkjS0NGqHYmhU3.json b/src/packs/items/armors/armor_Improved_Chainmail_Armor_K5WkjS0NGqHYmhU3.json index 96e320d1..ef93ecdd 100644 --- a/src/packs/items/armors/armor_Improved_Chainmail_Armor_K5WkjS0NGqHYmhU3.json +++ b/src/packs/items/armors/armor_Improved_Chainmail_Armor_K5WkjS0NGqHYmhU3.json @@ -5,6 +5,10 @@ "_id": "K5WkjS0NGqHYmhU3", "img": "icons/equipment/chest/breastplate-metal-scaled-grey.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Improved_Full_Plate_Armor_9f7RozpPTqrzJS1m.json b/src/packs/items/armors/armor_Improved_Full_Plate_Armor_9f7RozpPTqrzJS1m.json index ee63a774..1723c53a 100644 --- a/src/packs/items/armors/armor_Improved_Full_Plate_Armor_9f7RozpPTqrzJS1m.json +++ b/src/packs/items/armors/armor_Improved_Full_Plate_Armor_9f7RozpPTqrzJS1m.json @@ -5,6 +5,10 @@ "_id": "9f7RozpPTqrzJS1m", "img": "icons/equipment/chest/breastplate-cuirass-steel-grey.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Improved_Gambeson_Armor_jphnMZjnS2FkOH3s.json b/src/packs/items/armors/armor_Improved_Gambeson_Armor_jphnMZjnS2FkOH3s.json index 6f4ea1c3..a2ff6554 100644 --- a/src/packs/items/armors/armor_Improved_Gambeson_Armor_jphnMZjnS2FkOH3s.json +++ b/src/packs/items/armors/armor_Improved_Gambeson_Armor_jphnMZjnS2FkOH3s.json @@ -5,6 +5,10 @@ "_id": "jphnMZjnS2FkOH3s", "img": "icons/equipment/chest/breastplate-quilted-brown.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Improved_Leather_Armor_t91M61pSCMKStTNt.json b/src/packs/items/armors/armor_Improved_Leather_Armor_t91M61pSCMKStTNt.json index a4f38cc6..efa3643d 100644 --- a/src/packs/items/armors/armor_Improved_Leather_Armor_t91M61pSCMKStTNt.json +++ b/src/packs/items/armors/armor_Improved_Leather_Armor_t91M61pSCMKStTNt.json @@ -5,6 +5,10 @@ "_id": "t91M61pSCMKStTNt", "img": "icons/equipment/chest/breastplate-banded-simple-leather-brown.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Irontree_Breastplate_Armor_tzZntboNtHL5C6VM.json b/src/packs/items/armors/armor_Irontree_Breastplate_Armor_tzZntboNtHL5C6VM.json index a9e9eaca..a664ad9c 100644 --- a/src/packs/items/armors/armor_Irontree_Breastplate_Armor_tzZntboNtHL5C6VM.json +++ b/src/packs/items/armors/armor_Irontree_Breastplate_Armor_tzZntboNtHL5C6VM.json @@ -5,6 +5,10 @@ "_id": "tzZntboNtHL5C6VM", "img": "icons/equipment/chest/breastplate-layered-leather-brown-silver.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Leather_Armor_nibfdNtp2PtxvbVz.json b/src/packs/items/armors/armor_Leather_Armor_nibfdNtp2PtxvbVz.json index 37a13f2b..93d8f0cc 100644 --- a/src/packs/items/armors/armor_Leather_Armor_nibfdNtp2PtxvbVz.json +++ b/src/packs/items/armors/armor_Leather_Armor_nibfdNtp2PtxvbVz.json @@ -5,6 +5,10 @@ "_id": "nibfdNtp2PtxvbVz", "img": "icons/equipment/chest/breastplate-layered-leather-brown.webp", "system": { + "armor": { + "current": 0, + "max": 3 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Legendary_Chainmail_Armor_EsIN5OLKe9ZYFNXZ.json b/src/packs/items/armors/armor_Legendary_Chainmail_Armor_EsIN5OLKe9ZYFNXZ.json index 4bee5e4f..6c93cbe4 100644 --- a/src/packs/items/armors/armor_Legendary_Chainmail_Armor_EsIN5OLKe9ZYFNXZ.json +++ b/src/packs/items/armors/armor_Legendary_Chainmail_Armor_EsIN5OLKe9ZYFNXZ.json @@ -5,6 +5,10 @@ "_id": "EsIN5OLKe9ZYFNXZ", "img": "icons/equipment/chest/breastplate-banded-blue.webp", "system": { + "armor": { + "current": 0, + "max": 7 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Legendary_Full_Plate_Armor_SXWjUR2aUR6bYvdl.json b/src/packs/items/armors/armor_Legendary_Full_Plate_Armor_SXWjUR2aUR6bYvdl.json index baf544c2..f66e4c38 100644 --- a/src/packs/items/armors/armor_Legendary_Full_Plate_Armor_SXWjUR2aUR6bYvdl.json +++ b/src/packs/items/armors/armor_Legendary_Full_Plate_Armor_SXWjUR2aUR6bYvdl.json @@ -5,6 +5,10 @@ "_id": "SXWjUR2aUR6bYvdl", "img": "icons/equipment/chest/breastplate-layered-steel-blue-gold.webp", "system": { + "armor": { + "current": 0, + "max": 7 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Legendary_Gambeson_Armor_c6tMXz4rPf9ioQrf.json b/src/packs/items/armors/armor_Legendary_Gambeson_Armor_c6tMXz4rPf9ioQrf.json index 338c85e8..4cf1c856 100644 --- a/src/packs/items/armors/armor_Legendary_Gambeson_Armor_c6tMXz4rPf9ioQrf.json +++ b/src/packs/items/armors/armor_Legendary_Gambeson_Armor_c6tMXz4rPf9ioQrf.json @@ -5,6 +5,10 @@ "_id": "c6tMXz4rPf9ioQrf", "img": "icons/equipment/chest/breastplate-layered-leather-blue-gold.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Legendary_Leather_Armor_Tptgl5WOj76TyFn7.json b/src/packs/items/armors/armor_Legendary_Leather_Armor_Tptgl5WOj76TyFn7.json index 42334dc4..3ddc5ed7 100644 --- a/src/packs/items/armors/armor_Legendary_Leather_Armor_Tptgl5WOj76TyFn7.json +++ b/src/packs/items/armors/armor_Legendary_Leather_Armor_Tptgl5WOj76TyFn7.json @@ -5,6 +5,10 @@ "_id": "Tptgl5WOj76TyFn7", "img": "icons/equipment/chest/breastplate-layered-gilded-black.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Monett_s_Cloak_AQzU2RsqS5V5bd1v.json b/src/packs/items/armors/armor_Monett_s_Cloak_AQzU2RsqS5V5bd1v.json index 9a8e1f22..6bb479a4 100644 --- a/src/packs/items/armors/armor_Monett_s_Cloak_AQzU2RsqS5V5bd1v.json +++ b/src/packs/items/armors/armor_Monett_s_Cloak_AQzU2RsqS5V5bd1v.json @@ -5,6 +5,10 @@ "_id": "AQzU2RsqS5V5bd1v", "img": "icons/equipment/chest/coat-collared-red.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Rosewild_Armor_tN8kAeBvNKM3EBFo.json b/src/packs/items/armors/armor_Rosewild_Armor_tN8kAeBvNKM3EBFo.json index 0f0f6430..e3cde9fb 100644 --- a/src/packs/items/armors/armor_Rosewild_Armor_tN8kAeBvNKM3EBFo.json +++ b/src/packs/items/armors/armor_Rosewild_Armor_tN8kAeBvNKM3EBFo.json @@ -5,6 +5,10 @@ "_id": "tN8kAeBvNKM3EBFo", "img": "icons/equipment/chest/breastplate-banded-leather-purple.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": { "QRTnCYxJfuJHdnyV": { diff --git a/src/packs/items/armors/armor_Runes_of_Fortification_P4qAEDJUoNLgVRsA.json b/src/packs/items/armors/armor_Runes_of_Fortification_P4qAEDJUoNLgVRsA.json index 240a4f3e..2ccc80da 100644 --- a/src/packs/items/armors/armor_Runes_of_Fortification_P4qAEDJUoNLgVRsA.json +++ b/src/packs/items/armors/armor_Runes_of_Fortification_P4qAEDJUoNLgVRsA.json @@ -5,6 +5,10 @@ "_id": "P4qAEDJUoNLgVRsA", "img": "icons/magic/symbols/rune-sigil-red-orange.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": { "37KLF2bim9nRdPTU": { diff --git a/src/packs/items/armors/armor_Runetan_Floating_Armor_tHlBUDQC24YMZqd6.json b/src/packs/items/armors/armor_Runetan_Floating_Armor_tHlBUDQC24YMZqd6.json index 8d4af425..593bc8e0 100644 --- a/src/packs/items/armors/armor_Runetan_Floating_Armor_tHlBUDQC24YMZqd6.json +++ b/src/packs/items/armors/armor_Runetan_Floating_Armor_tHlBUDQC24YMZqd6.json @@ -5,6 +5,10 @@ "_id": "tHlBUDQC24YMZqd6", "img": "icons/equipment/chest/breastplate-layered-leather-black.webp", "system": { + "armor": { + "current": 0, + "max": 4 + }, "description": "", "actions": { "Nn33zCIcWe6LQMDH": { diff --git a/src/packs/items/armors/armor_Savior_Chainmail_8X16lJQ3xltTwynm.json b/src/packs/items/armors/armor_Savior_Chainmail_8X16lJQ3xltTwynm.json index 714e8592..6826254a 100644 --- a/src/packs/items/armors/armor_Savior_Chainmail_8X16lJQ3xltTwynm.json +++ b/src/packs/items/armors/armor_Savior_Chainmail_8X16lJQ3xltTwynm.json @@ -5,6 +5,10 @@ "_id": "8X16lJQ3xltTwynm", "img": "icons/equipment/chest/breastplate-layered-leather-green.webp", "system": { + "armor": { + "current": 0, + "max": 8 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Spiked_Plate_Armor_QjwsIhXKqnlvRBMv.json b/src/packs/items/armors/armor_Spiked_Plate_Armor_QjwsIhXKqnlvRBMv.json index cb5fc720..ac9115a2 100644 --- a/src/packs/items/armors/armor_Spiked_Plate_Armor_QjwsIhXKqnlvRBMv.json +++ b/src/packs/items/armors/armor_Spiked_Plate_Armor_QjwsIhXKqnlvRBMv.json @@ -5,6 +5,10 @@ "_id": "QjwsIhXKqnlvRBMv", "img": "icons/equipment/chest/breastplate-banded-steel-studded.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": {}, "attached": [], diff --git a/src/packs/items/armors/armor_Tyris_Soft_Armor_PSW3BxCGmtLeWOxM.json b/src/packs/items/armors/armor_Tyris_Soft_Armor_PSW3BxCGmtLeWOxM.json index da640830..c6766018 100644 --- a/src/packs/items/armors/armor_Tyris_Soft_Armor_PSW3BxCGmtLeWOxM.json +++ b/src/packs/items/armors/armor_Tyris_Soft_Armor_PSW3BxCGmtLeWOxM.json @@ -5,6 +5,10 @@ "_id": "PSW3BxCGmtLeWOxM", "img": "icons/equipment/chest/robe-layered-purple.webp", "system": { + "armor": { + "current": 0, + "max": 5 + }, "description": "", "actions": { "Ch6IhuPewBeseGez": { diff --git a/src/packs/items/armors/armor_Veritas_Opal_Armor_OvzgUTYy2RCN85vV.json b/src/packs/items/armors/armor_Veritas_Opal_Armor_OvzgUTYy2RCN85vV.json index 08a1b573..403b79e8 100644 --- a/src/packs/items/armors/armor_Veritas_Opal_Armor_OvzgUTYy2RCN85vV.json +++ b/src/packs/items/armors/armor_Veritas_Opal_Armor_OvzgUTYy2RCN85vV.json @@ -5,6 +5,10 @@ "_id": "OvzgUTYy2RCN85vV", "img": "icons/equipment/chest/breastplate-collared-steel-green.webp", "system": { + "armor": { + "current": 0, + "max": 6 + }, "description": "", "actions": { "sY3W5JYspN5Du5ag": { diff --git a/src/packs/items/armors/folders_Special_tI3bfr6Sgi16Z7zm.json b/src/packs/items/armors/folders_Special_tI3bfr6Sgi16Z7zm.json deleted file mode 100644 index 65c4eca8..00000000 --- a/src/packs/items/armors/folders_Special_tI3bfr6Sgi16Z7zm.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "Item", - "folder": null, - "name": "Special", - "color": null, - "sorting": "a", - "_id": "tI3bfr6Sgi16Z7zm", - "description": "", - "sort": 0, - "flags": {}, - "_key": "!folders!tI3bfr6Sgi16Z7zm" -} diff --git a/src/packs/items/weapons/weapon_Advanced_Round_Shield_hiEOGF2reabGLUoi.json b/src/packs/items/weapons/weapon_Advanced_Round_Shield_hiEOGF2reabGLUoi.json index da5d5bf8..54800642 100644 --- a/src/packs/items/weapons/weapon_Advanced_Round_Shield_hiEOGF2reabGLUoi.json +++ b/src/packs/items/weapons/weapon_Advanced_Round_Shield_hiEOGF2reabGLUoi.json @@ -113,26 +113,26 @@ "name": "Protective", "description": "

      Add the item's Tier to your Armor Score

      ", "img": "icons/skills/melee/shield-block-gray-orange.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier", - "priority": null - } - ], - "_id": "i5HfkF5aKQuUCTEG", + "_id": "7285CRGdZfHCEtT2", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier" + } + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -143,7 +143,10 @@ "_stats": { "compendiumSource": null }, - "_key": "!items.effects!hiEOGF2reabGLUoi.i5HfkF5aKQuUCTEG" + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!hiEOGF2reabGLUoi.7285CRGdZfHCEtT2" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Advanced_Tower_Shield_OfOzQbs4hg6QbfTG.json b/src/packs/items/weapons/weapon_Advanced_Tower_Shield_OfOzQbs4hg6QbfTG.json index 9fe47fc0..a88749a8 100644 --- a/src/packs/items/weapons/weapon_Advanced_Tower_Shield_OfOzQbs4hg6QbfTG.json +++ b/src/packs/items/weapons/weapon_Advanced_Tower_Shield_OfOzQbs4hg6QbfTG.json @@ -113,25 +113,25 @@ "name": "Barrier", "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", "img": "icons/skills/melee/shield-block-bash-blue.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier + 1" - }, - { - "key": "system.evasion", - "mode": 2, - "value": "-1" - } - ], "_id": "87gedjJZGdFY81Mt", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "key": "system.evasion", + "type": "add", + "value": -1, + "phase": "initial", + "priority": 0 + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -142,7 +142,49 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!items.effects!OfOzQbs4hg6QbfTG.87gedjJZGdFY81Mt" + }, + { + "name": "Barrier", + "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", + "img": "icons/skills/melee/shield-block-bash-blue.webp", + "_id": "J0f7zqqOr61ADpdy", + "type": "base", + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier + 1" + } + } + ] + }, + "disabled": false, + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!OfOzQbs4hg6QbfTG.J0f7zqqOr61ADpdy" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Improved_Round_Shield_DlinEBGZfIlvreO3.json b/src/packs/items/weapons/weapon_Improved_Round_Shield_DlinEBGZfIlvreO3.json index 091355c2..65868950 100644 --- a/src/packs/items/weapons/weapon_Improved_Round_Shield_DlinEBGZfIlvreO3.json +++ b/src/packs/items/weapons/weapon_Improved_Round_Shield_DlinEBGZfIlvreO3.json @@ -113,26 +113,26 @@ "name": "Protective", "description": "

      Add the item's Tier to your Armor Score

      ", "img": "icons/skills/melee/shield-block-gray-orange.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier", - "priority": null - } - ], - "_id": "cXWSV50apzaNQkdA", + "_id": "pZCrWd7zLTarvEQK", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier" + } + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -143,7 +143,10 @@ "_stats": { "compendiumSource": null }, - "_key": "!items.effects!DlinEBGZfIlvreO3.cXWSV50apzaNQkdA" + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!DlinEBGZfIlvreO3.pZCrWd7zLTarvEQK" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Improved_Tower_Shield_bxt3NsbMqTSdI5ab.json b/src/packs/items/weapons/weapon_Improved_Tower_Shield_bxt3NsbMqTSdI5ab.json index 6f55fe3d..64555dfa 100644 --- a/src/packs/items/weapons/weapon_Improved_Tower_Shield_bxt3NsbMqTSdI5ab.json +++ b/src/packs/items/weapons/weapon_Improved_Tower_Shield_bxt3NsbMqTSdI5ab.json @@ -113,25 +113,25 @@ "name": "Barrier", "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", "img": "icons/skills/melee/shield-block-bash-blue.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier + 1" - }, - { - "key": "system.evasion", - "mode": 2, - "value": "-1" - } - ], "_id": "tkNEA1PO3jEFhKCa", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "key": "system.evasion", + "type": "add", + "value": -1, + "phase": "initial", + "priority": 0 + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -142,7 +142,49 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!items.effects!bxt3NsbMqTSdI5ab.tkNEA1PO3jEFhKCa" + }, + { + "name": "Barrier", + "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", + "img": "icons/skills/melee/shield-block-bash-blue.webp", + "_id": "XugJeHJdnC6IymSa", + "type": "base", + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier + 1" + } + } + ] + }, + "disabled": false, + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!bxt3NsbMqTSdI5ab.XugJeHJdnC6IymSa" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json b/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json index 0dd7d23a..11994d3e 100644 --- a/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json +++ b/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json @@ -113,20 +113,26 @@ "name": "Protective", "description": "Add your character's Tier to your Armor Score", "img": "icons/skills/melee/shield-block-gray-orange.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier" - } - ], - "_id": "qTxADRsQnKiYfOiQ", + "_id": "vnR4Zhnb0rOqwrFw", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier" + } + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -137,7 +143,10 @@ "_stats": { "compendiumSource": null }, - "_key": "!items.effects!ijWppQzSOqVCb3rE.qTxADRsQnKiYfOiQ" + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!ijWppQzSOqVCb3rE.vnR4Zhnb0rOqwrFw" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Legendary_Round_Shield_A28WL9E2lJ3iLZHW.json b/src/packs/items/weapons/weapon_Legendary_Round_Shield_A28WL9E2lJ3iLZHW.json index 5eedb6c2..85134d21 100644 --- a/src/packs/items/weapons/weapon_Legendary_Round_Shield_A28WL9E2lJ3iLZHW.json +++ b/src/packs/items/weapons/weapon_Legendary_Round_Shield_A28WL9E2lJ3iLZHW.json @@ -113,26 +113,26 @@ "name": "Protective", "description": "

      Add the item's Tier to your Armor Score

      ", "img": "icons/skills/melee/shield-block-gray-orange.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier", - "priority": null - } - ], - "_id": "Z2p00q5h6x6seXys", + "_id": "EixxJrRHyc6kj3Wg", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier" + } + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -143,7 +143,10 @@ "_stats": { "compendiumSource": null }, - "_key": "!items.effects!A28WL9E2lJ3iLZHW.Z2p00q5h6x6seXys" + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!A28WL9E2lJ3iLZHW.EixxJrRHyc6kj3Wg" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Legendary_Tower_Shield_MaJIROht7A9LxIZx.json b/src/packs/items/weapons/weapon_Legendary_Tower_Shield_MaJIROht7A9LxIZx.json index 49ca454a..772e9ca9 100644 --- a/src/packs/items/weapons/weapon_Legendary_Tower_Shield_MaJIROht7A9LxIZx.json +++ b/src/packs/items/weapons/weapon_Legendary_Tower_Shield_MaJIROht7A9LxIZx.json @@ -113,25 +113,25 @@ "name": "Barrier", "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", "img": "icons/skills/melee/shield-block-bash-blue.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier + 1" - }, - { - "key": "system.evasion", - "mode": 2, - "value": "-1" - } - ], "_id": "lBJMzxdGO2nJdTQS", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "key": "system.evasion", + "type": "add", + "value": -1, + "phase": "initial", + "priority": 0 + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -142,7 +142,49 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!items.effects!MaJIROht7A9LxIZx.lBJMzxdGO2nJdTQS" + }, + { + "name": "Barrier", + "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", + "img": "icons/skills/melee/shield-block-bash-blue.webp", + "_id": "1fgUIaXl6VQrhP7j", + "type": "base", + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier + 1" + } + } + ] + }, + "disabled": false, + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!MaJIROht7A9LxIZx.1fgUIaXl6VQrhP7j" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Round_Shield_mxwWKDujgsRcZWPT.json b/src/packs/items/weapons/weapon_Round_Shield_mxwWKDujgsRcZWPT.json index ccba4b0e..55e92f01 100644 --- a/src/packs/items/weapons/weapon_Round_Shield_mxwWKDujgsRcZWPT.json +++ b/src/packs/items/weapons/weapon_Round_Shield_mxwWKDujgsRcZWPT.json @@ -113,26 +113,26 @@ "name": "Protective", "description": "

      Add the item's Tier to your Armor Score

      ", "img": "icons/skills/melee/shield-block-gray-orange.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier", - "priority": null - } - ], - "_id": "M70a81e0Mg66jHRL", + "_id": "eV4lFIpQMiKERj4U", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier" + } + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -143,7 +143,10 @@ "_stats": { "compendiumSource": null }, - "_key": "!items.effects!mxwWKDujgsRcZWPT.M70a81e0Mg66jHRL" + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!mxwWKDujgsRcZWPT.eV4lFIpQMiKERj4U" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json b/src/packs/items/weapons/weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json index 2dd08026..39c18b08 100644 --- a/src/packs/items/weapons/weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json +++ b/src/packs/items/weapons/weapon_Spiked_Shield_vzyzFwLUniWZV1rt.json @@ -113,32 +113,31 @@ "name": "Double Duty", "description": "+1 to Armor Score; +1 to primary weapon damage within Melee range", "img": "icons/skills/melee/sword-shield-stylized-white.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "1" - }, - { - "key": "system.bonuses.damage.primaryWeapon.bonus", - "mode": 2, - "value": "1" - } - ], "system": { "rangeDependence": { "enabled": true, "range": "melee", "target": "hostile", "type": "withinRange" - } + }, + "changes": [ + { + "key": "system.bonuses.damage.primaryWeapon.bonus", + "type": "add", + "value": 1, + "phase": "initial", + "priority": 0 + } + ] }, "_id": "d3TJtlpoHBCztbom", "type": "base", "disabled": false, "duration": { - "startTime": null, - "combat": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -149,7 +148,49 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!items.effects!vzyzFwLUniWZV1rt.d3TJtlpoHBCztbom" + }, + { + "name": "Double Duty", + "description": "+1 to Armor Score; +1 to primary weapon damage within Melee range", + "img": "icons/skills/melee/sword-shield-stylized-white.webp", + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "1" + } + } + ] + }, + "_id": "mvUY9LGfwICak7cE", + "type": "base", + "disabled": false, + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!vzyzFwLUniWZV1rt.mvUY9LGfwICak7cE" } ], "sort": 0, diff --git a/src/packs/items/weapons/weapon_Tower_Shield_C9aWpK1shVMWP4m5.json b/src/packs/items/weapons/weapon_Tower_Shield_C9aWpK1shVMWP4m5.json index 47043c54..e584b63c 100644 --- a/src/packs/items/weapons/weapon_Tower_Shield_C9aWpK1shVMWP4m5.json +++ b/src/packs/items/weapons/weapon_Tower_Shield_C9aWpK1shVMWP4m5.json @@ -113,25 +113,25 @@ "name": "Barrier", "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", "img": "icons/skills/melee/shield-block-bash-blue.webp", - "changes": [ - { - "key": "system.armorScore", - "mode": 2, - "value": "ITEM.@system.tier + 1" - }, - { - "key": "system.evasion", - "mode": 2, - "value": "-1" - } - ], "_id": "8r0TcKWXboFV0eqS", "type": "base", - "system": {}, + "system": { + "changes": [ + { + "key": "system.evasion", + "type": "add", + "value": -1, + "phase": "initial", + "priority": 0 + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "origin": null, "tint": "#ffffff", @@ -142,7 +142,49 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!items.effects!C9aWpK1shVMWP4m5.8r0TcKWXboFV0eqS" + }, + { + "name": "Barrier", + "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", + "img": "icons/skills/melee/shield-block-bash-blue.webp", + "_id": "tLRc4UAnGuIq7er3", + "type": "base", + "system": { + "changes": [ + { + "type": "armor", + "phase": "initial", + "priority": 20, + "value": { + "max": "ITEM.@system.tier + 1" + } + } + ] + }, + "disabled": false, + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "start": null, + "showIcon": 1, + "folder": null, + "_key": "!items.effects!C9aWpK1shVMWP4m5.tLRc4UAnGuIq7er3" } ], "sort": 0, diff --git a/styles/less/dialog/damage-reduction/damage-reduction-container.less b/styles/less/dialog/damage-reduction/damage-reduction-container.less index 2f343fb3..e8242bdd 100644 --- a/styles/less/dialog/damage-reduction/damage-reduction-container.less +++ b/styles/less/dialog/damage-reduction/damage-reduction-container.less @@ -35,7 +35,10 @@ display: flex; flex-direction: column; align-items: center; - width: 100%; + + &.full-width { + width: 100%; + } } .padded { @@ -45,6 +48,7 @@ .armor-title { margin: 0; white-space: nowrap; + width: 100%; } .resources-container { @@ -62,12 +66,17 @@ .mark-selection { display: flex; - align-items: center; + flex-direction: column; width: 100%; margin: 0; + h4 { + margin: 0; + } + .mark-selection-inner { display: flex; + justify-content: center; gap: 8px; .mark-container { @@ -91,6 +100,19 @@ opacity: 0.2; } + &.spent { + ::after { + position: absolute; + content: '/'; + color: red; + font-weight: 700; + font-size: 1.8em; + left: -1px; + top: -7px; + rotate: 13deg; + } + } + .fa-shield { position: relative; right: 0.5px; diff --git a/styles/less/sheets/activeEffects/activeEffects.less b/styles/less/sheets/activeEffects/activeEffects.less index ba3ff43f..3f6526cb 100644 --- a/styles/less/sheets/activeEffects/activeEffects.less +++ b/styles/less/sheets/activeEffects/activeEffects.less @@ -31,5 +31,70 @@ text-align: center; } } + + .armor-change-container { + padding-top: 0; + padding-bottom: 4px; + row-gap: 0; + + legend { + display: flex; + align-items: center; + padding-left: 3px; + } + + header { + padding: 0; + left: -0.25rem; // TODO: Find why this header is offset 0.25rem to the right so this can be removed. + } + + header, + ol { + grid-template-columns: 5rem 7rem 12rem 4rem; + } + + .damage-thresholds-container { + width: 100%; + display: flex; + flex-direction: column; + gap: 4px; + + .damage-threshold-title { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + + &::before, + &::after { + content: ''; + flex: 1; + height: 2px; + } + + &::before { + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, light-dark(@dark-blue, @golden) 100%); + } + + &::after { + background: linear-gradient(90deg, light-dark(@dark-blue, @golden) 0%, rgba(0, 0, 0, 0) 100%); + } + + span { + font-size: var(--font-size-18); + } + } + + .form-group { + flex-direction: column; + gap: 0; + + label { + color: inherit; + line-height: 16px; + } + } + } + } } } diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index 04baf2b9..bb0a43cd 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -276,6 +276,23 @@ } } + .slot-label { + .slot-value-container { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + + i { + position: absolute; + right: 0; + font-size: 12px; + color: light-dark(@beige, @dark-blue); + } + } + } + .status-value { padding: 0 5px; } @@ -298,6 +315,12 @@ border-radius: 3px; background: light-dark(@dark-blue, @golden); clip-path: none; + cursor: pointer; + display: flex; + align-items: center; + gap: 4px; + border: 1px solid transparent; + transition: all 0.3s ease; h4 { font-weight: bold; @@ -306,6 +329,20 @@ color: light-dark(@beige, @dark-blue); font-size: var(--font-size-12); } + + i { + font-size: 12px; + color: light-dark(@beige, @dark-blue); + } + + &:hover { + background: light-dark(@light-black, @dark-blue); + border: 1px solid light-dark(@dark-blue, @golden); + + h4, i { + color: light-dark(@dark-blue, @golden); + } + } } .slot-value { position: absolute; @@ -343,7 +380,7 @@ } } } - .slot-label { + .slot-label { display: flex; align-items: center; color: light-dark(@beige, @dark-blue); @@ -355,6 +392,17 @@ font-size: var(--font-size-12); flex-wrap: wrap; justify-content: center; + border: 1px solid transparent; + transition: all 0.3s ease; + + &:hover { + background: light-dark(@light-black, @dark-blue); + border: 1px solid light-dark(@dark-blue, @golden); + + .label, .value, i { + color: light-dark(@dark-blue, @golden); + } + } .label { padding-right: 1px; diff --git a/styles/less/ux/index.less b/styles/less/ux/index.less index c6c40f78..6fad3ab1 100644 --- a/styles/less/ux/index.less +++ b/styles/less/ux/index.less @@ -1,4 +1,5 @@ @import './tooltip/tooltip.less'; +@import './tooltip/armorManagement.less'; @import './tooltip/battlepoints.less'; @import './tooltip/bordered-tooltip.less'; @import './tooltip/domain-cards.less'; diff --git a/styles/less/ux/tooltip/armorManagement.less b/styles/less/ux/tooltip/armorManagement.less index bc716fa0..e1ac6bb9 100644 --- a/styles/less/ux/tooltip/armorManagement.less +++ b/styles/less/ux/tooltip/armorManagement.less @@ -1,7 +1,19 @@ +@import '../../utils/fonts.less'; +@import '../../utils/colors.less'; + .bordered-tooltip.locked-tooltip .daggerheart.armor-management-container { display: flex; flex-direction: column; - gap: 16px; + gap: 10px; + padding-bottom: 10px; + + h3 { + font-family: @font-subtitle; + margin: 0; + border: none; + font-weight: normal; + font-size: var(--font-size-20); + } .armor-source-container { display: flex; @@ -10,16 +22,16 @@ gap: 4px; .armor-source-label { - font-size: var(--font-size-24); - font-weight: bold; + font-family: @font-body; + margin: 0; } .status-bar { display: flex; justify-content: center; position: relative; - width: 80px; - height: 20px; + width: 100%; + height: 30px; .status-value { position: absolute; @@ -27,8 +39,8 @@ padding: 0 5px; font-size: 1rem; align-items: center; - width: 80px; - height: 20px; + width: 100%; + height: 30px; justify-content: center; text-align: center; z-index: 2; @@ -36,13 +48,13 @@ input[type='number'] { background: transparent; - font-size: 1rem; + font-size: 1.2rem; width: 30px; - height: 15px; text-align: center; border: none; outline: 2px solid transparent; color: @beige; + font-family: @font-body; &.bar-input { padding: 0; @@ -50,6 +62,7 @@ backdrop-filter: none; background: transparent; transition: all 0.3s ease; + height: 25px; &:hover, &:focus { @@ -60,14 +73,16 @@ } .bar-label { + font-family: @font-body; width: 40px; + font-size: 1.2rem; } } .progress-bar { position: absolute; appearance: none; - width: 80px; - height: 20px; + width: 100%; + height: 30px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; z-index: 1; @@ -97,4 +112,30 @@ } } } + + .slot-bar { + display: flex; + flex-wrap: wrap; + gap: 4px; + padding: 5px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + z-index: 1; + background: @dark-blue; + align-items: center; + justify-content: center; + color: light-dark(@dark-blue, @golden); + min-height: 30px; + width: 100%; + + .armor-slot { + cursor: pointer; + transition: all 0.3s ease; + font-size: var(--font-size-12); + + .fa-shield-halved { + color: light-dark(@dark-blue-40, @golden-40); + } + } + } } diff --git a/templates/dialogs/damageReduction.hbs b/templates/dialogs/damageReduction.hbs index 57d7ee61..50fe3422 100644 --- a/templates/dialogs/damageReduction.hbs +++ b/templates/dialogs/damageReduction.hbs @@ -7,53 +7,57 @@
      -

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.armorMarks"}}

      -
      {{armorMarks}}/{{armorScore}}
      +

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.maxUseableArmor"}}

      +
      {{availableArmor}}
      - {{#if this.stress}} -
      -

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stress"}}

      -
      {{this.stress.value}}/{{this.stress.max}}
      -
      - {{/if}}
      -
      -

      -
      - {{#each marks.armor}} -
      - -
      - {{/each}} +
      + {{#each marks.armor as |source|}} +
      +

      {{source.label}}

      +
      + {{#each source.marks}} + + + + {{/each}} +
      +
      + {{/each}} + {{#if usesStressArmor}} +
      +

      {{localize "Stress"}}

      +
      {{#each marks.stress}}
      {{/each}} +
      -

      -
      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.usedMarks"}}
      + {{/if}}
      {{#if availableStressReductions}}
      -

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stressReduction"}}

      +

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stressReduction"}}

      {{/if}} {{#each availableStressReductions}}
      -

      +

      {{#if this.any}} {{localize "DAGGERHEART.GENERAL.any"}} @@ -74,7 +78,7 @@ {{#if reduceSeverity}}
      -

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.reduceSeverity" nr=reduceSeverity}}

      +

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.reduceSeverity" nr=reduceSeverity}}

      {{/if}} @@ -82,7 +86,7 @@ {{#if thresholdImmunities}}
      -

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.thresholdImmunities"}}

      +

      {{localize "DAGGERHEART.APPLICATIONS.DamageReduction.thresholdImmunities"}}

      {{/if}} diff --git a/templates/sheets/activeEffect/change.hbs b/templates/sheets/activeEffect/change.hbs index 30f643b8..d7d396e4 100644 --- a/templates/sheets/activeEffect/change.hbs +++ b/templates/sheets/activeEffect/change.hbs @@ -3,7 +3,9 @@
      - {{formInput fields.type name=change.typePath value=change.type localize=true}} +
      {{formInput fields.value name=change.valuePath value=change.value elementType="input"}} diff --git a/templates/sheets/activeEffect/changes.hbs b/templates/sheets/activeEffect/changes.hbs index 026ffd90..47a48382 100644 --- a/templates/sheets/activeEffect/changes.hbs +++ b/templates/sheets/activeEffect/changes.hbs @@ -13,4 +13,11 @@ {{{change}}} {{/each}} + +
      + {{localize "DAGGERHEART.GENERAL.armor"}} + {{#if typedChanges.armor}} + {{> "systems/daggerheart/templates/sheets/activeEffect/typeChanges/armorChange.hbs" typedChanges.armor fields=@root.systemFields.changes.element.types.armor.fields}} + {{/if}} +
      diff --git a/templates/sheets/activeEffect/typeChanges/armorChange.hbs b/templates/sheets/activeEffect/typeChanges/armorChange.hbs new file mode 100644 index 00000000..662a1294 --- /dev/null +++ b/templates/sheets/activeEffect/typeChanges/armorChange.hbs @@ -0,0 +1,28 @@ +
      +
      {{localize "EFFECT.FIELDS.changes.element.value.label"}}
      +
      {{localize "DAGGERHEART.GENERAL.max"}}
      +
      {{localize "DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.interaction.label"}}
      +
      {{localize "EFFECT.FIELDS.changes.element.priority.label"}}
      +
      +
        +
      1. + + + {{formInput fields.value.fields.current name=(concat "system.changes." index ".value.current") value=value.current data-dtype="Number"}} + {{formInput fields.value.fields.max name=(concat "system.changes." index ".value.max") value=value.max data-dtype="Number"}} + {{formInput fields.value.fields.interaction name=(concat "system.changes." index ".value.interaction") value=value.interaction localize=true}} + {{formInput fields.priority name=(concat "system.changes." index ".priority") value=priority}} +
      2. +
      +
      +
      + {{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}} + +
      + {{#if value.damageThresholds}} +
      + {{formGroup fields.value.fields.damageThresholds.fields.major name=(concat "system.changes." index ".value.damageThresholds.major") value=value.damageThresholds.major localize=true }} + {{formGroup fields.value.fields.damageThresholds.fields.severe name=(concat "system.changes." index ".value.damageThresholds.severe") value=value.damageThresholds.severe localize=true }} +
      + {{/if}} +
      \ No newline at end of file diff --git a/templates/sheets/actors/character/effects.hbs b/templates/sheets/actors/character/effects.hbs index 30fb31f4..3355d575 100644 --- a/templates/sheets/actors/character/effects.hbs +++ b/templates/sheets/actors/character/effects.hbs @@ -18,6 +18,7 @@ collection=effects.inactives canCreate=true hideResources=true + disabled=true }}
      \ No newline at end of file diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index b2757b55..d3be4983 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -30,14 +30,14 @@

      - {{#if document.system.armor.system.marks}} + {{#if document.system.armorScore.max}} {{else}} diff --git a/templates/sheets/actors/party/resources.hbs b/templates/sheets/actors/party/resources.hbs index 74f94806..bfbfb578 100644 --- a/templates/sheets/actors/party/resources.hbs +++ b/templates/sheets/actors/party/resources.hbs @@ -52,13 +52,12 @@ - {{#if actor.system.armor.system.marks}} + {{#if actor.system.armorScore.max}} {{/if}} diff --git a/templates/sheets/items/armor/header.hbs b/templates/sheets/items/armor/header.hbs index d4306131..d8044332 100644 --- a/templates/sheets/items/armor/header.hbs +++ b/templates/sheets/items/armor/header.hbs @@ -9,7 +9,7 @@

      {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}: - {{source.system.baseScore}} + {{source.system.armor.max}} - {{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.base"}}: {{source.system.baseThresholds.major}} diff --git a/templates/sheets/items/armor/settings.hbs b/templates/sheets/items/armor/settings.hbs index e7bde6fe..51bf1746 100644 --- a/templates/sheets/items/armor/settings.hbs +++ b/templates/sheets/items/armor/settings.hbs @@ -6,9 +6,9 @@
      {{localize tabs.settings.label}} {{localize "DAGGERHEART.GENERAL.Tiers.singular"}} - {{formField systemFields.tier value=source.system.tier}} + {{ formField systemFields.tier value=source.system.tier }} {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}} - {{formField systemFields.baseScore value=source.system.baseScore}} + {{ formField systemFields.armor.fields.max value=source.system.armor.max }} {{localize "TYPES.Item.feature"}} diff --git a/templates/ui/tooltip/armorManagement.hbs b/templates/ui/tooltip/armorManagement.hbs index aa8c9878..963959c5 100644 --- a/templates/ui/tooltip/armorManagement.hbs +++ b/templates/ui/tooltip/armorManagement.hbs @@ -1,18 +1,18 @@
      +

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

      {{#each sources as |source|}}
      - - {{/each}} From de801924e637439ea6db5caf5de91c877514395c Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 22 Mar 2026 16:59:23 +0100 Subject: [PATCH 035/304] Fixed a bunch of deprecations --- module/applications/dialogs/d20RollDialog.mjs | 8 ++++---- module/applications/dialogs/damageDialog.mjs | 6 +++--- module/data/action/baseAction.mjs | 13 +++++++++++-- module/data/fields/actionField.mjs | 2 +- module/dice/damageRoll.mjs | 2 +- module/dice/dhRoll.mjs | 4 ++-- module/documents/rollTable.mjs | 2 +- templates/dialogs/dice-roll/damageSelection.hbs | 4 ++-- templates/dialogs/dice-roll/rollSelection.hbs | 4 ++-- 9 files changed, 27 insertions(+), 18 deletions(-) diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 0fdb1896..64fa168a 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -70,8 +70,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio context.rollConfig = this.config; context.hasRoll = !!this.config.roll; context.canRoll = true; - context.selectedRollMode = this.config.selectedRollMode ?? game.settings.get('core', 'rollMode'); - context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({ + context.selectedMessageMode = this.config.selectedMessageMode ?? game.settings.get('core', 'messageMode'); + context.rollModes = Object.entries(CONFIG.ChatMessage.modes).map(([action, { label, icon }]) => ({ action, label, icon @@ -142,10 +142,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio })); } - static updateRollConfiguration(event, _, formData) { + static updateRollConfiguration(_event, _, formData) { const { ...rest } = foundry.utils.expandObject(formData.object); - this.config.selectedRollMode = rest.selectedRollMode; + this.config.selectedMessageMode = rest.selectedMessageMode; if (this.config.costs) { this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs); diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index b24570cc..d00a744d 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -52,8 +52,8 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application context.formula = this.roll.constructFormula(this.config); context.hasHealing = this.config.hasHealing; context.directDamage = this.config.directDamage; - context.selectedRollMode = this.config.selectedRollMode; - context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({ + context.selectedMessageMode = this.config.selectedMessageMode; + context.rollModes = Object.entries(CONFIG.ChatMessage.modes).map(([action, { label, icon }]) => ({ action, label, icon @@ -69,7 +69,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application const { ...rest } = foundry.utils.expandObject(formData.object); foundry.utils.mergeObject(this.config.roll, rest.roll); foundry.utils.mergeObject(this.config.modifiers, rest.modifiers); - this.config.selectedRollMode = rest.selectedRollMode; + this.config.selectedMessageMode = rest.selectedMessageMode; this.render(); } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 01139b30..1f75d382 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -264,12 +264,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel hasSave: this.hasSave, onSave: this.save?.damageMod, isDirect: !!this.damage?.direct, - selectedRollMode: game.settings.get('core', 'rollMode'), + selectedMessageMode: game.settings.get('core', 'messageMode'), data: this.getRollData(), evaluate: this.hasRoll, resourceUpdates: new ResourceUpdateMap(this.actor), targetUuid: this.targetUuid, - ...configOptions + ...configOptions, + skips: { + resources: false, + triggers: false, + createMessage: false, + updateCountdowns: false, + reaction: false, + ...(configOptions.skips ?? {}) + } }; DHBaseAction.applyKeybindings(config); @@ -329,6 +337,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * @param {boolean} successCost */ async consume(config, successCost = false) { + config.resourceUpdates = new ResourceUpdateMap(config.actionActor); await this.workflow.get('cost')?.execute(config, successCost); await this.workflow.get('uses')?.execute(config, successCost); diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index 20e4d6f0..ee5d221b 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -309,7 +309,7 @@ export function ActionMixin(Base) { } }; - ChatMessage.applyRollMode(msg, game.settings.get('core', 'rollMode')); + ChatMessage.applyMode(msg, game.settings.get('core', 'messageMode')); cls.create(msg); } } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 8cd3caac..58c0cd02 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -33,7 +33,7 @@ export default class DamageRoll extends DHRoll { static async buildPost(roll, config, message) { const chatMessage = config.source?.message ? ui.chat.collection.get(config.source.message) - : getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode ?? CONST.DICE_ROLL_MODES.PUBLIC); + : getDocumentClass('ChatMessage').applyMode({}, config.rollMode ?? 'public'); if (game.modules.get('dice-so-nice')?.active) { const pool = foundry.dice.terms.PoolTerm.fromRolls( Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll)) diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index b74adaf3..a5d95cd1 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -117,10 +117,10 @@ export default class DHRoll extends Roll { rolls: [roll] }; - config.selectedRollMode ??= game.settings.get('core', 'rollMode'); + config.selectedMessageMode ??= game.settings.get('core', 'messageMode'); if (roll._evaluated) { - const message = await cls.create(msgData, { rollMode: config.selectedRollMode }); + const message = await cls.create(msgData, { messageMode: config.selectedMessageMode }); if (config.tagTeamSelected) { game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message); diff --git a/module/documents/rollTable.mjs b/module/documents/rollTable.mjs index 50b8fe63..59652f44 100644 --- a/module/documents/rollTable.mjs +++ b/module/documents/rollTable.mjs @@ -76,7 +76,7 @@ export default class DhRollTable extends foundry.documents.RollTable { } async toMessage(results, { roll, messageData = {}, messageOptions = {} } = {}) { - messageOptions.rollMode ??= game.settings.get('core', 'rollMode'); + messageOptions.rollMode ??= game.settings.get('core', 'messageMode'); // Construct chat data messageData = foundry.utils.mergeObject( diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index c0dbae62..a5add3a4 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -56,8 +56,8 @@ {{/unless}}
      {{#if directDamage}} - + {{selectOptions rollModes selected=selectedMessageMode valueAttr="action" labelAttr="label" localize=true}} {{/if}}
      - - +
      + {{#each levels as |level|}} + {{level.name}} + {{/each}} +
      + {{/if}} + {{#if hasCompanion}}
      {{/each}} From aa1d117c433fe06a9c1103a40c5c83c3f5bc7bf1 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 25 Mar 2026 13:54:14 +0100 Subject: [PATCH 040/304] [V14] Effect Stacking (#1667) * Added the ability for effects to have stacks * Fixed effect stacking * Improved token overlay spacing * Compendium updaetes * Simplify effect click event (#1748) * Fixed a bunch of deprecations * Corrected AgileScout Beastform json data * Updated TokenHUD to the new v14 * Removed DestroyOnEmpty from consumables * Fixed so that tooltips don't get stuck (#1745) * [Feature] TagTeam Partial Rendering (#1735) * I done did it, I think * Think I fixed the partial rendering bug for gm->player * [V14] 1743 - Damage Update Error (#1746) * Fixed DamageParts causing errors on update * Fixed ActionBaseConfig error when no damage present on the action * Fix removal of damage field * Removed unneccessary default value function for parts --------- Co-authored-by: Carlos Fernandez * Simplify effect click event --------- Co-authored-by: WBHarry Co-authored-by: WBHarry <89362246+WBHarry@users.noreply.github.com> * Fixed stacking-value pointer event * Set the stacking value in EffectsDisplay to be tabular-nums for monospacing * Made baseEffect.stacking nullable instead of having an enabled property * . * Fixed so that actor._onUpdateDescantDocuments re-renders the EffectDisplay if effects were updated --------- Co-authored-by: Carlos Fernandez --- lang/en.json | 5 +- .../sheets-configs/activeEffectConfig.mjs | 14 ++ module/applications/ui/effectsDisplay.mjs | 22 ++- module/applications/ux/contextMenu.mjs | 53 ------- module/canvas/placeables/token.mjs | 37 ++++- module/data/activeEffect/baseEffect.mjs | 15 +- module/data/fields/action/effectsField.mjs | 13 +- module/documents/activeEffect.mjs | 17 +- module/documents/actor.mjs | 8 + .../feature_No_Mercy_njj2C3tMDeCHHOoh.json | 8 +- ...eature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json | 8 +- ...Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json | 48 ++++-- .../domainCard_Rage_Up_GRL0cvs96vrTDckZ.json | 147 ++++++++---------- styles/less/global/elements.less | 18 +++ .../sheets/activeEffects/activeEffects.less | 10 -- styles/less/ui/effects-display/sheet.less | 15 ++ styles/less/ux/tooltip/bordered-tooltip.less | 50 +++++- templates/sheets/activeEffect/changes.hbs | 8 +- templates/sheets/activeEffect/settings.hbs | 14 ++ templates/ui/effects-display.hbs | 3 + templates/ui/tooltip/effect-display.hbs | 40 ++++- 21 files changed, 349 insertions(+), 204 deletions(-) diff --git a/lang/en.json b/lang/en.json index cf96ee3c..47697fa8 100755 --- a/lang/en.json +++ b/lang/en.json @@ -153,7 +153,8 @@ "Config": { "rangeDependence": { "title": "Range Dependence" - } + }, + "stacking": { "title": "Stacking" } }, "RangeDependance": { "hint": "Settings for an optional distance at which this effect should activate", @@ -2987,6 +2988,8 @@ }, "EffectsDisplay": { "removeThing": "[Right Click] Remove {thing}", + "increaseStacks": "[Left Click] Increment Stacks", + "decreaseStacks": "[Right Click] Decrement Stacks", "appliedBy": "Applied By: {by}" }, "ItemBrowser": { diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 9f970e1d..834a57a8 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -151,6 +151,10 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac }); }); + htmlElement + .querySelector('.stacking-change-checkbox') + ?.addEventListener('change', this.stackingChangeToggle.bind(this)); + htmlElement .querySelector('.armor-change-checkbox') ?.addEventListener('change', this.armorChangeToggle.bind(this)); @@ -209,6 +213,16 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac return partContext; } + stackingChangeToggle(event) { + const stackingFields = this.document.system.schema.fields.stacking.fields; + const systemData = { + stacking: event.target.checked + ? { value: stackingFields.value.initial, max: stackingFields.max.initial } + : null + }; + return this.submit({ updateData: { system: systemData } }); + } + armorChangeToggle(event) { if (event.target.checked) { this.addArmorChange(); diff --git a/module/applications/ui/effectsDisplay.mjs b/module/applications/ui/effectsDisplay.mjs index 3bc5e716..035041e1 100644 --- a/module/applications/ui/effectsDisplay.mjs +++ b/module/applications/ui/effectsDisplay.mjs @@ -49,11 +49,9 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); - - if (this.element) { - this.element.querySelectorAll('.effect-container a').forEach(element => { - element.addEventListener('contextmenu', this.removeEffect.bind(this)); - }); + for (const element of this.element?.querySelectorAll('.effect-container a') ?? []) { + element.addEventListener('click', e => this.#onClickEffect(e)); + element.addEventListener('contextmenu', e => this.#onClickEffect(e, -1)); } } @@ -87,11 +85,21 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica this.render(); } - async removeEffect(event) { + async #onClickEffect(event, delta = 1) { const element = event.target.closest('.effect-container'); const effects = DhEffectsDisplay.getTokenEffects(); const effect = effects.find(x => x.id === element.dataset.effectId); - await effect.delete(); + if (!effect || (delta >= 0 && !effect.system.stacking)) { + return; + } + + const maxValue = effect.system.stacking?.max ?? Infinity; + const newValue = Math.clamp((effect.system.stacking?.value ?? 1) + delta, 0, maxValue); + if (newValue > 0) { + await effect.update({ 'system.stacking.value': newValue }); + } else { + await effect.delete(); + } this.render(); } diff --git a/module/applications/ux/contextMenu.mjs b/module/applications/ux/contextMenu.mjs index 9a308667..4e4ec6a4 100644 --- a/module/applications/ux/contextMenu.mjs +++ b/module/applications/ux/contextMenu.mjs @@ -1,56 +1,3 @@ -/** - * @typedef ContextMenuEntry - * @property {string} name The context menu label. Can be localized. - * @property {string} [icon] A string containing an HTML icon element for the menu item. - * @property {string} [classes] Additional CSS classes to apply to this menu item. - * @property {string} [group] An identifier for a group this entry belongs to. - * @property {ContextMenuJQueryCallback} callback The function to call when the menu item is clicked. - * @property {ContextMenuCondition|boolean} [condition] A function to call or boolean value to determine if this entry - * appears in the menu. - */ - -/** - * @callback ContextMenuCondition - * @param {jQuery|HTMLElement} html The element of the context menu entry. - * @returns {boolean} Whether the entry should be rendered in the context menu. - */ - -/** - * @callback ContextMenuCallback - * @param {HTMLElement} target The element that the context menu has been triggered for. - * @returns {unknown} - */ - -/** - * @callback ContextMenuJQueryCallback - * @param {HTMLElement|jQuery} target The element that the context menu has been triggered for. Will - * either be a jQuery object or an HTMLElement instance, depending - * on how the ContextMenu was configured. - * @returns {unknown} - */ - -/** - * @typedef ContextMenuOptions - * @property {string} [eventName="contextmenu"] Optionally override the triggering event which can spawn the menu. If - * the menu is using fixed positioning, this event must be a MouseEvent. - * @property {ContextMenuCallback} [onOpen] A function to call when the context menu is opened. - * @property {ContextMenuCallback} [onClose] A function to call when the context menu is closed. - * @property {boolean} [fixed=false] If true, the context menu is given a fixed position rather than being - * injected into the target. - * @property {boolean} [jQuery=true] If true, callbacks will be passed jQuery objects instead of HTMLElement - * instances. - */ - -/** - * @typedef ContextMenuRenderOptions - * @property {Event} [event] The event that triggered the context menu opening. - * @property {boolean} [animate=true] Animate the context menu opening. - */ - -/** - * A subclass of ContextMenu. - * @extends {foundry.applications.ux.ContextMenu} - */ export default class DHContextMenu extends foundry.applications.ux.ContextMenu { /** * Trigger a context menu event in response to a normal click on a additional options button. diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 04d1c3c0..35d34f83 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -30,8 +30,8 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { if (!effect.img) continue; const promise = effect === overlayEffect - ? this._drawOverlay(effect.img, effect.tint) - : this._drawEffect(effect.img, effect.tint); + ? this._drawOverlay(effect.img, effect.tint, effect) + : this._drawEffect(effect.img, effect.tint, effect); promises.push( promise.then(e => { if (e) e.zIndex = i; @@ -45,6 +45,39 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { this.renderFlags.set({ refreshEffects: true }); } + /**@inheritdoc */ + async _drawEffect(src, tint, effect) { + if (!src) return; + const tex = await foundry.canvas.loadTexture(src, { fallback: 'icons/svg/hazard.svg' }); + const icon = new PIXI.Sprite(tex); + icon.tint = tint ?? 0xffffff; + + if (effect.system.stacking?.value > 1) { + const stackOverlay = new PIXI.Text(effect.system.stacking.value, { + fill: '#f3c267', + stroke: '#000000', + fontSize: 96, + strokeThickness: 4 + }); + const nrDigits = Math.floor(Math.log10(effect.system.stacking.value)) + 1; + stackOverlay.y = -8; + /* This does not account for 1:s being much less wide than other digits. I don't think it's desired however as it makes it look jumpy */ + stackOverlay.x = icon.width - 8 - nrDigits * 56; + stackOverlay.anchor.set(0, 0); + + icon.addChild(stackOverlay); + } + + return this.effects.addChild(icon); + } + + async _drawOverlay(src, tint, effect) { + const icon = await this._drawEffect(src, tint, effect); + if (icon) icon.alpha = 0.8; + this.effects.overlay = icon ?? null; + return icon; + } + /** * Returns the distance from this token to another token object. * This value is corrected to handle alternate token sizes and other grid types diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index c00b8cf7..44e7ec0f 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -80,7 +80,20 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { initial: CONFIG.DH.GENERAL.range.melee.id, label: 'DAGGERHEART.GENERAL.range' }) - }) + }), + stacking: new fields.SchemaField( + { + value: new fields.NumberField({ + initial: 1, + min: 1, + integer: true, + nullable: false, + label: 'DAGGERHEART.GENERAL.value' + }), + max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' }) + }, + { nullable: true, initial: null } + ) }; } diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index 1a003e2b..9a4ffc31 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -106,22 +106,11 @@ export default class EffectsField extends fields.ArrayField { } /** - * Apply an Effect to a target or enable it if already on it + * Apply an Effect to a target * @param {object} effect Effect object containing ActiveEffect UUID * @param {object} actor Actor Document */ static async applyEffect(effect, actor) { - const existingEffect = actor.effects.find(e => e.origin === effect.uuid); - if (existingEffect) { - return effect.update( - foundry.utils.mergeObject({ - ...effect.constructor.getInitialDuration(), - disabled: false - }) - ); - } - - // Otherwise, create a new effect on the target const effectData = foundry.utils.mergeObject({ ...(effect.toObject?.() ?? effect), disabled: false, diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 07c68518..4aeba3af 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -108,6 +108,18 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { update.img = 'icons/magic/life/heart-cross-blue.webp'; } + const existingEffect = this.actor.effects.find(x => x.origin === data.origin); + const stacks = Boolean(data.system?.stacking); + if (existingEffect && !stacks) return false; + + if (existingEffect && stacks) { + const incrementedValue = existingEffect.system.stacking.value + 1; + await existingEffect.update({ + 'system.stacking.value': Math.min(incrementedValue, existingEffect.system.stacking.max ?? Infinity) + }); + return false; + } + const statuses = Object.keys(data.statuses ?? {}); const immuneStatuses = statuses.filter( @@ -184,7 +196,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { } catch (_) {} } - const evalValue = this.effectSafeEval(itemAbleRollParse(key, parseModel, effect.parent)); + const stackingParsedValue = effect.system.stacking + ? Roll.replaceFormulaData(key, { stacks: effect.system.stacking.value }) + : key; + const evalValue = itemAbleRollParse(stackingParsedValue, parseModel, effect.parent); return evalValue ?? key; } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 47a9cd31..8105471b 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -122,6 +122,14 @@ export default class DhpActor extends Actor { } } + _onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) { + if (collection === 'effects') { + ui.effectsDisplay.render(); + } + + super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId); + } + async updateLevel(newLevel) { if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return; diff --git a/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json b/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json index b5239242..5b770e5d 100644 --- a/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json +++ b/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json @@ -71,13 +71,17 @@ "changes": [ { "key": "system.bonuses.roll.attack.bonus", - "value": 1, + "type": "add", + "value": "@stacks", "priority": null, - "type": "add" + "phase": "initial" } ], "duration": { "type": "shortRest" + }, + "stacking": { + "max": null } }, "disabled": false, diff --git a/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json b/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json index 0f31f491..b886b079 100644 --- a/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json +++ b/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json @@ -69,14 +69,18 @@ "changes": [ { "key": "system.evasion", - "value": 2, + "type": "add", + "value": "2 * @stacks", "priority": null, - "type": "add" + "phase": "initial" } ], "duration": { "type": "temporary", "description": "

      Until the next time an attack succeeds against you.

      " + }, + "stacking": { + "max": null } }, "disabled": false, diff --git a/src/packs/domains/domainCard_Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json b/src/packs/domains/domainCard_Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json index 01d88111..6a039bbf 100644 --- a/src/packs/domains/domainCard_Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json +++ b/src/packs/domains/domainCard_Corrosive_Projectile_qJaSNTuDfbPVr8Lb.json @@ -127,7 +127,7 @@ "sort": 3400000, "effects": [ { - "name": "Corroded (1 stack)", + "name": "Corroded", "img": "icons/magic/acid/dissolve-bone-white.webp", "origin": "Compendium.daggerheart.domains.Item.qJaSNTuDfbPVr8Lb", "transfer": false, @@ -139,27 +139,31 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.difficulty", + "type": "add", + "value": "-@stack", + "priority": null, + "phase": "initial" + } + ], + "stacking": { + "max": null + }, + "duration": { + "type": "" } }, - "changes": [ - { - "key": "system.difficulty", - "mode": 2, - "value": "-1", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "

      While a target is Corroded, they gain a −1 penalty to their Difficulty for every 2 Stress you spent. This condition can stack.

      ", + "description": "

      While a target is Corroded, they gain a −1 penalty to their Difficulty for every 2 Stress you spent. This condition can stack.

      ", "tint": "#ffffff", "statuses": [ "corrode" @@ -169,6 +173,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!qJaSNTuDfbPVr8Lb.zB95bjSSdVlApQnR" } ], diff --git a/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json b/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json index 6f8b481d..c3493aea 100644 --- a/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json +++ b/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json @@ -42,44 +42,8 @@ "type": "self", "amount": null }, - "name": "Mark 1 Stress", - "img": "icons/magic/control/silhouette-aura-energy.webp", - "range": "self" - }, - "fKY9NcYBwCFwMsgV": { - "type": "effect", - "_id": "fKY9NcYBwCFwMsgV", - "systemPath": "actions", - "description": "

      You can mark a Stress to gain a bonus to your damage roll equal to twice your Strength.

      ", - "chatDisplay": true, - "actionType": "action", - "cost": [ - { - "scalable": false, - "key": "stress", - "value": 2, - "step": null, - "consumeOnSuccess": false - } - ], - "uses": { - "value": null, - "max": "", - "recovery": null, - "consumeOnSuccess": false - }, - "effects": [ - { - "_id": "t6SIjQxB6UBUJ98f", - "onSave": false - } - ], - "target": { - "type": "self", - "amount": null - }, - "name": "Mark 2 Stress", - "img": "icons/magic/control/silhouette-aura-energy.webp", + "name": "Mark Stress", + "img": "icons/skills/wounds/injury-face-impact-orange.webp", "range": "self" } }, @@ -94,8 +58,8 @@ "sort": 3400000, "effects": [ { - "name": "Rage Up (1)", - "img": "systems/daggerheart/assets/icons/domains/domain-card/blade.png", + "name": "Rage Up", + "img": "icons/skills/wounds/injury-face-impact-orange.webp", "origin": "Compendium.daggerheart.domains.Item.GRL0cvs96vrTDckZ", "transfer": false, "_id": "bq1MhcmoP6Wo5CXF", @@ -106,33 +70,38 @@ "type": "withinRange", "target": "hostile", "range": "melee" + }, + "changes": [ + { + "key": "system.bonuses.damage.magical.bonus", + "type": "add", + "value": "2*@system.traits.strength.value*@stacks", + "priority": 21, + "phase": "initial" + }, + { + "key": "system.bonuses.damage.physical.bonus", + "type": "add", + "value": "2*@system.traits.strength.value*@stacks", + "priority": 21, + "phase": "initial" + } + ], + "stacking": { + "max": 2 + }, + "duration": { + "type": "" } }, - "changes": [ - { - "key": "system.bonuses.damage.magical.bonus", - "mode": 2, - "value": "2*@system.traits.strength.value", - "priority": 21 - }, - { - "key": "system.bonuses.damage.physical.bonus", - "mode": 2, - "value": "2*@system.traits.strength.value", - "priority": 21 - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, - "description": "", + "description": "

      For your next attack you have a bonus to your damage roll equal to twice your Strength.

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -140,6 +109,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!GRL0cvs96vrTDckZ.bq1MhcmoP6Wo5CXF" }, { @@ -155,31 +134,28 @@ "type": "withinRange", "target": "hostile", "range": "melee" - } - }, - "changes": [ - { - "key": "system.bonuses.damage.magical.bonus", - "mode": 2, - "value": "4*@system.traits.strength.value", - "priority": 21 }, - { - "key": "system.bonuses.damage.physical.bonus", - "mode": 2, - "value": "4*@system.traits.strength.value", - "priority": 21 - } - ], + "changes": [ + { + "key": "system.bonuses.damage.magical.bonus", + "value": "4*@system.traits.strength.value", + "priority": 21, + "type": "add" + }, + { + "key": "system.bonuses.damage.physical.bonus", + "value": "4*@system.traits.strength.value", + "priority": 21, + "type": "add" + } + ] + }, "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -189,6 +165,9 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!items.effects!GRL0cvs96vrTDckZ.t6SIjQxB6UBUJ98f" } ], diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index c33557b1..793c8164 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -293,6 +293,20 @@ } } + &.optional, + &.one-column.optional { + padding-top: 0; + padding-bottom: 4px; + min-height: auto; + row-gap: 0; + + legend { + display: flex; + align-items: center; + padding-left: 3px; + } + } + .list-w-img { padding: 5px; label { @@ -469,6 +483,10 @@ &.even { grid-template-columns: 1fr 1fr; } + + &.full-width { + width: 100%; + } } .three-columns { diff --git a/styles/less/sheets/activeEffects/activeEffects.less b/styles/less/sheets/activeEffects/activeEffects.less index 3f6526cb..e4f5c541 100644 --- a/styles/less/sheets/activeEffects/activeEffects.less +++ b/styles/less/sheets/activeEffects/activeEffects.less @@ -33,16 +33,6 @@ } .armor-change-container { - padding-top: 0; - padding-bottom: 4px; - row-gap: 0; - - legend { - display: flex; - align-items: center; - padding-left: 3px; - } - header { padding: 0; left: -0.25rem; // TODO: Find why this header is offset 0.25rem to the right so this can be removed. diff --git a/styles/less/ui/effects-display/sheet.less b/styles/less/ui/effects-display/sheet.less index 1331b094..17d9889f 100644 --- a/styles/less/ui/effects-display/sheet.less +++ b/styles/less/ui/effects-display/sheet.less @@ -35,6 +35,21 @@ color: @golden; filter: drop-shadow(0 0 3px black); } + + .stacking-value { + position: absolute; + top: 4px; + right: 4px; + font-size: 16px; + font-weight: bold; + font-variant-numeric: tabular-nums; + color: @golden; + background: black; + padding: 1px; + border-radius: 6px; + border: 1px solid @golden; + pointer-events: none; + } } } } diff --git a/styles/less/ux/tooltip/bordered-tooltip.less b/styles/less/ux/tooltip/bordered-tooltip.less index b3a5ed29..abec93b7 100644 --- a/styles/less/ux/tooltip/bordered-tooltip.less +++ b/styles/less/ux/tooltip/bordered-tooltip.less @@ -6,6 +6,7 @@ .daggerheart.dh-style.tooltip { display: flex; flex-direction: column; + align-items: start; text-align: start; width: 100%; gap: 5px; @@ -13,6 +14,7 @@ border-radius: 3px; .tooltip-header { + width: 100%; display: flex; flex-direction: column; align-items: center; @@ -35,12 +37,48 @@ } } - .close-hint { - border-radius: 3px; - padding: 3px; - background: @rustic-brown-80; - color: @golden; - font-size: 12px; + .effect-stacks-outer-container { + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; + + .effect-stacks-title { + font-size: var(--font-size-20); + font-weight: bold; + text-align: center; + } + + .effect-stacks-container { + display: flex; + justify-content: space-between; + + .effect-stacks-inner-container { + display: flex; + flex-direction: column; + gap: 2px; + + .effect-stack-title { + font-weight: bold; + } + } + } + } + + .close-hints { + margin-top: 0.5rem; + display: flex; + flex-direction: column; + gap: 4px; + + .close-hint { + border-radius: 3px; + padding: 3px; + background: @rustic-brown-80; + color: @golden; + font-size: 12px; + margin: 0; + } } .duration-container { diff --git a/templates/sheets/activeEffect/changes.hbs b/templates/sheets/activeEffect/changes.hbs index 47a48382..37feb845 100644 --- a/templates/sheets/activeEffect/changes.hbs +++ b/templates/sheets/activeEffect/changes.hbs @@ -14,8 +14,12 @@ {{/each}} -
      - {{localize "DAGGERHEART.GENERAL.armor"}} +
      + + {{localize "DAGGERHEART.GENERAL.armor"}} + + + {{#if typedChanges.armor}} {{> "systems/daggerheart/templates/sheets/activeEffect/typeChanges/armorChange.hbs" typedChanges.armor fields=@root.systemFields.changes.element.types.armor.fields}} {{/if}} diff --git a/templates/sheets/activeEffect/settings.hbs b/templates/sheets/activeEffect/settings.hbs index 9443edfb..9307ff65 100644 --- a/templates/sheets/activeEffect/settings.hbs +++ b/templates/sheets/activeEffect/settings.hbs @@ -1,4 +1,18 @@
      +
      + + {{localize "DAGGERHEART.ACTIVEEFFECT.Config.stacking.title"}} + + + + {{#if source.system.stacking}} +
      + {{formGroup systemFields.stacking.fields.value value=source.system.stacking.value localize=true }} + {{formGroup systemFields.stacking.fields.max value=source.system.stacking.max localize=true }} +
      + {{/if}} +
      +
      {{localize "DAGGERHEART.ACTIVEEFFECT.Config.rangeDependence.title"}} diff --git a/templates/ui/effects-display.hbs b/templates/ui/effects-display.hbs index 95c6023c..67c783a6 100644 --- a/templates/ui/effects-display.hbs +++ b/templates/ui/effects-display.hbs @@ -8,6 +8,9 @@ + {{#if (gt effect.system.stacking.value 1)}} + {{effect.system.stacking.value}} + {{/if}} {{#if effect.condition}}{{/if}} {{/each}} diff --git a/templates/ui/tooltip/effect-display.hbs b/templates/ui/tooltip/effect-display.hbs index d37b5147..ebb21be4 100644 --- a/templates/ui/tooltip/effect-display.hbs +++ b/templates/ui/tooltip/effect-display.hbs @@ -17,7 +17,6 @@ {{/if}} {{#if effect.system.duration.type}} -
      {{localize "EFFECT.DURATION.Label"}}: @@ -26,9 +25,42 @@
      {{/if}} + {{#if effect.system.stacking}} +
      +
      {{localize "Stacks"}}
      +
      +
      + {{localize "Current"}} + {{effect.system.stacking.value}} +
      +
      + {{localize "Max"}} + {{effect.system.stacking.max}} +
      +
      +
      + {{/if}} + {{#unless effect.isLockedCondition}} -

      - {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} -

      +
      + {{#if effect.system.stacking}} +

      + {{localize "DAGGERHEART.UI.EffectsDisplay.increaseStacks"}} +

      + {{#if (gt effect.system.stacking.value 1)}} +

      + {{localize "DAGGERHEART.UI.EffectsDisplay.decreaseStacks"}} +

      + {{else}} +

      + {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} +

      + {{/if}} + {{else}} +

      + {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} +

      + {{/if}} +
      {{/unless}}
      \ No newline at end of file From 931217577aae515afbabc5f6326db1db19ac99dd Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 25 Mar 2026 14:30:42 +0100 Subject: [PATCH 041/304] Fixed BaseEffect emiting a scroll text of 0 armor change on every update --- module/data/activeEffect/baseEffect.mjs | 6 ++++-- module/helpers/utils.mjs | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index 44e7ec0f..e3f4137d 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -166,8 +166,10 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { return acc; }, this.parent.actor.system.armor?.system?.armor?.current ?? 0); - const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor'); - options.scrollingTextData = [armorData]; + if (newArmorTotal !== this.parent.actor.system.armorScore.value) { + const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor'); + options.scrollingTextData = [armorData]; + } } } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 9038bb08..131f94b7 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -752,8 +752,7 @@ export function getArmorSources(actor) { const data = rawArmorSources.map(doc => { // Get the origin item. Since the actor is already loaded, it should already be cached // Consider the relative function versions if this causes an issue - const isItem = doc instanceof Item; - const origin = isItem ? doc : doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc.parent; + const origin = doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc; return { origin, name: origin.name, From e3e1395de6f088613dc71472b18e937e9f8e4115 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 25 Mar 2026 16:36:36 +0100 Subject: [PATCH 042/304] Minor ActionConfig improvements --- .../sheets-configs/action-base-config.mjs | 3 +++ module/applications/sheets-configs/action-config.mjs | 12 +++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 7396894f..05a3177d 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -318,6 +318,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) const type = choices[button.form.elements.type.value].value; const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue(); part.applyTo = type; + if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) + part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial; + data.damage.parts[type] = part; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); }; diff --git a/module/applications/sheets-configs/action-config.mjs b/module/applications/sheets-configs/action-config.mjs index e75e16ab..e15b0391 100644 --- a/module/applications/sheets-configs/action-config.mjs +++ b/module/applications/sheets-configs/action-config.mjs @@ -24,15 +24,13 @@ export default class DHActionConfig extends DHActionBaseConfig { const effectData = this._addEffectData.bind(this)(); const data = this.action.toObject(); - const created = await game.system.api.documents.DhActiveEffect.createDialog(effectData, { - parent: this.action.item, - render: false - }); - if (!created) return; + const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [ + game.system.api.data.activeEffects.BaseEffect.getDefaultObject() + ]); - data.effects.push({ _id: created._id }); + data.effects.push({ _id: created[0]._id }); this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); - this.action.item.effects.get(created._id).sheet.render(true); + this.action.item.effects.get(created[0]._id).sheet.render(true); } /** From 394d1d338d3cd0e6b6e2445233a28e0d34752f57 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 26 Mar 2026 16:27:09 +0100 Subject: [PATCH 043/304] Corrected BaseEffect scrollText armorUpdate logic --- module/data/activeEffect/baseEffect.mjs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index e3f4137d..bac50c56 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -161,12 +161,11 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { this.parent.actor?.type === 'character' && this.parent.actor.system.resources.armor ) { - const newArmorTotal = (changed.system?.changes ?? []).reduce((acc, change) => { - if (change.type === 'armor') acc += change.value.current; - return acc; - }, this.parent.actor.system.armor?.system?.armor?.current ?? 0); + const armorEffect = changed.system?.changes?.find(x => x.type === 'armor'); + const newArmorTotal = + armorEffect?.value?.current + (this.parent.actor.system.armor?.system?.armor?.current ?? 0); - if (newArmorTotal !== this.parent.actor.system.armorScore.value) { + if (armorEffect && newArmorTotal !== this.parent.actor.system.armorScore.value) { const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor'); options.scrollingTextData = [armorData]; } From d7ce388cad1f217bbe438e1474e14e393e3a6fd7 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 27 Mar 2026 10:29:01 +0100 Subject: [PATCH 044/304] Fixed resource error on TagTeamDialog reroll --- module/applications/dialogs/tagTeamDialog.mjs | 3 ++- module/dice/dhRoll.mjs | 4 ---- module/dice/dualityRoll.mjs | 12 ++++++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 27003162..5fdefcbd 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -453,7 +453,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const { parsedRoll, newRoll } = await game.system.api.dice.DualityRoll.reroll( memberData.rollData, dieIndex, - diceType + diceType, + false ); const rollData = parsedRoll.toJSON(); this.updatePartyData( diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index a5d95cd1..3310b9ca 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -122,10 +122,6 @@ export default class DHRoll extends Roll { if (roll._evaluated) { const message = await cls.create(msgData, { messageMode: config.selectedMessageMode }); - if (config.tagTeamSelected) { - game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message); - } - if (roll.formula !== '' && game.modules.get('dice-so-nice')?.active) { await game.dice3d.waitFor3DAnimationByMessageID(message.id); } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 03035f68..84e0b493 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -305,7 +305,6 @@ export default class DualityRoll extends D20Roll { !config.source?.actor || (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) || config.actionType === 'reaction' || - config.tagTeamSelected || config.skips?.resources ) return; @@ -346,7 +345,6 @@ export default class DualityRoll extends D20Roll { if ( automationSettings.countdownAutomation && config.actionType !== 'reaction' && - !config.tagTeamSelected && !config.skips?.updateCountdowns ) { const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; @@ -374,7 +372,7 @@ export default class DualityRoll extends D20Roll { } } - static async reroll(rollBase, dieIndex, diceType) { + static async reroll(rollBase, dieIndex, diceType, updateResources = true) { let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollBase, evaluated: false }); const term = parsedRoll.terms[dieIndex]; await term.reroll(`/r1=${term.total}`); @@ -421,12 +419,14 @@ export default class DualityRoll extends D20Roll { source: { actor: parsedRoll.options.source.actor ?? '' }, targets: parsedRoll.targets, roll: newRoll, - rerolledRoll: parsedRoll.roll, + rerolledRoll: parsedRoll.options.roll, resourceUpdates: new ResourceUpdateMap(actor) }; - await DualityRoll.addDualityResourceUpdates(config); - await config.resourceUpdates.updateResources(); + if (updateResources) { + await DualityRoll.addDualityResourceUpdates(config); + await config.resourceUpdates.updateResources(); + } return { newRoll, parsedRoll }; } From d79c236cfefb470935188b9d20e1ea92b2d5e6c5 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 27 Mar 2026 22:11:01 +0100 Subject: [PATCH 045/304] Fixed effects not being creatable when not on an actor --- module/documents/activeEffect.mjs | 60 ++++++++++++++++--------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 4aeba3af..959971d5 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -108,37 +108,41 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { update.img = 'icons/magic/life/heart-cross-blue.webp'; } - const existingEffect = this.actor.effects.find(x => x.origin === data.origin); - const stacks = Boolean(data.system?.stacking); - if (existingEffect && !stacks) return false; + if (this.actor) { + const existingEffect = this.actor.effects.find(x => x.origin === data.origin); + const stacks = Boolean(data.system?.stacking); + if (existingEffect && !stacks) return false; - if (existingEffect && stacks) { - const incrementedValue = existingEffect.system.stacking.value + 1; - await existingEffect.update({ - 'system.stacking.value': Math.min(incrementedValue, existingEffect.system.stacking.max ?? Infinity) - }); - return false; + if (existingEffect && stacks) { + const incrementedValue = existingEffect.system.stacking.value + 1; + await existingEffect.update({ + 'system.stacking.value': Math.min(incrementedValue, existingEffect.system.stacking.max ?? Infinity) + }); + return false; + } } - const statuses = Object.keys(data.statuses ?? {}); - const immuneStatuses = - statuses.filter( - status => - this.parent.system.rules?.conditionImmunities && - this.parent.system.rules.conditionImmunities[status] - ) ?? []; - if (immuneStatuses.length > 0) { - update.statuses = statuses.filter(x => !immuneStatuses.includes(x)); - const conditions = CONFIG.DH.GENERAL.conditions(); - const scrollingTexts = immuneStatuses.map(status => ({ - text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', { - status: game.i18n.localize(conditions[status].name) - }) - })); - if (update.statuses.length > 0) { - setTimeout(() => scrollingTexts, 500); - } else { - this.parent.queueScrollText(scrollingTexts); + if (this.parent) { + const statuses = Object.keys(data.statuses ?? {}); + const immuneStatuses = + statuses.filter( + status => + this.parent.system.rules?.conditionImmunities && + this.parent.system.rules.conditionImmunities[status] + ) ?? []; + if (immuneStatuses.length > 0) { + update.statuses = statuses.filter(x => !immuneStatuses.includes(x)); + const conditions = CONFIG.DH.GENERAL.conditions(); + const scrollingTexts = immuneStatuses.map(status => ({ + text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', { + status: game.i18n.localize(conditions[status].name) + }) + })); + if (update.statuses.length > 0) { + setTimeout(() => scrollingTexts, 500); + } else { + this.parent.queueScrollText(scrollingTexts); + } } } From 7f8e3fee6e2c0ac3035502354a6431846cb939cd Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 27 Mar 2026 22:24:13 +0100 Subject: [PATCH 046/304] Fixed ActiveEffect preCreate blocking multiple effects with origin=null --- module/documents/activeEffect.mjs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 959971d5..8ec7a751 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -1,5 +1,4 @@ import { itemAbleRollParse } from '../helpers/utils.mjs'; -import { RefreshType } from '../systemRegistration/socket.mjs'; export default class DhActiveEffect extends foundry.documents.ActiveEffect { /* -------------------------------------------- */ @@ -108,7 +107,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { update.img = 'icons/magic/life/heart-cross-blue.webp'; } - if (this.actor) { + if (this.actor && data.origin) { const existingEffect = this.actor.effects.find(x => x.origin === data.origin); const stacks = Boolean(data.system?.stacking); if (existingEffect && !stacks) return false; @@ -153,20 +152,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { await super._preCreate(data, options, user); } - /** @inheritdoc */ - _onCreate(data, options, userId) { - super._onCreate(data, options, userId); - - Hooks.callAll(RefreshType.EffectsDisplay); - } - - /** @inheritdoc */ - _onDelete(data, options, userId) { - super._onDelete(data, options, userId); - - Hooks.callAll(RefreshType.EffectsDisplay); - } - /* -------------------------------------------- */ /* Methods */ /* -------------------------------------------- */ From e3c4f1ce9fbbe55d7f88808b6a45be206e70dec3 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 28 Mar 2026 00:17:53 +0100 Subject: [PATCH 047/304] Raised version to Testing 3 --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index 9a78bf50..cc322576 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.0.0", "compatibility": { "minimum": "14.355", - "verified": "14.357", + "verified": "14.358", "maximum": "14" }, "authors": [ From c730cc3d4d15e13d9c1a900fe8a56bcf781ab034 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:18:35 +0100 Subject: [PATCH 048/304] [Feature] 1740 - Beastform Info (#1750) * Improved the EffectDisplay tooltip of the beastform effect to show the info about the active beastform * . * Move template to more sorted location --------- Co-authored-by: Carlos Fernandez --- module/data/activeEffect/beastformEffect.mjs | 2 +- module/data/item/beastform.mjs | 10 +- module/documents/tooltipManager.mjs | 33 ++++- module/systemRegistration/handlebars.mjs | 1 + .../less/sheets/actors/character/sidebar.less | 9 +- styles/less/ux/index.less | 1 + styles/less/ux/tooltip/bordered-tooltip.less | 2 +- styles/less/ux/tooltip/sheet.less | 129 ++++++++++++++++++ styles/less/ux/tooltip/tooltip.less | 111 --------------- templates/ui/tooltip/beastform.hbs | 32 +---- templates/ui/tooltip/effect-display.hbs | 28 ++-- templates/ui/tooltip/parts/beastformData.hbs | 31 +++++ templates/ui/tooltip/parts/tooltipTags.hbs | 1 + 13 files changed, 226 insertions(+), 164 deletions(-) create mode 100644 styles/less/ux/tooltip/sheet.less create mode 100644 templates/ui/tooltip/parts/beastformData.hbs diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 47e28b4c..128c0c52 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -25,7 +25,7 @@ export default class BeastformEffect extends BaseEffect { width: new fields.NumberField({ integer: false, nullable: true }) }) }), - advantageOn: new fields.ArrayField(new fields.StringField()), + advantageOn: new fields.TypedObjectField(new fields.SchemaField({ value: new fields.StringField() })), featureIds: new fields.ArrayField(new fields.StringField()), effectIds: new fields.ArrayField(new fields.StringField()) }; diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index 2792f7e3..d30d6046 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -99,10 +99,14 @@ export default class DHBeastform extends BaseDataItem { get beastformAttackData() { const effect = this.parent.effects.find(x => x.type === 'beastform'); + return DHBeastform.getBeastformAttackData(effect); + } + + static getBeastformAttackData(effect) { if (!effect) return null; - const traitBonus = - effect.system.changes.find(x => x.key === `system.traits.${this.mainTrait}.value`)?.value ?? 0; + const mainTrait = effect.system.changes.find(x => x.key === 'system.rules.attack.roll.trait')?.value; + const traitBonus = effect.system.changes.find(x => x.key === `system.traits.${mainTrait}.value`)?.value ?? 0; const evasionBonus = effect.system.changes.find(x => x.key === 'system.evasion')?.value ?? 0; const damageDiceIndex = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.diceIndex'); @@ -110,7 +114,7 @@ export default class DHBeastform extends BaseDataItem { const damageBonus = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.bonus')?.value ?? 0; return { - trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.mainTrait].label), + trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[mainTrait]?.label), traitBonus: traitBonus ? Number(traitBonus).signedString() : '', evasionBonus: evasionBonus ? Number(evasionBonus).signedString() : '', damageDice: damageDice, diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index bf107a42..e10dc5fa 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -31,12 +31,39 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti this.#bordered = true; let effect = {}; if (element.dataset.uuid) { - const effectData = (await foundry.utils.fromUuid(element.dataset.uuid)).toObject(); + const effectItem = await foundry.utils.fromUuid(element.dataset.uuid); + const effectData = effectItem.toObject(); + effect = { ...effectData, - name: game.i18n.localize(effectData.name), - description: game.i18n.localize(effectData.description ?? effectData.parent.system.description) + name: game.i18n.localize(effectData.name) }; + + if (effectData.type === 'beastform') { + const beastformData = { + features: [], + advantageOn: effectData.system.advantageOn, + beastformAttackData: game.system.api.data.items.DHBeastform.getBeastformAttackData(effectItem) + }; + + const features = effectItem.parent.items.filter(x => effectItem.system.featureIds.includes(x.id)); + for (const feature of features) { + const featureData = feature.toObject(); + featureData.enrichedDescription = await feature.system.getEnrichedDescription(); + beastformData.features.push(featureData); + } + + effect.description = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/tooltip/parts/beastformData.hbs', + { + item: { system: beastformData } + } + ); + } else { + effect.description = game.i18n.localize( + effectData.description ?? effectData.parent.system.description + ); + } } else { const conditions = CONFIG.DH.GENERAL.conditions(); const condition = conditions[element.dataset.condition]; diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 36df8b54..63e591c6 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -36,6 +36,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/actionTypes/summon.hbs', 'systems/daggerheart/templates/actionTypes/transform.hbs', 'systems/daggerheart/templates/settings/components/settings-item-line.hbs', + 'systems/daggerheart/templates/ui/tooltip/parts/beastformData.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs', 'systems/daggerheart/templates/dialogs/downtime/activities.hbs', diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index bb0a43cd..8bbede76 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -339,7 +339,8 @@ background: light-dark(@light-black, @dark-blue); border: 1px solid light-dark(@dark-blue, @golden); - h4, i { + h4, + i { color: light-dark(@dark-blue, @golden); } } @@ -380,7 +381,7 @@ } } } - .slot-label { + .slot-label { display: flex; align-items: center; color: light-dark(@beige, @dark-blue); @@ -399,7 +400,9 @@ background: light-dark(@light-black, @dark-blue); border: 1px solid light-dark(@dark-blue, @golden); - .label, .value, i { + .label, + .value, + i { color: light-dark(@dark-blue, @golden); } } diff --git a/styles/less/ux/index.less b/styles/less/ux/index.less index 6fad3ab1..a73f2d1c 100644 --- a/styles/less/ux/index.less +++ b/styles/less/ux/index.less @@ -1,3 +1,4 @@ +@import './tooltip/sheet.less'; @import './tooltip/tooltip.less'; @import './tooltip/armorManagement.less'; @import './tooltip/battlepoints.less'; diff --git a/styles/less/ux/tooltip/bordered-tooltip.less b/styles/less/ux/tooltip/bordered-tooltip.less index abec93b7..d72f635e 100644 --- a/styles/less/ux/tooltip/bordered-tooltip.less +++ b/styles/less/ux/tooltip/bordered-tooltip.less @@ -18,7 +18,7 @@ display: flex; flex-direction: column; align-items: center; - text-align: start; + text-align: center; padding: 5px; gap: 0px; diff --git a/styles/less/ux/tooltip/sheet.less b/styles/less/ux/tooltip/sheet.less new file mode 100644 index 00000000..59e4e638 --- /dev/null +++ b/styles/less/ux/tooltip/sheet.less @@ -0,0 +1,129 @@ +#tooltip:has(div.daggerheart.dh-style.tooltip.card-style), +aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip), +#tooltip.bordered-tooltip { + .tooltip-title { + font-size: var(--font-size-20); + color: light-dark(@dark-blue, @golden); + font-weight: 700; + } + + .tooltip-description { + font-style: inherit; + text-align: inherit; + width: 100%; + padding: 5px 10px; + position: relative; + margin-top: 5px; + + &::before { + content: ''; + background: @golden; + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + } + + &::before { + position: absolute; + top: -5px; + } + } + + .tooltip-separator { + background: @golden; + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + margin-bottom: 2px; + } + + .tooltip-tags { + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; + padding: 5px 10px; + position: relative; + max-height: 150px; + overflow-y: auto; + position: relative; + + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + + .tooltip-tag { + display: flex; + gap: 10px; + flex-direction: column; + + .tooltip-tag-label-container { + display: flex; + align-items: center; + gap: 5px; + + img { + width: 40px; + height: 40px; + border-radius: 3px; + } + } + } + } + + .tags { + display: flex; + gap: 5px 10px; + padding-bottom: 16px; + flex-wrap: wrap; + justify-content: center; + + &.advantages { + width: 100%; + padding: 5px 10px; + padding-bottom: 16px; + position: relative; + margin-top: 5px; + + &::before { + content: ''; + background: @golden; + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + } + + &::before { + position: absolute; + top: -5px; + } + + .tag { + background: @green-10; + color: @green; + border-color: @green; + } + } + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: var(--font-size-12); + font: @font-body; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: var(--font-size-12); + } + } +} diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less index ac09afc0..1566059f 100644 --- a/styles/less/ux/tooltip/tooltip.less +++ b/styles/less/ux/tooltip/tooltip.less @@ -13,13 +13,6 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip.card-style) { outline: 1px solid light-dark(@dark-80, @beige-80); box-shadow: 0 0 25px rgba(0, 0, 0, 0.8); - .tooltip-title { - font-size: var(--font-size-20); - color: light-dark(@dark-blue, @golden); - font-weight: 700; - margin-bottom: 5px; - } - .tooltip-subtitle { margin: 0; } @@ -80,110 +73,6 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip.card-style) { } } - .tooltip-tags { - display: flex; - flex-direction: column; - gap: 10px; - width: 100%; - padding: 5px 10px; - position: relative; - padding-top: 10px; - max-height: 150px; - overflow-y: auto; - position: relative; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - - &::before { - content: ''; - background: @golden; - mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); - height: 2px; - width: calc(100% - 10px); - } - - &::before { - position: absolute; - top: 0px; - } - - .tooltip-tag { - display: flex; - gap: 10px; - flex-direction: column; - - .tooltip-tag-label-container { - display: flex; - align-items: center; - gap: 5px; - - img { - width: 40px; - height: 40px; - border-radius: 3px; - } - } - } - } - - .tags { - display: flex; - gap: 5px 10px; - padding-bottom: 16px; - flex-wrap: wrap; - justify-content: center; - - &.advantages { - width: 100%; - padding: 5px 10px; - padding-bottom: 16px; - position: relative; - margin-top: 5px; - - &::before { - content: ''; - background: @golden; - mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); - height: 2px; - width: calc(100% - 10px); - } - - &::before { - position: absolute; - top: -5px; - } - - .tag { - background: @green-10; - color: @green; - border-color: @green; - } - } - - .tag { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 3px 5px; - font-size: var(--font-size-12); - font: @font-body; - - background: light-dark(@dark-15, @beige-15); - border: 1px solid light-dark(@dark, @beige); - border-radius: 3px; - } - - .label { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - font-size: var(--font-size-12); - } - } - .item-icons-list { position: absolute; display: flex; diff --git a/templates/ui/tooltip/beastform.hbs b/templates/ui/tooltip/beastform.hbs index 1b04ac82..3d07f78f 100644 --- a/templates/ui/tooltip/beastform.hbs +++ b/templates/ui/tooltip/beastform.hbs @@ -3,37 +3,7 @@

      {{item.name}}

      {{item.system.examples}}

      - {{#if description}} -
      {{{description}}}
      - {{/if}} - -
      - {{#with item.system.beastformAttackData}} -
      - {{localize "DAGGERHEART.ITEMS.Beastform.mainTrait"}} {{this.trait}} -
      -
      - {{localize "DAGGERHEART.ITEMS.Beastform.traitBonus"}} {{this.traitBonus}} -
      -
      - {{localize "DAGGERHEART.GENERAL.evasion"}} {{this.evasionBonus}} -
      -
      - {{localize "DAGGERHEART.GENERAL.damage"}} {{concat this.damageDice ' ' this.damageBonus}} -
      - {{/with}} -
      - -

      {{localize "DAGGERHEART.ITEMS.Beastform.FIELDS.advantageOn.label"}}

      -
      - {{#each item.system.advantageOn as | chip |}} -
      - {{ifThen chip.value chip.value chip}} -
      - {{/each}} -
      - - {{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.system.features label=(localize "DAGGERHEART.GENERAL.features")}} + {{> "systems/daggerheart/templates/ui/tooltip/parts/beastformData.hbs" }}

      {{localize "DAGGERHEART.UI.Tooltip.middleClick"}} diff --git a/templates/ui/tooltip/effect-display.hbs b/templates/ui/tooltip/effect-display.hbs index ebb21be4..f1db03c5 100644 --- a/templates/ui/tooltip/effect-display.hbs +++ b/templates/ui/tooltip/effect-display.hbs @@ -41,26 +41,32 @@ {{/if}} - {{#unless effect.isLockedCondition}} +

      - {{#if effect.system.stacking}} + {{#if (eq effect.type 'beastform')}}

      - {{localize "DAGGERHEART.UI.EffectsDisplay.increaseStacks"}} + {{localize "DAGGERHEART.UI.Tooltip.middleClick"}}

      - {{#if (gt effect.system.stacking.value 1)}} + {{/if}} + {{#unless effect.isLockedCondition}} + {{#if effect.system.stacking}}

      - {{localize "DAGGERHEART.UI.EffectsDisplay.decreaseStacks"}} + {{localize "DAGGERHEART.UI.EffectsDisplay.increaseStacks"}}

      + {{#if (gt effect.system.stacking.value 1)}} +

      + {{localize "DAGGERHEART.UI.EffectsDisplay.decreaseStacks"}} +

      + {{else}} +

      + {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} +

      + {{/if}} {{else}}

      {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}}

      {{/if}} - {{else}} -

      - {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} -

      - {{/if}} + {{/unless}}
      - {{/unless}} \ No newline at end of file diff --git a/templates/ui/tooltip/parts/beastformData.hbs b/templates/ui/tooltip/parts/beastformData.hbs new file mode 100644 index 00000000..0473df40 --- /dev/null +++ b/templates/ui/tooltip/parts/beastformData.hbs @@ -0,0 +1,31 @@ +{{#if description}} +
      {{{description}}}
      +{{/if}} + +
      + {{#with item.system.beastformAttackData}} +
      + {{localize "DAGGERHEART.ITEMS.Beastform.mainTrait"}} {{this.trait}} +
      +
      + {{localize "DAGGERHEART.ITEMS.Beastform.traitBonus"}} {{this.traitBonus}} +
      +
      + {{localize "DAGGERHEART.GENERAL.evasion"}} {{this.evasionBonus}} +
      +
      + {{localize "DAGGERHEART.GENERAL.damage"}} {{concat this.damageDice ' ' this.damageBonus}} +
      + {{/with}} +
      + +

      {{localize "DAGGERHEART.ITEMS.Beastform.FIELDS.advantageOn.label"}}

      +
      + {{#each item.system.advantageOn as | chip |}} +
      + {{ifThen chip.value chip.value chip}} +
      + {{/each}} +
      + +{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.system.features label=(localize "DAGGERHEART.GENERAL.features")}} \ No newline at end of file diff --git a/templates/ui/tooltip/parts/tooltipTags.hbs b/templates/ui/tooltip/parts/tooltipTags.hbs index 6a6d126d..da32723f 100644 --- a/templates/ui/tooltip/parts/tooltipTags.hbs +++ b/templates/ui/tooltip/parts/tooltipTags.hbs @@ -1,4 +1,5 @@ {{#if (gt features.length 0)}}

      {{label}}

      {{/if}} +
      {{#each features as | feature |}} {{#with (ifThen ../isAction feature (ifThen feature.item feature.item feature))}} From 2a294684d4d70a3d1984facac75a67c7f89acc46 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:38:50 +0100 Subject: [PATCH 049/304] Remade branch (#1754) --- lang/en.json | 11 +- .../applications/dialogs/beastformDialog.mjs | 38 +++ .../sheets-configs/action-base-config.mjs | 19 +- module/data/fields/action/beastformField.mjs | 23 +- .../feature_Evolution_6rlxhrRwFaVgq9fe.json | 270 +----------------- styles/less/dialog/beastform/sheet.less | 38 +++ styles/less/sheets/actions/actions.less | 14 + templates/actionTypes/beastform.hbs | 16 +- templates/dialogs/beastform/modifications.hbs | 28 ++ 9 files changed, 182 insertions(+), 275 deletions(-) create mode 100644 templates/dialogs/beastform/modifications.hbs diff --git a/lang/en.json b/lang/en.json index 5dc09a1e..d341b4bf 100755 --- a/lang/en.json +++ b/lang/en.json @@ -89,9 +89,14 @@ }, "Config": { "beastform": { - "exact": "Beastform Max Tier", - "exactHint": "The Character's Tier is used if empty", - "label": "Beastform" + "exact": { "label": "Beastform Max Tier", "hint": "The Character's Tier is used if empty" }, + "modifications": { + "traitBonuses": { + "label": { "single": "Trait Bonus", "plural": "Trait Bonuses" }, + "hint": "Pick bonuses you apply to freely chosen traits at the time of transforming", + "bonus": "Bonus Amount" + } + } }, "countdown": { "defaultOwnership": "Default Ownership", diff --git a/module/applications/dialogs/beastformDialog.mjs b/module/applications/dialogs/beastformDialog.mjs index 09a9222b..8ae6d5fe 100644 --- a/module/applications/dialogs/beastformDialog.mjs +++ b/module/applications/dialogs/beastformDialog.mjs @@ -10,6 +10,12 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat this.selected = null; this.evolved = { form: null }; this.hybrid = { forms: {}, advantages: {}, features: {} }; + this.modifications = { + traitBonuses: configData.modifications.traitBonuses.map(x => ({ + trait: null, + bonus: x.bonus + })) + }; this._dragDrop = this._createDragDropHandlers(); } @@ -28,6 +34,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat selectBeastform: this.selectBeastform, toggleHybridFeature: this.toggleHybridFeature, toggleHybridAdvantage: this.toggleHybridAdvantage, + toggleTraitBonus: this.toggleTraitBonus, submitBeastform: this.submitBeastform }, form: { @@ -48,6 +55,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' }, beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' }, advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' }, + modifications: { template: 'systems/daggerheart/templates/dialogs/beastform/modifications.hbs' }, footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' } }; @@ -146,6 +154,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat {} ); + context.modifications = this.modifications; + context.traits = CONFIG.DH.ACTOR.abilities; + context.tier = beastformTiers[this.tabGroups.primary]; context.tierKey = this.tabGroups.primary; @@ -155,6 +166,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat } canSubmit() { + const modificationsFinished = this.modifications.traitBonuses.every(x => x.trait); + if (!modificationsFinished) return false; + if (this.selected) { switch (this.selected.system.beastformType) { case 'normal': @@ -261,6 +275,13 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat this.render(); } + static toggleTraitBonus(_, button) { + const { index, trait } = button.dataset; + this.modifications.traitBonuses[index].trait = + this.modifications.traitBonuses[index].trait === trait ? null : trait; + this.render(); + } + static async submitBeastform() { await this.close({ submitted: true }); } @@ -292,6 +313,23 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat } } + const beastformEffect = selected.effects.find(x => x.type === 'beastform'); + for (const traitBonus of app.modifications.traitBonuses) { + const existingChange = beastformEffect.changes.find( + x => x.key === `system.traits.${traitBonus.trait}.value` + ); + if (existingChange) { + existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus; + } else { + beastformEffect.changes.push({ + key: `system.traits.${traitBonus.trait}.value`, + mode: 2, + priority: null, + value: traitBonus.bonus + }); + } + } + resolve({ selected: selected, evolved: { ...app.evolved, form: evolved }, diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 05a3177d..0ae39477 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -36,7 +36,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) editDoc: this.editDoc, addTrigger: this.addTrigger, removeTrigger: this.removeTrigger, - expandTrigger: this.expandTrigger + expandTrigger: this.expandTrigger, + addBeastformTraitBonus: this.addBeastformTraitBonus, + removeBeastformTraitBonus: this.removeBeastformTraitBonus }, form: { handler: this.updateForm, @@ -412,6 +414,21 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) } } + static async addBeastformTraitBonus() { + const data = this.action.toObject(); + data.beastform.modifications.traitBonuses = [ + ...data.beastform.modifications.traitBonuses, + this.action.schema.fields.beastform.fields.modifications.fields.traitBonuses.element.getInitialValue() + ]; + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + + static async removeBeastformTraitBonus(_event, button) { + const data = this.action.toObject(); + data.beastform.modifications.traitBonuses.splice(button.dataset.index, 1); + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + updateSummonCount(event) { event.stopPropagation(); const wrapper = event.target.closest('.summon-count-wrapper'); diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs index e19807c7..e3be9937 100644 --- a/module/data/fields/action/beastformField.mjs +++ b/module/data/fields/action/beastformField.mjs @@ -28,8 +28,21 @@ export default class BeastformField extends fields.SchemaField { { 1: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') } ); }, - hint: 'DAGGERHEART.ACTIONS.Config.beastform.exactHint' + label: 'DAGGERHEART.ACTIONS.Config.beastform.exact.label', + hint: 'DAGGERHEART.ACTIONS.Config.beastform.exact.hint' }) + }), + modifications: new fields.SchemaField({ + traitBonuses: new fields.ArrayField( + new fields.SchemaField({ + bonus: new fields.NumberField({ + integer: true, + initial: 1, + min: 1, + label: 'DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.bonus' + }) + }) + ) }) }; super(beastformFields, options, context); @@ -66,15 +79,9 @@ export default class BeastformField extends fields.SchemaField { ) ?? 1; config.tierLimit = this.beastform.tierAccess.exact ?? actorTier; + config.modifications = this.beastform.modifications; } - /** - * TODO by Harry - * @param {*} selectedForm - * @param {*} evolvedData - * @param {*} hybridData - * @returns - */ static async transform(selectedForm, evolvedData, hybridData) { const formData = evolvedData?.form ?? selectedForm; const beastformEffect = formData.effects.find(x => x.type === 'beastform'); diff --git a/src/packs/classes/feature_Evolution_6rlxhrRwFaVgq9fe.json b/src/packs/classes/feature_Evolution_6rlxhrRwFaVgq9fe.json index 46380fe8..421063a4 100644 --- a/src/packs/classes/feature_Evolution_6rlxhrRwFaVgq9fe.json +++ b/src/packs/classes/feature_Evolution_6rlxhrRwFaVgq9fe.json @@ -5,7 +5,7 @@ "_id": "6rlxhrRwFaVgq9fe", "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", "system": { - "description": "

      Spend 3 Hope to transform into a Beastform without marking a Stress. When you do, choose one trait to raise by +1 until you drop out of that Beastform.

      Note: Toggle one of the Evolution Traits in the effects tab to raise a trait by 1, e.g. Evolution: Agility

      ", + "description": "

      Spend 3 Hope to transform into a Beastform without marking a Stress. When you do, choose one trait to raise by +1 until you drop out of that Beastform.

      ", "resource": null, "actions": { "bj4m9E8ObFT0xDQ4": { @@ -31,6 +31,13 @@ "beastform": { "tierAccess": { "exact": null + }, + "modifications": { + "traitBonuses": [ + { + "bonus": 1 + } + ] } }, "name": "Beastform", @@ -46,266 +53,7 @@ "artist": "" } }, - "effects": [ - { - "name": "Evolution: Agility", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "vQOqLZAxOltAzsVv", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.agility.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      Toggle this for +1 to Agility when using Evolution. Turn it off when you leave Beastform.

      ", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.vQOqLZAxOltAzsVv" - }, - { - "name": "Evolution: Strength", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "cwEsO1NZpkQHuoTT", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.strength.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      Toggle this for +1 to Strength when using Evolution. Turn it off when you leave Beastform.

      ", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.cwEsO1NZpkQHuoTT" - }, - { - "name": "Evolution: Finesse", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "8P0nwRHNsVnHVPjq", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.finesse.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      Toggle this for +1 to Finesse when using Evolution. Turn it off when you leave Beastform.

      ", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.8P0nwRHNsVnHVPjq" - }, - { - "name": "Evolution: Instinct", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "i2GhNGo5TnGtLuA0", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.instinct.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      Toggle this for +1 to Instinct when using Evolution. Turn it off when you leave Beastform.

      ", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.i2GhNGo5TnGtLuA0" - }, - { - "name": "Evolution: Presence", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "APQF1in1LXjBZh9n", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.presence.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      Toggle this for +1 to Presence when using Evolution. Turn it off when you leave Beastform.

      ", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.APQF1in1LXjBZh9n" - }, - { - "name": "Evolution: Knowledge", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "WwOvGJYJb4d37cOy", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.knowledge.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

      Toggle this for +1 to Knowledge when using Evolution. Turn it off when you leave Beastform.

      ", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.WwOvGJYJb4d37cOy" - } - ], + "effects": [], "sort": 100000, "ownership": { "default": 0, diff --git a/styles/less/dialog/beastform/sheet.less b/styles/less/dialog/beastform/sheet.less index 9e87f53b..0e1fe746 100644 --- a/styles/less/dialog/beastform/sheet.less +++ b/styles/less/dialog/beastform/sheet.less @@ -204,6 +204,44 @@ } } + .modifications-container { + display: flex; + flex-direction: column; + gap: 16px; + + .trait-bonuses-container { + display: flex; + flex-direction: column; + gap: 8px; + + .bonus-separator { + background: light-dark(@dark-blue, @golden); + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + } + + .trait-bonus-container { + display: flex; + gap: 4px; + + .trait-card { + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + padding: 2px; + opacity: 0.4; + flex: 1; + white-space: nowrap; + text-align: center; + + &.selected { + opacity: 1; + } + } + } + } + } + footer { margin-top: 8px; display: flex; diff --git a/styles/less/sheets/actions/actions.less b/styles/less/sheets/actions/actions.less index 5c21dc60..5a18d622 100644 --- a/styles/less/sheets/actions/actions.less +++ b/styles/less/sheets/actions/actions.less @@ -133,4 +133,18 @@ height: 300px; } } + + .deletable-row { + display: flex; + align-items: end; + gap: 8px; + + input { + flex: 1; + } + + a { + padding-bottom: 7px; + } + } } diff --git a/templates/actionTypes/beastform.hbs b/templates/actionTypes/beastform.hbs index b9bea445..885038a1 100644 --- a/templates/actionTypes/beastform.hbs +++ b/templates/actionTypes/beastform.hbs @@ -1,4 +1,16 @@ +{{formGroup fields.tierAccess.fields.exact value=source.tierAccess.exact name="beastform.tierAccess.exact" labelAttr="label" valueAttr="key" localize=true blank=""}} +
      - {{localize "DAGGERHEART.ACTIONS.Config.beastform.label"}} - {{formGroup fields.tierAccess.fields.exact value=source.tierAccess.exact name="beastform.tierAccess.exact" labelAttr="label" valueAttr="key" localize=true blank=""}} + {{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.plural"}} + + {{#if source.modifications.traitBonuses.length}} + {{#each source.modifications.traitBonuses as |traitBonus index|}} +
      + {{formGroup ../fields.modifications.fields.traitBonuses.element.fields.bonus value=traitBonus.bonus name=(concat "beastform.modifications.traitBonuses." index ".bonus") localize=true}} + +
      + {{/each}} + {{else}} + {{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.hint"}} + {{/if}}
      \ No newline at end of file diff --git a/templates/dialogs/beastform/modifications.hbs b/templates/dialogs/beastform/modifications.hbs new file mode 100644 index 00000000..2563ea13 --- /dev/null +++ b/templates/dialogs/beastform/modifications.hbs @@ -0,0 +1,28 @@ +
      + {{#if modifications.traitBonuses.length}} +
      + + {{#if (gt modifications.traitBonuses.length 1)}} + {{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.plural"}} + {{else}} + {{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.single"}} + {{/if}} + + +
      + {{#each modifications.traitBonuses as |traitBonus index|}} +
      + {{#each @root.traits as |trait|}} + + {{localize trait.label}} +{{traitBonus.bonus}} + + {{/each}} +
      + {{#unless @last}}
      {{/unless}} + {{/each}} +
      +
      + {{/if}} +
      \ No newline at end of file From 24d22dde59728d3a8785c1ef5a912351fdb3bfc6 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 28 Mar 2026 03:01:50 +0100 Subject: [PATCH 050/304] [V14] [Feature] Spotlight Without Combat (#1755) --- lang/en.json | 3 +- module/applications/ui/combatTracker.mjs | 11 ++++- module/canvas/placeables/token.mjs | 30 +++++++++++++ module/config/settingsConfig.mjs | 3 +- module/data/_module.mjs | 1 + module/data/spotlightTracker.mjs | 9 ++++ module/macros/spotlightCombatant.mjs | 57 ++++++++++++++++++------ module/systemRegistration/settings.mjs | 14 +++++- 8 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 module/data/spotlightTracker.mjs diff --git a/lang/en.json b/lang/en.json index d341b4bf..cb020a93 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2555,8 +2555,7 @@ "MACROS": { "Spotlight": { "errors": { - "noActiveCombat": "There is no active encounter", - "noCombatantSelected": "A combatant token must be either selected or hovered to spotlight it" + "noTokenSelected": "A token on the canvas must either be selected or hovered to spotlight it" } } }, diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index 345c6fcd..1043e128 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -1,5 +1,6 @@ import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs'; import { expireActiveEffects } from '../../helpers/utils.mjs'; +import { clearPreviousSpotlight } from '../../macros/spotlightCombatant.mjs'; export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker { static DEFAULT_OPTIONS = { @@ -150,13 +151,13 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C } async setCombatantSpotlight(combatantId) { + const combatant = this.viewed.combatants.get(combatantId); const update = { system: { 'spotlight.requesting': false, 'spotlight.requestOrderIndex': 0 } }; - const combatant = this.viewed.combatants.get(combatantId); const toggleTurn = this.viewed.combatants.contents .sort(this.viewed._sortCombatants) @@ -187,6 +188,14 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C round: this.viewed.round + 1 }); await combatant.update(update); + if (combatant.token) clearPreviousSpotlight(); + } + + async clearTurn() { + await this.viewed.update({ + turn: null, + round: this.viewed.round + 1 + }); } static async requestSpotlight(_, target) { diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 35d34f83..77d8c3f4 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -10,6 +10,36 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { this.previewHelp ||= this.addChild(this.#drawPreviewHelp()); } + /**@inheritdoc */ + _refreshTurnMarker() { + // Should a Turn Marker be active? + const { turnMarker } = this.document; + const markersEnabled = + CONFIG.Combat.settings.turnMarker.enabled && turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED; + const spotlighted = game.settings + .get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker) + .spotlightedTokens.has(this.document.uuid); + + const turnIsSet = game.combat?.turn !== null; + const isTurn = game.combat?.combatant?.tokenId === this.id; + const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted; + + // Activate a Turn Marker + if (markerActive) { + if (!this.turnMarker) + this.turnMarker = this.addChildAt(new foundry.canvas.placeables.tokens.TokenTurnMarker(this), 0); + canvas.tokens.turnMarkers.add(this); + this.turnMarker.draw(); + } + + // Remove a Turn Marker + else if (this.turnMarker) { + canvas.tokens.turnMarkers.delete(this); + this.turnMarker.destroy(); + this.turnMarker = null; + } + } + /** @inheritDoc */ async _drawEffects() { this.effects.renderable = false; diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 36892731..de4d96be 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -39,7 +39,8 @@ export const gameSettings = { Countdowns: 'Countdowns', LastMigrationVersion: 'LastMigrationVersion', SpotlightRequestQueue: 'SpotlightRequestQueue', - CompendiumBrowserSettings: 'CompendiumBrowserSettings' + CompendiumBrowserSettings: 'CompendiumBrowserSettings', + SpotlightTracker: 'SpotlightTracker' }; export const actionAutomationChoices = { diff --git a/module/data/_module.mjs b/module/data/_module.mjs index 43ff7807..0e7e295e 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -4,6 +4,7 @@ export { default as DhRollTable } from './rollTable.mjs'; export { default as RegisteredTriggers } from './registeredTriggers.mjs'; export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs'; export { default as TagTeamData } from './tagTeamData.mjs'; +export { default as SpotlightTracker } from './spotlightTracker.mjs'; export * as countdowns from './countdowns.mjs'; export * as actions from './action/_module.mjs'; diff --git a/module/data/spotlightTracker.mjs b/module/data/spotlightTracker.mjs new file mode 100644 index 00000000..57f54e16 --- /dev/null +++ b/module/data/spotlightTracker.mjs @@ -0,0 +1,9 @@ +export default class SpotlightTracker extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + spotlightedTokens: new fields.SetField(new fields.DocumentUUIDField()) + }; + } +} diff --git a/module/macros/spotlightCombatant.mjs b/module/macros/spotlightCombatant.mjs index 68a26ff9..dc8339ac 100644 --- a/module/macros/spotlightCombatant.mjs +++ b/module/macros/spotlightCombatant.mjs @@ -1,21 +1,50 @@ /** - * Spotlights a combatant. - * The combatant can be selected in a number of ways. If many are applied at the same time, the following order is used: - * 1) SelectedCombatant - * 2) HoveredCombatant + * Spotlight a token on the canvas. If it is a combatant, run it through combatTracker's spotlight logic. + * @param {TokenDocument} token - The token to spotlight + * @returns {void} */ -const spotlightCombatant = () => { - if (!game.combat) - return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noActiveCombat')); +const spotlightCombatantMacro = async token => { + if (!token) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noTokenSelected')); - const selectedCombatant = canvas.tokens.controlled.length > 0 ? canvas.tokens.controlled[0].combatant : null; - const hoveredCombatant = game.canvas.tokens.hover?.combatant; + const combatantCombat = token.combatant + ? game.combat + : game.combats.find(combat => combat.combatants.some(x => x.token && x.token.id === token.document.id)); + if (combatantCombat) { + const combatant = combatantCombat.combatants.find(x => x.token.id === token.document.id); + if (!combatantCombat.active) { + await combatantCombat.activate(); + if (combatantCombat.combatant?.id !== combatant.id) ui.combat.setCombatantSpotlight(combatant.id); + } else { + ui.combat.setCombatantSpotlight(combatant.id); + } + } else { + if (game.combat) await ui.combat.clearTurn(); - const combatant = selectedCombatant ?? hoveredCombatant; - if (!combatant) - return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noCombatantSelected')); + const spotlightTracker = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker); + const isSpotlighted = spotlightTracker.spotlightedTokens.has(token.document.uuid); + if (!isSpotlighted) await clearPreviousSpotlight(); - ui.combat.setCombatantSpotlight(combatant.id); + spotlightTracker.updateSource({ + spotlightedTokens: isSpotlighted ? [] : [token.document.uuid] + }); + + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker); + token.renderFlags.set({ refreshTurnMarker: true }); + } }; -export default spotlightCombatant; +export const clearPreviousSpotlight = async () => { + const spotlightTracker = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker); + const previouslySpotlightedUuid = + spotlightTracker.spotlightedTokens.size > 0 ? spotlightTracker.spotlightedTokens.first() : null; + if (!previouslySpotlightedUuid) return; + + spotlightTracker.updateSource({ spotlightedTokens: [] }); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker); + + const previousToken = await foundry.utils.fromUuid(previouslySpotlightedUuid); + previousToken.object.renderFlags.set({ refreshTurnMarker: true }); +}; + +export default spotlightCombatantMacro; diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index 5eb9dad1..63611cda 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -16,6 +16,7 @@ import { DhVariantRuleSettings } from '../applications/settings/_module.mjs'; import { CompendiumBrowserSettings } from '../data/_module.mjs'; +import SpotlightTracker from '../data/spotlightTracker.mjs'; export const registerDHSettings = () => { registerKeyBindings(); @@ -40,7 +41,12 @@ export const registerKeyBindings = () => { hint: game.i18n.localize('DAGGERHEART.SETTINGS.Keybindings.spotlight.hint'), uneditable: [], editable: [], - onDown: game.system.api.macros.spotlightCombatant, + onDown: () => { + const selectedTokens = canvas.tokens.controlled.length > 0 ? canvas.tokens.controlled[0] : null; + const hoveredTokens = game.canvas.tokens.hover ? game.canvas.tokens.hover : null; + const tokens = selectedTokens ?? hoveredTokens; + game.system.api.macros.spotlightCombatant(tokens); + }, onUp: () => {}, restricted: true, reservedModifiers: [], @@ -177,4 +183,10 @@ const registerNonConfigSettings = () => { config: false, type: CompendiumBrowserSettings }); + + game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, { + scope: 'world', + config: false, + type: SpotlightTracker + }); }; From 740216ada2bc86d2a5b5aa517101649bd6006352 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 28 Mar 2026 03:09:41 +0100 Subject: [PATCH 051/304] Fixed spotlight case outside of combat --- module/canvas/placeables/token.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 77d8c3f4..77c178d6 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -20,7 +20,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { .get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker) .spotlightedTokens.has(this.document.uuid); - const turnIsSet = game.combat?.turn !== null; + const turnIsSet = typeof game.combat?.turn === 'number'; const isTurn = game.combat?.combatant?.tokenId === this.id; const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted; From e8f052faf3b5f671c1490600942cfc2c17bf8356 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 29 Mar 2026 11:07:16 +0200 Subject: [PATCH 052/304] Fixed drag/drop on application-sheet --- .../sheets/api/application-mixin.mjs | 35 ++++++------------- .../global/partials/inventory-item-V2.hbs | 2 +- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 83313454..64f62405 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -79,8 +79,6 @@ export default function DHApplicationMixin(Base) { */ constructor(options = {}) { super(options); - - this._setupDragDrop(); } /** @@ -175,9 +173,6 @@ export default function DHApplicationMixin(Base) { _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); - /* Core dragDrop from ActorDocument is always only 1. Possible we could refactor our own */ - if (Array.isArray(this._dragDrop)) this._dragDrop.forEach(d => d.bind(htmlElement)); - // Handle delta inputs for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) { deltaInput.dataset.numValue = deltaInput.value; @@ -289,6 +284,16 @@ export default function DHApplicationMixin(Base) { async _onRender(context, options) { await super._onRender(context, options); this._createTagifyElements(this.options.tagifyConfigs); + + for (const d of this.options.dragDrop) { + new foundry.applications.ux.DragDrop.implementation({ + ...d, + callbacks: { + dragstart: this._onDragStart.bind(this), + drop: this._onDrop.bind(this) + } + }).bind(this.element); + } } /* -------------------------------------------- */ @@ -349,26 +354,6 @@ export default function DHApplicationMixin(Base) { /* Drag and Drop */ /* -------------------------------------------- */ - /** - * Creates drag-drop handlers from the configured options. - * @returns {foundry.applications.ux.DragDrop[]} - * @private - */ - _setupDragDrop() { - if (this._dragDrop) { - this._dragDrop.callbacks.dragStart = this._onDragStart; - this._dragDrop.callback.drop = this._onDrop; - } else { - this._dragDrop = this.options.dragDrop.map(d => { - d.callbacks = { - dragstart: this._onDragStart.bind(this), - drop: this._onDrop.bind(this) - }; - return new foundry.applications.ux.DragDrop.implementation(d); - }); - } - } - /** * Handle dragStart event. * @param {DragEvent} event diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index a758a28f..2129b969 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -17,7 +17,7 @@ Parameters: - showActions {boolean} : If true show feature's actions. --}} -
    1. Date: Sun, 29 Mar 2026 12:50:34 +0200 Subject: [PATCH 053/304] Removed a temporary override function in document/token. Doesn't seem needed anymore, and it was outdated, making bar2 on tokens never visualy update --- module/documents/token.mjs | 58 -------------------------------------- 1 file changed, 58 deletions(-) diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 8e810689..4ee7ce05 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -494,62 +494,4 @@ export default class DHToken extends CONFIG.Token.documentClass { game.system.registeredTriggers.unregisterItemTriggers(this.actor.items); } } - - /* V14 TEMP until foundry fixes: https://discord.com/channels/170995199584108546/1421197211194228907/1467296028700049566 */ - _onRelatedUpdate(update = {}, operation = {}) { - this.#refreshOverrides(operation); - this._prepareBars(); - - // Update tracked Combat resource - const combatant = this.combatant; - if (combatant) { - const isActorUpdate = [this, null, undefined].includes(operation.parent); - const resource = game.combat.settings.resource; - const updates = Array.isArray(update) ? update : [update]; - if (isActorUpdate && resource && updates.some(u => foundry.utils.hasProperty(u.system ?? {}, resource))) { - combatant.updateResource(); - } - ui.combat.render(); - } - - // Trigger redraws on the token - if (this.parent.isView) { - if (this.object?.hasActiveHUD) canvas.tokens.hud.render(); - this.object?.renderFlags.set({ redrawEffects: true }); - for (const key of ['bar1', 'bar2']) { - const name = `${this.object?.objectId}.animate${key.capitalize()}`; - const easing = foundry.canvas.animation.CanvasAnimation.easeInOutCosine; - this.object?.animate({ [key]: this[key] }, { name, easing }); - } - for (const app of foundry.applications.sheets.TokenConfig.instances()) { - app._preview?.updateSource({ delta: this.toObject().delta }, { diff: false, recursive: false }); - app._preview?.object?.renderFlags.set({ refreshBars: true, redrawEffects: true }); - } - } - } - - /* V14 TEMP until foundry fixes: https://discord.com/channels/170995199584108546/1421197211194228907/1467296028700049566 */ - #refreshOverrides(operation) { - if (!this.actor) return; - - const { deepClone, mergeObject, equals, isEmpty } = foundry.utils; - const oldOverrides = deepClone(this._overrides) ?? {}; - const newOverrides = deepClone(this.actor?.tokenOverrides ?? {}, { prune: true }); - if (!equals(oldOverrides, newOverrides)) { - this._overrides = newOverrides; - this.reset(); - - // Send emulated update data to the PlaceableObject - if (!canvas.ready || canvas.scene !== this.scene) return; - const { width, height, depth, ...changes } = mergeObject( - mergeObject(oldOverrides, this, { insertKeys: false, insertValues: false }), - this._overrides - ); - this.object?._onUpdate(changes, {}, game.user.id); - - // Hand off size changes to a secondary handler requiring downstream implementation. - const sizeChanges = deepClone({ width, height, depth }, { prune: true }); - if (!isEmpty(sizeChanges)) this._onOverrideSize(sizeChanges, operation); - } - } } From dbd5ef8bb0b689510589b5de6348f5a981210bed Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 29 Mar 2026 23:54:45 +0200 Subject: [PATCH 054/304] Added fall and collision damage buttons in the GM Menu (#1756) --- lang/en.json | 9 ++++++- module/applications/sheets/api/base-actor.mjs | 1 - .../sidebar/tabs/daggerheartMenu.mjs | 22 ++++++++++++++- module/config/generalConfig.mjs | 27 +++++++++++++++++++ module/documents/item.mjs | 1 - styles/less/ui/sidebar/daggerheartMenu.less | 6 ++--- templates/sidebar/daggerheart-menu/main.hbs | 20 +++++++++++--- templates/ui/chat/chat-message.hbs | 23 +++++++++------- 8 files changed, 90 insertions(+), 19 deletions(-) diff --git a/lang/en.json b/lang/en.json index cb020a93..d4c81546 100755 --- a/lang/en.json +++ b/lang/en.json @@ -450,7 +450,8 @@ }, "DaggerheartMenu": { "title": "GM Tools", - "refreshFeatures": "Refresh Features" + "refreshFeatures": "Refresh Features", + "fallingAndCollision": "Falling And Collision Damage" }, "DeleteConfirmation": { "title": "Delete {type} - {name}", @@ -1155,6 +1156,12 @@ "description": "" } }, + "fallAndCollision": { + "veryClose": { "label": "Very Close", "chatTitle": "Fall Damage: Very Close" }, + "close": { "label": "Close", "chatTitle": "Fall Damage: Close" }, + "far": { "label": "Far", "chatTitle": "Fall Damage: Far" }, + "collision": { "label": "Collision", "chatTitle": "Dangerous Collision" } + }, "FeatureForm": { "label": "Feature Form", "passive": "Passive", diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 4b0fd7d9..4a550d72 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -228,7 +228,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { 'systems/daggerheart/templates/ui/chat/action.hbs', systemData ), - title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'), speaker: cls.getSpeaker(), flags: { daggerheart: { diff --git a/module/applications/sidebar/tabs/daggerheartMenu.mjs b/module/applications/sidebar/tabs/daggerheartMenu.mjs index 26ae484b..86c1b8cb 100644 --- a/module/applications/sidebar/tabs/daggerheartMenu.mjs +++ b/module/applications/sidebar/tabs/daggerheartMenu.mjs @@ -31,7 +31,8 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract }, actions: { selectRefreshable: DaggerheartMenu.#selectRefreshable, - refreshActors: DaggerheartMenu.#refreshActors + refreshActors: DaggerheartMenu.#refreshActors, + createFallCollisionDamage: DaggerheartMenu.#createFallCollisionDamage } }; @@ -50,6 +51,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract const context = await super._prepareContext(options); context.refreshables = this.refreshSelections; context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected); + context.fallAndCollision = CONFIG.DH.GENERAL.fallAndCollisionDamage; return context; } @@ -71,4 +73,22 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract this.refreshSelections = DaggerheartMenu.defaultRefreshSelections(); this.render(); } + + static async #createFallCollisionDamage(_event, button) { + const data = CONFIG.DH.GENERAL.fallAndCollisionDamage[button.dataset.key]; + const roll = new Roll(data.damageFormula); + await roll.evaluate(); + + /* class BaseRoll needed to get rendered by foundryRoll.hbs */ + const rollJSON = roll.toJSON(); + rollJSON.class = 'BaseRoll'; + + foundry.documents.ChatMessage.implementation.create({ + title: game.i18n.localize(data.chatTitle), + author: game.user.id, + speaker: foundry.documents.ChatMessage.implementation.getSpeaker(), + rolls: [rollJSON], + sound: CONFIG.sounds.dice + }); + } } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index c15cae56..f3484e43 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -1057,3 +1057,30 @@ export const activeEffectDurations = { label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.custom' } }; + +export const fallAndCollisionDamage = { + veryClose: { + id: 'veryClose', + label: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.label', + chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.chatTitle', + damageFormula: '1d10 + 3' + }, + close: { + id: 'veryClose', + label: 'DAGGERHEART.CONFIG.fallAndCollision.close.label', + chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.close.chatTitle', + damageFormula: '1d20 + 5' + }, + far: { + id: 'veryClose', + label: 'DAGGERHEART.CONFIG.fallAndCollision.far.label', + chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.far.chatTitle', + damageFormula: '1d100 + 15' + }, + collision: { + id: 'veryClose', + label: 'DAGGERHEART.CONFIG.fallAndCollision.collision.label', + chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.collision.chatTitle', + damageFormula: '1d20 + 5' + } +}; diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 56048a81..d1a618c7 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -197,7 +197,6 @@ export default class DHItem extends foundry.documents.Item { actor: item.parent, speaker: cls.getSpeaker(), system: systemData, - title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'), content: await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/ui/chat/ability-use.hbs', systemData diff --git a/styles/less/ui/sidebar/daggerheartMenu.less b/styles/less/ui/sidebar/daggerheartMenu.less index 677214d7..88b139c5 100644 --- a/styles/less/ui/sidebar/daggerheartMenu.less +++ b/styles/less/ui/sidebar/daggerheartMenu.less @@ -15,17 +15,17 @@ font-weight: bold; } - .menu-refresh-container { + .menu-options-container { display: flex; flex-direction: column; gap: 8px; - .menu-refresh-inner-container { + .menu-options-inner-container { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; - .experience-chip { + .option-chip { display: flex; align-items: center; border-radius: 5px; diff --git a/templates/sidebar/daggerheart-menu/main.hbs b/templates/sidebar/daggerheart-menu/main.hbs index 4d948409..a1ea8730 100644 --- a/templates/sidebar/daggerheart-menu/main.hbs +++ b/templates/sidebar/daggerheart-menu/main.hbs @@ -4,10 +4,10 @@
      {{localize "DAGGERHEART.APPLICATIONS.DaggerheartMenu.refreshFeatures"}} -
      + +
      + {{localize "DAGGERHEART.APPLICATIONS.DaggerheartMenu.fallingAndCollision"}} + + +
    2. diff --git a/templates/ui/chat/chat-message.hbs b/templates/ui/chat/chat-message.hbs index 94527f16..8a334b60 100644 --- a/templates/ui/chat/chat-message.hbs +++ b/templates/ui/chat/chat-message.hbs @@ -4,17 +4,22 @@
      - {{#unless actor.name}} -

      {{author.name}}

      + {{#if message.title}} +

      {{message.title}}

      +
      {{actor.name}} {{#if author.isGM}}(GM){{/if}}
      {{else}} - {{#if (eq message.type 'base')}} -

      {{actor.name}}

      -
      {{author.name}}
      + {{#unless actor.name}} +

      {{author.name}}

      {{else}} -

      {{ifThen message.title message.title alias}}

      -
      {{actor.name}} {{#if author.isGM}}(GM){{/if}}
      - {{/if}} - {{/unless}} + {{#if (eq message.type 'base')}} +

      {{actor.name}}

      +
      {{author.name}}
      + {{else}} +

      {{alias}}

      +
      {{actor.name}} {{#if author.isGM}}(GM){{/if}}
      + {{/if}} + {{/unless}} + {{/if}}
      \ No newline at end of file diff --git a/templates/actionTypes/save.hbs b/templates/actionTypes/save.hbs index 85536c87..fdadba54 100644 --- a/templates/actionTypes/save.hbs +++ b/templates/actionTypes/save.hbs @@ -2,8 +2,8 @@ {{localize "DAGGERHEART.GENERAL.Roll.reaction"}}

      {{localize "DAGGERHEART.ACTIONS.Settings.saveHint"}}

      - {{formField fields.trait label="Trait" name="save.trait" value=source.trait localize=true}} - {{formField fields.difficulty label="Difficulty" name="save.difficulty" value=source.difficulty disabled=(not source.trait) placeholder=@root.baseSaveDifficulty}} - {{formField fields.damageMod label="Damage on Save" name="save.damageMod" value=source.damageMod localize=true disabled=(not source.trait)}} + {{formField fields.trait label="DAGGERHEART.GENERAL.Trait.single" name="save.trait" value=source.trait localize=true}} + {{formField fields.difficulty label="DAGGERHEART.GENERAL.difficulty" name="save.difficulty" value=source.difficulty disabled=(not source.trait) placeholder=@root.baseSaveDifficulty}} + {{formField fields.damageMod label="DAGGERHEART.ACTIONS.Config.damageOnSave" name="save.damageMod" value=source.damageMod localize=true disabled=(not source.trait)}}
      \ No newline at end of file diff --git a/templates/sheets-settings/adversary-settings/attack.hbs b/templates/sheets-settings/adversary-settings/attack.hbs index c3ee21f6..f829338f 100644 --- a/templates/sheets-settings/adversary-settings/attack.hbs +++ b/templates/sheets-settings/adversary-settings/attack.hbs @@ -11,7 +11,7 @@
      {{localize "DAGGERHEART.GENERAL.attack"}}
      - {{formField systemFields.attack.fields.roll.fields.bonus value=document._source.system.attack.roll.bonus label="DAGGERHEART.ACTIONS.Settings.attackBonus" name="system.attack.roll.bonus" localize=true}} + {{formField systemFields.attack.fields.roll.fields.bonus value=document._source.system.attack.roll.bonus label="DAGGERHEART.ACTIONS.Settings.attackModifier" name="system.attack.roll.bonus" localize=true}} {{formField systemFields.attack.fields.range value=document._source.system.attack.range label="DAGGERHEART.GENERAL.range" name="system.attack.range" localize=true}} {{#if systemFields.attack.fields.target.fields}} {{ formField systemFields.attack.fields.target.fields.type value=document._source.system.attack.target.type label="DAGGERHEART.GENERAL.Target.single" name="system.attack.target.type" localize=true }} From 07479946867f42d5ed4c0513aaf05de23c2506aa Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 2 Apr 2026 16:00:04 +0200 Subject: [PATCH 069/304] Improved TagTeamRoll initialization when done by a player --- module/applications/dialogs/tagTeamDialog.mjs | 49 ++++++++++++++++--- .../dialogs/tagTeamDialog/initialization.hbs | 11 +++-- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 2d363471..5236afb8 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -16,9 +16,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ...member.toObject(), uuid: member.uuid, id: member.id, - selected: false + selected: false, + owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) })); - this.intiator = null; + + this.initiator = null; this.openForAllPlayers = true; this.tabGroups.application = Object.keys(party.system.tagTeam.members).length @@ -80,6 +82,18 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio for (const element of htmlElement.querySelectorAll('.roll-type-select')) element.addEventListener('change', this.updateRollType.bind(this)); + + htmlElement + .querySelector('.initiator-member-field') + ?.addEventListener('input', this.updateInitiatorMemberField.bind(this)); + + htmlElement + .querySelector('.initiator-cost-field') + ?.addEventListener('input', this.updateInitiatorCostField.bind(this)); + + htmlElement + .querySelector('.openforall-field') + ?.addEventListener('change', this.updateOpenForAllField.bind(this)); } _configureRenderParts(options) { @@ -135,9 +149,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const selectedMembers = partContext.memberSelection.filter(x => x.selected); partContext.allSelected = selectedMembers.length === 2; - partContext.canStartTagTeam = partContext.allSelected && this.initiator; + partContext.canStartTagTeam = + partContext.allSelected && this.initiator?.memberId && typeof this.initiator?.cost === 'number'; partContext.initiator = this.initiator; - partContext.initiatorOptions = selectedMembers.map(x => ({ value: x.id, label: x.name })); + partContext.initiatorOptions = selectedMembers + .filter(actor => actor.owned) + .map(x => ({ value: x.id, label: x.name })); partContext.initiatorDisabled = !selectedMembers.length; partContext.openForAllPlayers = this.openForAllPlayers; @@ -230,14 +247,15 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } static async updateData(event, _, formData) { - const { initiator, openForAllPlayers, ...partyData } = foundry.utils.expandObject(formData.object); - this.initiator = initiator; - this.openForAllPlayers = openForAllPlayers !== undefined ? openForAllPlayers : this.openForAllPlayers; + const partyData = foundry.utils.expandObject(formData.object); this.updatePartyData(partyData, this.getUpdatingParts(event.target)); } async updatePartyData(update, updatingParts, options = { render: true }) { + if (!game.users.activeGM) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired')); + const gmUpdate = async update => { await this.party.update(update); this.render({ parts: updatingParts }); @@ -374,6 +392,23 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ); } + updateInitiatorMemberField(event) { + if (!this.initiator) this.initiator = {}; + this.initiator.memberId = event.target.value; + this.render(); + } + + updateInitiatorCostField(event) { + if (!this.initiator) this.initiator = {}; + this.initiator.cost = event.target.value ? Number.parseInt(event.target.value) : null; + this.render(); + } + + updateOpenForAllField(event) { + this.openForAllPlayers = event.target.checked; + this.render(); + } + static async #removeRoll(_, button) { this.updatePartyData( { diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs index 604d06c0..d25e8f6c 100644 --- a/templates/dialogs/tagTeamDialog/initialization.hbs +++ b/templates/dialogs/tagTeamDialog/initialization.hbs @@ -17,19 +17,24 @@
      - {{selectOptions initiatorOptions selected=initiator.memberId blank="" }}
      - {{formGroup tagTeamFields.initiator.fields.cost name="initiator.cost" value=initiator.cost disabled=initiatorDisabled localize=true }} +
      + +
      + +
      +
      {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.openDialogForAll"}} - +
      \ No newline at end of file From be8d7f64698dbff7b1e510ed32d75a019b3a02a9 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 2 Apr 2026 17:07:52 +0200 Subject: [PATCH 070/304] Fixed so that tokens with vision range set to Infinity doesn't make summon actions error out --- module/documents/tokenManager.mjs | 2 +- system.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/module/documents/tokenManager.mjs b/module/documents/tokenManager.mjs index be5467da..f766a677 100644 --- a/module/documents/tokenManager.mjs +++ b/module/documents/tokenManager.mjs @@ -95,7 +95,7 @@ export default class DhTokenManager { : this.#actor; const tokenData = await actor.getTokenDocument(); const result = await canvas.scene.createEmbeddedDocuments('Token', [ - { ...tokenData, x: this.#activePreview.document.x, y: this.#activePreview.document.y } + { ...tokenData.toObject(), x: this.#activePreview.document.x, y: this.#activePreview.document.y } ]); this.#activePreview = undefined; diff --git a/system.json b/system.json index 9242a24a..a19d9e91 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.9.6", + "version": "1.9.7", "compatibility": { "minimum": "13.346", "verified": "13.351", From f92f9f7132a1a8d3d8265647bb8191104d9c131b Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 2 Apr 2026 17:10:13 +0200 Subject: [PATCH 071/304] Fixed so that tokens with vision range set to Infinity doesn't make summon actions error out --- module/documents/tokenManager.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/documents/tokenManager.mjs b/module/documents/tokenManager.mjs index be5467da..f766a677 100644 --- a/module/documents/tokenManager.mjs +++ b/module/documents/tokenManager.mjs @@ -95,7 +95,7 @@ export default class DhTokenManager { : this.#actor; const tokenData = await actor.getTokenDocument(); const result = await canvas.scene.createEmbeddedDocuments('Token', [ - { ...tokenData, x: this.#activePreview.document.x, y: this.#activePreview.document.y } + { ...tokenData.toObject(), x: this.#activePreview.document.x, y: this.#activePreview.document.y } ]); this.#activePreview = undefined; From 96eba49dc19524b6aee7386a4f73d2acf0b2a235 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 2 Apr 2026 22:04:07 +0200 Subject: [PATCH 072/304] Fixed RainofBlades --- ...nCard_Rain_of_Blades_Ucenef6JpjQxwXni.json | 55 +------------------ 1 file changed, 3 insertions(+), 52 deletions(-) diff --git a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json index 080dd67f..293490e3 100644 --- a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json +++ b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json @@ -44,7 +44,9 @@ "flatMultiplier": 1 }, "applyTo": "hitPoints", - "type": [], + "type": [ + "magical" + ], "base": false, "valueAlt": { "multiplier": "prof", @@ -87,57 +89,6 @@ "name": "Cast", "img": "icons/skills/melee/spear-tips-three-green.webp", "range": "veryClose" - }, - "CUKoYyDxQhNc0pLs": { - "type": "damage", - "_id": "CUKoYyDxQhNc0pLs", - "systemPath": "actions", - "description": "

      If a target you hit is Vulnerable, they take an extra 1d8 damage.

      ", - "chatDisplay": true, - "actionType": "action", - "cost": [], - "uses": { - "value": null, - "max": "", - "recovery": null - }, - "damage": { - "parts": { - "hitPoints": { - "value": { - "custom": { - "enabled": false - }, - "multiplier": "flat", - "flatMultiplier": 1, - "dice": "d8", - "bonus": null - }, - "applyTo": "hitPoints", - "type": [], - "base": false, - "resultBased": false, - "valueAlt": { - "multiplier": "prof", - "flatMultiplier": 1, - "dice": "d6", - "bonus": null, - "custom": { - "enabled": false - } - } - } - }, - "includeBase": false - }, - "target": { - "type": "any", - "amount": null - }, - "effects": [], - "name": "Damage Against Vulnerable", - "img": "icons/skills/melee/spear-tips-three-purple.webp", - "range": "" } }, "attribution": { From 36ffe8a23a7149b3451746a12d12a14a6db20c78 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 2 Apr 2026 22:18:18 +0200 Subject: [PATCH 073/304] Fixed labrys axe and unneccessary hr separations in enrichedItemDescriptions when prefix is empty --- .../weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json | 18 +++++------------- templates/sheets/items/armor/description.hbs | 8 ++++---- templates/sheets/items/weapon/description.hbs | 8 ++++---- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json b/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json index 11994d3e..d5af9b14 100644 --- a/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json +++ b/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json @@ -5,22 +5,14 @@ "_id": "ijWppQzSOqVCb3rE", "img": "icons/weapons/axes/axe-battle-jagged.webp", "system": { - "description": "", + "description": "Protective: +1 to Armor Score", "actions": {}, "attached": [], "tier": 3, "equipped": false, "secondary": false, "burden": "twoHanded", - "weaponFeatures": [ - { - "value": "protective", - "effectIds": [ - "qTxADRsQnKiYfOiQ" - ], - "actionIds": [] - } - ], + "weaponFeatures": [], "attack": { "name": "Attack", "img": "icons/skills/melee/blood-slash-foam-red.webp", @@ -111,8 +103,8 @@ "effects": [ { "name": "Protective", - "description": "Add your character's Tier to your Armor Score", - "img": "icons/skills/melee/shield-block-gray-orange.webp", + "description": "+1 to Armor Score", + "img": "icons/magic/defensive/shield-barrier-deflect-teal.webp", "_id": "vnR4Zhnb0rOqwrFw", "type": "base", "system": { @@ -122,7 +114,7 @@ "phase": "initial", "priority": 20, "value": { - "max": "ITEM.@system.tier" + "max": "1" } } ] diff --git a/templates/sheets/items/armor/description.hbs b/templates/sheets/items/armor/description.hbs index af2698ef..74e4e234 100644 --- a/templates/sheets/items/armor/description.hbs +++ b/templates/sheets/items/armor/description.hbs @@ -1,9 +1,9 @@ -
      - {{#if features.length}} +{{#if features.length}} +
      {{#each features as | feature |}}
      {{localize feature.label}}: {{{localize feature.description}}}
      {{/each}}
      - {{/if}} -
      \ No newline at end of file +
      +{{/if}} \ No newline at end of file diff --git a/templates/sheets/items/weapon/description.hbs b/templates/sheets/items/weapon/description.hbs index af2698ef..74e4e234 100644 --- a/templates/sheets/items/weapon/description.hbs +++ b/templates/sheets/items/weapon/description.hbs @@ -1,9 +1,9 @@ -
      - {{#if features.length}} +{{#if features.length}} +
      {{#each features as | feature |}}
      {{localize feature.label}}: {{{localize feature.description}}}
      {{/each}}
      - {{/if}} -
      \ No newline at end of file +
      +{{/if}} \ No newline at end of file From bbc521ece0384e0474be552f4e8759cea5549754 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 2 Apr 2026 22:30:49 +0200 Subject: [PATCH 074/304] Labrys axe, rain of blades fixes. Enriched Description improvement on armor and weapon --- ...nCard_Rain_of_Blades_Ucenef6JpjQxwXni.json | 61 +++---------------- .../weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json | 18 ++---- templates/sheets/items/armor/description.hbs | 8 +-- templates/sheets/items/weapon/description.hbs | 8 +-- 4 files changed, 20 insertions(+), 75 deletions(-) diff --git a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json index 839dc2c2..7a5ccb1b 100644 --- a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json +++ b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json @@ -36,7 +36,8 @@ "resultBased": false, "value": { "custom": { - "enabled": false + "enabled": false, + "formula": "" }, "multiplier": "prof", "dice": "d8", @@ -44,7 +45,9 @@ "flatMultiplier": 1 }, "applyTo": "hitPoints", - "type": [], + "type": [ + "magical" + ], "base": false, "valueAlt": { "multiplier": "prof", @@ -52,7 +55,8 @@ "dice": "d6", "bonus": null, "custom": { - "enabled": false + "enabled": false, + "formula": "" } } } @@ -87,57 +91,6 @@ "name": "Cast", "img": "icons/skills/melee/spear-tips-three-green.webp", "range": "veryClose" - }, - "CUKoYyDxQhNc0pLs": { - "type": "damage", - "_id": "CUKoYyDxQhNc0pLs", - "systemPath": "actions", - "description": "

      If a target you hit is Vulnerable, they take an extra 1d8 damage.

      ", - "chatDisplay": true, - "actionType": "action", - "cost": [], - "uses": { - "value": null, - "max": "", - "recovery": null - }, - "damage": { - "parts": [ - { - "value": { - "custom": { - "enabled": false - }, - "multiplier": "flat", - "flatMultiplier": 1, - "dice": "d8", - "bonus": null - }, - "applyTo": "hitPoints", - "type": [], - "base": false, - "resultBased": false, - "valueAlt": { - "multiplier": "prof", - "flatMultiplier": 1, - "dice": "d6", - "bonus": null, - "custom": { - "enabled": false - } - } - } - ], - "includeBase": false - }, - "target": { - "type": "any", - "amount": null - }, - "effects": [], - "name": "Damage Against Vulnerable", - "img": "icons/skills/melee/spear-tips-three-purple.webp", - "range": "" } }, "attribution": { diff --git a/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json b/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json index 4958bbe5..3c11528e 100644 --- a/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json +++ b/src/packs/items/weapons/weapon_Labrys_Axe_ijWppQzSOqVCb3rE.json @@ -5,22 +5,14 @@ "_id": "ijWppQzSOqVCb3rE", "img": "icons/weapons/axes/axe-battle-jagged.webp", "system": { - "description": "", + "description": "Protective: +1 to Armor Score", "actions": {}, "attached": [], "tier": 3, "equipped": false, "secondary": false, "burden": "twoHanded", - "weaponFeatures": [ - { - "value": "protective", - "effectIds": [ - "qTxADRsQnKiYfOiQ" - ], - "actionIds": [] - } - ], + "weaponFeatures": [], "attack": { "name": "Attack", "img": "icons/skills/melee/blood-slash-foam-red.webp", @@ -111,13 +103,13 @@ "effects": [ { "name": "Protective", - "description": "Add your character's Tier to your Armor Score", - "img": "icons/skills/melee/shield-block-gray-orange.webp", + "description": "+1 to Armor Score", + "img": "icons/magic/defensive/shield-barrier-deflect-teal.webp", "changes": [ { "key": "system.armorScore", "mode": 2, - "value": "ITEM.@system.tier" + "value": "1" } ], "_id": "qTxADRsQnKiYfOiQ", diff --git a/templates/sheets/items/armor/description.hbs b/templates/sheets/items/armor/description.hbs index af2698ef..086879d6 100644 --- a/templates/sheets/items/armor/description.hbs +++ b/templates/sheets/items/armor/description.hbs @@ -1,9 +1,9 @@ -
      - {{#if features.length}} +{{#if features.length}} +
      {{#each features as | feature |}}
      {{localize feature.label}}: {{{localize feature.description}}}
      {{/each}}
      - {{/if}} -
      \ No newline at end of file +
      +{{/if}} \ No newline at end of file diff --git a/templates/sheets/items/weapon/description.hbs b/templates/sheets/items/weapon/description.hbs index af2698ef..74e4e234 100644 --- a/templates/sheets/items/weapon/description.hbs +++ b/templates/sheets/items/weapon/description.hbs @@ -1,9 +1,9 @@ -
      - {{#if features.length}} +{{#if features.length}} +
      {{#each features as | feature |}}
      {{localize feature.label}}: {{{localize feature.description}}}
      {{/each}}
      - {{/if}} -
      \ No newline at end of file +
      +{{/if}} \ No newline at end of file From 32656137670acd507754ed94ab5c78be98a9a800 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 2 Apr 2026 23:22:09 +0200 Subject: [PATCH 075/304] [Fix] Template Scene Awareness (#1769) * . * Using foundry values from schema for fallback gridSize and gridDistance * . --- module/canvas/placeables/measuredTemplate.mjs | 2 +- module/enrichers/TemplateEnricher.mjs | 27 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/module/canvas/placeables/measuredTemplate.mjs b/module/canvas/placeables/measuredTemplate.mjs index e1ea79f5..e439dadc 100644 --- a/module/canvas/placeables/measuredTemplate.mjs +++ b/module/canvas/placeables/measuredTemplate.mjs @@ -18,7 +18,7 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur static getRangeLabels(distanceValue, settings) { let result = { distance: distanceValue, units: '' }; - if (!settings.enabled) return result; + if (!settings.enabled || !canvas.scene) return result; const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement; const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting; diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index fd0e78eb..1a075518 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -63,14 +63,12 @@ export const renderMeasuredTemplate = async event => { const usedAngle = type === CONE ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) : type === INFRONT ? '180' : undefined; - let baseDistance = range; - if (Number.isNaN(Number(range))) { - baseDistance = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement[ - range - ]; - } + let baseDistance = getTemplateDistance(range); - const dimensionConstant = game.scenes.active.grid.size / game.scenes.active.grid.distance; + const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields; + const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial; + const sceneGridDistance = canvas.scene?.grid.distance ?? distance.getInitialValue(); + const dimensionConstant = sceneGridSize / sceneGridDistance; baseDistance *= dimensionConstant; @@ -115,3 +113,18 @@ export const renderMeasuredTemplate = async event => { { create: true } ); }; + +const getTemplateDistance = range => { + const rangeNumber = Number(range); + if (!Number.isNaN(rangeNumber)) return rangeNumber; + + const { custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting; + const sceneMeasurements = canvas.scene?.flags.daggerheart?.rangeMeasurement; + const globalMeasurements = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.variantRules + ).rangeMeasurement; + + const settings = sceneMeasurements?.setting === custom.id ? sceneMeasurements : globalMeasurements; + return settings[range]; +}; From 8ee5db2832c2ff4cac365385595008b846a65338 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 2 Apr 2026 23:38:18 +0200 Subject: [PATCH 076/304] Fixed our templates taking custom scene distance into account --- module/canvas/placeables/measuredTemplate.mjs | 2 +- module/enrichers/TemplateEnricher.mjs | 28 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/module/canvas/placeables/measuredTemplate.mjs b/module/canvas/placeables/measuredTemplate.mjs index e1ea79f5..e439dadc 100644 --- a/module/canvas/placeables/measuredTemplate.mjs +++ b/module/canvas/placeables/measuredTemplate.mjs @@ -18,7 +18,7 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur static getRangeLabels(distanceValue, settings) { let result = { distance: distanceValue, units: '' }; - if (!settings.enabled) return result; + if (!settings.enabled || !canvas.scene) return result; const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement; const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting; diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index 4bc8fdeb..74462e00 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -65,13 +65,7 @@ export const renderMeasuredTemplate = async event => { ? '180' : undefined; - let baseDistance = range; - if (Number.isNaN(Number(range))) { - baseDistance = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement[ - range - ]; - } - const distance = type === CONFIG.DH.GENERAL.templateTypes.EMANATION ? baseDistance + 2.5 : baseDistance; + const distance = getTemplateDistance(range, type); const { width, height } = game.canvas.scene.dimensions; const data = { @@ -86,3 +80,23 @@ export const renderMeasuredTemplate = async event => { CONFIG.ux.TemplateManager.createPreview(data); }; + +const getTemplateDistance = (range, type) => { + const rangeNumber = Number(range); + if (!Number.isNaN(rangeNumber)) return rangeNumber; + + const { custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting; + const sceneMeasurements = canvas.scene?.flags.daggerheart?.rangeMeasurement; + const globalMeasurements = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.variantRules + ).rangeMeasurement; + + const settings = sceneMeasurements?.setting === custom.id ? sceneMeasurements : globalMeasurements; + const baseDistance = settings[range]; + + if (type !== CONFIG.DH.GENERAL.templateTypes.EMANATION) return baseDistance; + + const emanationAddDistance = settings.melee / 2; + return baseDistance + emanationAddDistance; +}; From f8df53ed8368928d241e810a08d6aba6fdb55b36 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 3 Apr 2026 00:10:32 +0200 Subject: [PATCH 077/304] Updated github deploy manifest to be the latest on the V13 branch --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index aef0ddd1..33910ac9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: env: version: ${{steps.get_version.outputs.version-without-v}} url: https://github.com/${{github.repository}} - manifest: https://github.com/${{github.repository}}/releases/latest/download/system.json + manifest: https://raw.githubusercontent.com/{{github.repository}}/V13/system.json download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip # Create a zip file with all files required by the module to add to the release From 02cca277da4f8de7b23bd1565c38122f81b61817 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 3 Apr 2026 00:13:07 +0200 Subject: [PATCH 078/304] Updated github deploy manifest to be the latest on the main branch --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index aef0ddd1..80b84704 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: env: version: ${{steps.get_version.outputs.version-without-v}} url: https://github.com/${{github.repository}} - manifest: https://github.com/${{github.repository}}/releases/latest/download/system.json + manifest: https://raw.githubusercontent.com/{{github.repository}}/main/system.json download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip # Create a zip file with all files required by the module to add to the release From 622b38ac08e280c0a254adfcf4184940018fd553 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 3 Apr 2026 19:05:49 +0200 Subject: [PATCH 079/304] Fixed certain fields in actionConfig not getting translated --- templates/actionTypes/range-target.hbs | 6 +++--- templates/actionTypes/roll.hbs | 8 ++++---- templates/actionTypes/save.hbs | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/templates/actionTypes/range-target.hbs b/templates/actionTypes/range-target.hbs index 143acdf8..114c76a6 100644 --- a/templates/actionTypes/range-target.hbs +++ b/templates/actionTypes/range-target.hbs @@ -1,12 +1,12 @@
      {{localize "DAGGERHEART.GENERAL.range"}}{{#if fields.target}} & {{localize "DAGGERHEART.GENERAL.Target.single"}}{{/if}} - {{formField fields.range value=source.range label="DAGGERHEART.GENERAL.range" name=(concat path "range") localize=true}} + {{formField fields.range value=source.range label=(localize "DAGGERHEART.GENERAL.range") name=(concat path "range") localize=true}} {{#if fields.target}}
      {{#if (and source.target.type (not (eq source.target.type 'self')))}} - {{ formField fields.target.amount value=source.target.amount label="DAGGERHEART.GENERAL.amount" name=(concat path "target.amount") localize=true}} + {{ formField fields.target.amount value=source.target.amount label=(localize "DAGGERHEART.GENERAL.amount") name=(concat path "target.amount") localize=true}} {{/if}} - {{ formField fields.target.type value=source.target.type label="DAGGERHEART.GENERAL.Target.single" name=(concat path "target.type") localize=true }} + {{ formField fields.target.type value=source.target.type label=(localize "DAGGERHEART.GENERAL.Target.single") name=(concat path "target.type") localize=true }}
      {{/if}}
      \ No newline at end of file diff --git a/templates/actionTypes/roll.hbs b/templates/actionTypes/roll.hbs index 2f257768..9784fc08 100644 --- a/templates/actionTypes/roll.hbs +++ b/templates/actionTypes/roll.hbs @@ -4,7 +4,7 @@ {{#if @root.hasBaseDamage}}{{formInput fields.useDefault name="roll.useDefault" value=source.useDefault dataset=(object tooltip="Use default Item values" tooltipDirection="UP")}}{{/if}} - {{formField fields.type label="Type" name="roll.type" value=source.type localize=true choices=@root.getRollTypeOptions}} + {{formField fields.type label="DAGGERHEART.GENERAL.type" name="roll.type" value=source.type localize=true choices=@root.getRollTypeOptions localize=true}} {{#if (eq source.type "diceSet")}}
      {{formField fields.diceRolling.fields.multiplier name="roll.diceRolling.multiplier" value=source.diceRolling.multiplier localize=true}} @@ -17,13 +17,13 @@
      {{#unless (eq source.type 'spellcast')}} {{#if @root.isNPC}} - {{formField fields.bonus label="DAGGERHEART.GENERAL.Modifier.single" name="roll.bonus" value=source.bonus placeholder=@root.baseAttackBonus disabled=(not source.type)}} + {{formField fields.bonus label="DAGGERHEART.GENERAL.Modifier.single" name="roll.bonus" value=source.bonus placeholder=@root.baseAttackBonus disabled=(not source.type) localize=true}} {{else}} {{formField fields.trait label="DAGGERHEART.GENERAL.Trait.single" name="roll.trait" value=source.trait localize=true disabled=(not source.type)}} {{/if}} {{/unless}} - {{formField fields.difficulty label="DAGGERHEART.GENERAL.difficulty" name="roll.difficulty" value=source.difficulty disabled=(not source.type)}} - {{formField fields.advState label="DAGGERHEART.ACTIONS.Config.advantageState" name="roll.advState" value=source.advState localize=true disabled=(not source.type)}} + {{formField fields.difficulty label="DAGGERHEART.GENERAL.difficulty" name="roll.difficulty" value=source.difficulty localize=true disabled=(not source.type)}} + {{formField fields.advState label="DAGGERHEART.ACTIONS.Config.advantageState" name="roll.advState" value=source.advState localize=true localize=true disabled=(not source.type)}}
      {{/if}}
      \ No newline at end of file diff --git a/templates/actionTypes/save.hbs b/templates/actionTypes/save.hbs index fdadba54..865991c7 100644 --- a/templates/actionTypes/save.hbs +++ b/templates/actionTypes/save.hbs @@ -3,7 +3,7 @@

      {{localize "DAGGERHEART.ACTIONS.Settings.saveHint"}}

      {{formField fields.trait label="DAGGERHEART.GENERAL.Trait.single" name="save.trait" value=source.trait localize=true}} - {{formField fields.difficulty label="DAGGERHEART.GENERAL.difficulty" name="save.difficulty" value=source.difficulty disabled=(not source.trait) placeholder=@root.baseSaveDifficulty}} - {{formField fields.damageMod label="DAGGERHEART.ACTIONS.Config.damageOnSave" name="save.damageMod" value=source.damageMod localize=true disabled=(not source.trait)}} + {{formField fields.difficulty label="DAGGERHEART.GENERAL.difficulty" name="save.difficulty" value=source.difficulty disabled=(not source.trait) placeholder=@root.baseSaveDifficulty localize=true}} + {{formField fields.damageMod label="DAGGERHEART.ACTIONS.Config.damageOnSave" name="save.damageMod" value=source.damageMod localize=true localize=true disabled=(not source.trait)}}
      \ No newline at end of file From 01619ef0672ef913f41af05f81b25d49d8131309 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 3 Apr 2026 19:15:20 +0200 Subject: [PATCH 080/304] Corrected github deploy manifest path --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 80b84704..e245c7fa 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: env: version: ${{steps.get_version.outputs.version-without-v}} url: https://github.com/${{github.repository}} - manifest: https://raw.githubusercontent.com/{{github.repository}}/main/system.json + manifest: https://raw.githubusercontent.com/${{github.repository}}/main/system.json download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip # Create a zip file with all files required by the module to add to the release From d7b37f8178c0fb17aee92056193c7879d8ccdba6 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 3 Apr 2026 20:17:20 +0200 Subject: [PATCH 081/304] Fixed faulty deploy script --- .github/workflows/deploy.yml | 2 +- system.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 33910ac9..5e2f9ac7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: env: version: ${{steps.get_version.outputs.version-without-v}} url: https://github.com/${{github.repository}} - manifest: https://raw.githubusercontent.com/{{github.repository}}/V13/system.json + manifest: https://raw.githubusercontent.com/${{github.repository}}/V13/system.json download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip # Create a zip file with all files required by the module to add to the release diff --git a/system.json b/system.json index a19d9e91..cc26de16 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.9.7", + "version": "1.9.8", "compatibility": { "minimum": "13.346", "verified": "13.351", From 3a117ef117f9de1d712076eae5235023c550477d Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:32:30 +0200 Subject: [PATCH 082/304] Added evasion to party resources (#1771) --- .../less/sheets/actors/party/resources.less | 20 +++++++++++++++++++ system.json | 2 +- templates/sheets/actors/party/resources.hbs | 6 ++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/styles/less/sheets/actors/party/resources.less b/styles/less/sheets/actors/party/resources.less index 4db254bf..68628295 100644 --- a/styles/less/sheets/actors/party/resources.less +++ b/styles/less/sheets/actors/party/resources.less @@ -155,6 +155,26 @@ body.game:is(.performance-low, .noblur) { } } + .stat-section { + position: relative; + display: flex; + gap: 10px; + background-color: light-dark(transparent, @dark-blue); + color: light-dark(@dark-blue, @golden); + padding: 5px 10px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 3px; + align-items: center; + width: fit-content; + + h4 { + font-size: var(--font-size-12); + font-weight: bold; + text-transform: uppercase; + color: light-dark(@dark-blue, @golden); + } + } + .threshold-section { display: flex; align-self: center; diff --git a/system.json b/system.json index ea71aaba..78355364 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.0.1", + "version": "2.0.2", "compatibility": { "minimum": "14.359", "verified": "14.359", diff --git a/templates/sheets/actors/party/resources.hbs b/templates/sheets/actors/party/resources.hbs index bfbfb578..b53282ca 100644 --- a/templates/sheets/actors/party/resources.hbs +++ b/templates/sheets/actors/party/resources.hbs @@ -87,6 +87,12 @@ {{/unless}} + {{#if (eq actor.type 'character')}} +
      +

      {{localize "DAGGERHEART.GENERAL.evasion"}}: {{actor.system.evasion}}

      +
      + {{/if}} + {{#unless (eq actor.type 'companion')}}

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

      From f91c140d34eaf8da276e14f29d1456666edfed18 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 4 Apr 2026 11:48:41 +0200 Subject: [PATCH 083/304] Fixed so that multi term expressions get evaluated into a single number (#1772) --- module/data/activeEffect/changeTypes/armor.mjs | 3 ++- system.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs index f400d41b..713ef03d 100644 --- a/module/data/activeEffect/changeTypes/armor.mjs +++ b/module/data/activeEffect/changeTypes/armor.mjs @@ -44,7 +44,8 @@ export default class ArmorChange extends foundry.abstract.DataModel { label: 'Armor', defaultPriority: 20, handler: (actor, change, _options, _field, replacementData) => { - const parsedMax = itemAbleRollParse(change.value.max, actor, change.effect.parent); + const baseParsedMax = itemAbleRollParse(change.value.max, actor, change.effect.parent); + const parsedMax = new Roll(baseParsedMax).evaluateSync().total; game.system.api.documents.DhActiveEffect.applyChange( actor, { diff --git a/system.json b/system.json index 78355364..63dc33c2 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.0.2", + "version": "2.0.3", "compatibility": { "minimum": "14.359", "verified": "14.359", From 331f1ebf75a2221beaec7985f47dc642aeef8fa7 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 4 Apr 2026 12:42:50 +0200 Subject: [PATCH 084/304] Fixed prose-mirror width --- styles/less/global/prose-mirror.less | 1 + 1 file changed, 1 insertion(+) diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index 506fb8b7..3523dc89 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -4,6 +4,7 @@ .application.daggerheart { prose-mirror { height: 100% !important; + width: 100%; .editor-menu { background-color: transparent; From 7057504a9eb3684b268617cf6e6ea73b699f659c Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:01:24 +0200 Subject: [PATCH 085/304] Fixes (#1774) --- module/dice/dhRoll.mjs | 6 +++--- module/dice/die/_module.mjs | 4 ++++ module/dice/die/dualityDie.mjs | 14 +++++++++++--- module/dice/die/fearDie.mjs | 9 +++++++++ module/dice/die/hopeDie.mjs | 9 +++++++++ module/dice/dualityRoll.mjs | 20 ++++++++++---------- 6 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 module/dice/die/fearDie.mjs create mode 100644 module/dice/die/hopeDie.mjs diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index e4a34bd4..aa4dd75f 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -246,7 +246,7 @@ export default class DHRoll extends Roll { return (this._formula = this.constructor.getFormula(this.terms)); } - /** + /** * Calculate total modifiers of any rolls, including non-dh rolls. * This exists because damage rolls still may receive base roll classes */ @@ -256,7 +256,7 @@ export default class DHRoll extends Roll { if (!roll.terms[i].isDeterministic) continue; const termTotal = roll.terms[i].total; if (typeof termTotal === 'number') { - const multiplier = roll.terms[i - 1]?.operator === " - " ? -1 : 1; + const multiplier = roll.terms[i - 1]?.operator === ' - ' ? -1 : 1; modifierTotal += multiplier * termTotal; } } @@ -272,7 +272,7 @@ export default class DHRoll extends Roll { const changeKeys = this.getActionChangeKeys(); return ( this.options.effects?.reduce((acc, effect) => { - if (effect.system.changes.some(x => changeKeys.some(key => x.key.includes(key)))) { + if (effect.system.changes.some(x => changeKeys.some(key => x.key?.includes(key)))) { acc[effect.id] = { id: effect.id, name: effect.name, diff --git a/module/dice/die/_module.mjs b/module/dice/die/_module.mjs index ed892f6a..19ca951a 100644 --- a/module/dice/die/_module.mjs +++ b/module/dice/die/_module.mjs @@ -1,9 +1,13 @@ import DualityDie from './dualityDie.mjs'; +import HopeDie from './hopeDie.mjs'; +import FearDie from './fearDie.mjs'; import AdvantageDie from './advantageDie.mjs'; import DisadvantageDie from './disadvantageDie.mjs'; export const diceTypes = { DualityDie, + HopeDie, + FearDie, AdvantageDie, DisadvantageDie }; diff --git a/module/dice/die/dualityDie.mjs b/module/dice/die/dualityDie.mjs index e9deb77f..83229425 100644 --- a/module/dice/die/dualityDie.mjs +++ b/module/dice/die/dualityDie.mjs @@ -43,9 +43,10 @@ export default class DualityDie extends foundry.dice.terms.Die { options: { appearance: {} } }; - const preset = await getDiceSoNicePreset(diceSoNice[key], faces); - diceSoNiceRoll.dice[0].options.appearance = preset.appearance; - diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile; + const diceAppearance = await this.getDiceSoNiceAppearance(options.liveRoll.roll); + diceSoNiceRoll.dice[0].options.appearance = diceAppearance.appearance; + diceSoNiceRoll.dice[0].options.modelFile = diceAppearance.modelFile; + diceSoNiceRoll.dice[0].results = diceSoNiceRoll.dice[0].results.filter(x => x.active); await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); } else { @@ -59,4 +60,11 @@ export default class DualityDie extends foundry.dice.terms.Die { this.#updateResources(oldDuality, newDuality, options.liveRoll.actor); } } + + /** + * Overridden by extending classes HopeDie and FearDie + */ + async getDiceSoNiceAppearance() { + return {}; + } } diff --git a/module/dice/die/fearDie.mjs b/module/dice/die/fearDie.mjs new file mode 100644 index 00000000..2a09d432 --- /dev/null +++ b/module/dice/die/fearDie.mjs @@ -0,0 +1,9 @@ +import { getDiceSoNicePresets } from '../../config/generalConfig.mjs'; +import DualityDie from './dualityDie.mjs'; + +export default class FearDie extends DualityDie { + async getDiceSoNiceAppearance(roll) { + const { fear } = await getDiceSoNicePresets(roll, this.denomination, this.denomination); + return fear; + } +} diff --git a/module/dice/die/hopeDie.mjs b/module/dice/die/hopeDie.mjs new file mode 100644 index 00000000..af5a4425 --- /dev/null +++ b/module/dice/die/hopeDie.mjs @@ -0,0 +1,9 @@ +import { getDiceSoNicePresets } from '../../config/generalConfig.mjs'; +import DualityDie from './dualityDie.mjs'; + +export default class HopeDie extends DualityDie { + async getDiceSoNiceAppearance(roll) { + const { hope } = await getDiceSoNicePresets(roll, this.denomination, this.denomination); + return hope; + } +} diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index bc381f07..f9a06d37 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -24,7 +24,7 @@ export default class DualityRoll extends D20Roll { } get dHope() { - if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); + if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.HopeDie)) this.createBaseDice(); return this.dice[0]; } @@ -34,7 +34,7 @@ export default class DualityRoll extends D20Roll { } get dFear() { - if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); + if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.FearDie)) this.createBaseDice(); return this.dice[1]; } @@ -68,8 +68,8 @@ export default class DualityRoll extends D20Roll { } get extraDice() { - const { DualityDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes; - return this.dice.filter(x => ![DualityDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die)); + const { HopeDie, FearDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes; + return this.dice.filter(x => ![HopeDie, FearDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die)); } setRallyChoices() { @@ -125,8 +125,8 @@ export default class DualityRoll extends D20Roll { /** @inheritDoc */ static fromData(data) { - data.terms[0].class = 'DualityDie'; - data.terms[2].class = 'DualityDie'; + data.terms[0].class = 'HopeDie'; + data.terms[2].class = 'FearDie'; if (data.options.roll.advantage?.type && data.terms[4]?.faces) { data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie'; } @@ -135,18 +135,18 @@ export default class DualityRoll extends D20Roll { createBaseDice() { if ( - this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie && - this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie + this.dice[0] instanceof game.system.api.dice.diceTypes.HopeDie && + this.dice[1] instanceof game.system.api.dice.diceTypes.FearDie ) { this.terms = [this.terms[0], this.terms[1], this.terms[2]]; return; } - this.terms[0] = new game.system.api.dice.diceTypes.DualityDie({ + this.terms[0] = new game.system.api.dice.diceTypes.HopeDie({ faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12 }); this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' }); - this.terms[2] = new game.system.api.dice.diceTypes.DualityDie({ + this.terms[2] = new game.system.api.dice.diceTypes.FearDie({ faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12 }); } From 70e21f34db33f884d315af0c2729d17723f2b44f Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 4 Apr 2026 13:21:17 +0200 Subject: [PATCH 086/304] Corrected system.json --- system.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system.json b/system.json index 63dc33c2..28d849b3 100644 --- a/system.json +++ b/system.json @@ -297,7 +297,7 @@ "background": "systems/daggerheart/assets/logos/FoundrybornBackgroundLogo.png", "primaryTokenAttribute": "resources.hitPoints", "secondaryTokenAttribute": "resources.stress", - "url": "https://your/hosted/system/repo/", - "manifest": "https://your/hosted/system/repo/system.json", - "download": "https://your/packaged/download/archive.zip" + "url": "https://github.com/Foundryborne/daggerheart", + "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/main/system.json", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.0.3/system.zip" } From 0d7469801e7123588f736b43debdaa6e29eccd4e Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:22:25 +0200 Subject: [PATCH 087/304] Updated the longrest repair armor to the new armor max path along with a migration (#1777) --- module/config/generalConfig.mjs | 2 +- module/systemRegistration/migrations.mjs | 13 +++++++++++++ system.json | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index f3484e43..24f1de92 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -484,7 +484,7 @@ export const defaultRestOptions = { value: { custom: { enabled: true, - formula: '@system.armorScore' + formula: '@system.armorScore.max' } } } diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index 458ee6ef..2851f7d4 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -1,3 +1,4 @@ +import { defaultRestOptions } from '../config/generalConfig.mjs'; import { RefreshType, socketEvent } from './socket.mjs'; export async function runMigrations() { @@ -341,6 +342,18 @@ export async function runMigrations() { lastMigrationVersion = '2.0.0'; } + + if (foundry.utils.isNewerVersion('2.0.4', lastMigrationVersion)) { + const downtimeMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew); + if (restMoves.longRest.moves.repairArmor) { + await downtimeMoves.updateSource({ + 'restMoves.longRest.moves.repairArmor': defaultRestOptions.longRest().repairArmor + }); + game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, downtimeMoves.toObject()); + } + + lastMigrationVersion = '2.0.4'; + } //#endregion await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion); diff --git a/system.json b/system.json index 28d849b3..e237f538 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.0.3", + "version": "2.0.4", "compatibility": { "minimum": "14.359", "verified": "14.359", From 90f433989810d7a9098023a9f279a06c43d32788 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 5 Apr 2026 10:23:02 +0200 Subject: [PATCH 088/304] Restoring current version number --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index e237f538..28d849b3 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.0.4", + "version": "2.0.3", "compatibility": { "minimum": "14.359", "verified": "14.359", From dbcef140a263297c0783dcea4dd1489d33ad40d7 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 5 Apr 2026 11:09:00 +0200 Subject: [PATCH 089/304] Fixed armorEffects erroring on isSuppressed when not on an actor --- module/data/activeEffect/changeTypes/armor.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs index 713ef03d..2f3b9765 100644 --- a/module/data/activeEffect/changeTypes/armor.mjs +++ b/module/data/activeEffect/changeTypes/armor.mjs @@ -111,6 +111,8 @@ export default class ArmorChange extends foundry.abstract.DataModel { }; get isSuppressed() { + if (!this.parent.parent?.actor) return false; + switch (this.value.interaction) { case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id: return !this.parent.parent?.actor.system.armor; From fdfd8c5a8d40cc1eb99b84fbfe8cf29dd6d89ab5 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 5 Apr 2026 11:28:41 +0200 Subject: [PATCH 090/304] Fixed selecting which roll to use in TagTeamRolls becoming impossible when using an Ability option --- module/applications/dialogs/tagTeamDialog.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 5236afb8..054331b5 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -366,8 +366,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio let rollIsSelected = false; for (const member of Object.values(members)) { const rollFinished = Boolean(member.rollData); - const damageFinished = - member.rollData?.options?.hasDamage !== undefined ? member.rollData.options.damage : true; + const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true; rollsAreFinished = rollsAreFinished && rollFinished && damageFinished; rollIsSelected = rollIsSelected || member.selected; From 67d142df3d5ae908293fbae74da8d35cb96f5b4b Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 5 Apr 2026 17:27:02 +0200 Subject: [PATCH 091/304] Fixed migration --- module/systemRegistration/migrations.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index 2851f7d4..c2c53f4e 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -345,7 +345,7 @@ export async function runMigrations() { if (foundry.utils.isNewerVersion('2.0.4', lastMigrationVersion)) { const downtimeMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew); - if (restMoves.longRest.moves.repairArmor) { + if (downtimeMoves.restMoves.longRest.moves.repairArmor) { await downtimeMoves.updateSource({ 'restMoves.longRest.moves.repairArmor': defaultRestOptions.longRest().repairArmor }); From 4c2d31b2f4dd7b76e4c6374bd6af3cf40a1cf56a Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:28:27 +0200 Subject: [PATCH 092/304] Fixed so that expanded damage info without any dice will show the correct value (#1780) --- templates/ui/chat/parts/damage-part.hbs | 41 +++++++++++++------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/templates/ui/chat/parts/damage-part.hbs b/templates/ui/chat/parts/damage-part.hbs index 02519a86..45b09b72 100644 --- a/templates/ui/chat/parts/damage-part.hbs +++ b/templates/ui/chat/parts/damage-part.hbs @@ -33,31 +33,32 @@
      {{total}}
      {{/if}}
      - {{#each dice}} - {{#each results}} - {{#unless discarded}} -
      -
      - {{#if hasRerolls}}{{/if}} - {{result}} + {{#if dice.length}} + {{#each dice}} + {{#each results}} + {{#unless discarded}} +
      +
      + {{#if hasRerolls}}{{/if}} + {{result}} +
      -
      - {{/unless}} + {{/unless}} + {{/each}} {{/each}} - {{/each}} - {{#if modifierTotal}} -
      -
      {{modifierTotal}}
      -
      - {{/if}} - {{#unless dice.length}} + {{#if modifierTotal}} +
      +
      {{modifierTotal}}
      +
      + {{/if}} + {{else}}
      {{total}}
      - {{/unless}} + {{/if}}
      {{/each}} From fad830580ccdde5347aaeb7364449f1ff6a29494 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 6 Apr 2026 00:18:43 +0200 Subject: [PATCH 093/304] Added checkboxes for muting the sounds of dice animations (#1781) --- lang/en.json | 1 + module/config/generalConfig.mjs | 4 ++-- module/data/settings/Appearance.mjs | 3 +++ .../appearance-settings/diceSoNice.less | 24 +++++++++++++++++++ .../appearance-settings/diceSoNice.hbs | 11 +++++++-- .../appearance-settings/diceSoNiceTab.hbs | 10 ++++++-- 6 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lang/en.json b/lang/en.json index d19dfb58..7b1e5744 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2857,6 +2857,7 @@ "system": "Dice Preset", "font": "Font", "critical": "Duality Critical Animation", + "muted": "Muted", "diceAppearance": "Dice Appearance", "animations": "Animations", "defaultAnimations": "Set Animations As Player Defaults", diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 24f1de92..918cb417 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -708,14 +708,14 @@ const getDiceSoNiceSFX = sfxOptions => { if (sfxOptions.critical && criticalAnimationData.class) { return { specialEffect: criticalAnimationData.class, - options: {} + options: { ...criticalAnimationData.options } }; } if (sfxOptions.higher && sfxOptions.data.higher) { return { specialEffect: sfxOptions.data.higher.class, - options: {} + options: { ...sfxOptions.data.higher.options } }; } diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs index 9da3afac..4db27be0 100644 --- a/module/data/settings/Appearance.mjs +++ b/module/data/settings/Appearance.mjs @@ -8,6 +8,9 @@ export default class DhAppearance extends foundry.abstract.DataModel { initial: null, blank: true, choices: CONFIG.DH.GENERAL.diceSoNiceSFXClasses + }), + options: new foundry.data.fields.SchemaField({ + muteSound: new foundry.data.fields.BooleanField() }) }); diff --git a/styles/less/ui/settings/appearance-settings/diceSoNice.less b/styles/less/ui/settings/appearance-settings/diceSoNice.less index 079bebc5..a4846596 100644 --- a/styles/less/ui/settings/appearance-settings/diceSoNice.less +++ b/styles/less/ui/settings/appearance-settings/diceSoNice.less @@ -68,5 +68,29 @@ text-align: center; white-space: nowrap; } + + color-picker { + gap: 4px; + background: inherit; + } + } + + .animation-container { + display: flex; + align-items: center; + justify-content: space-between; + + .animation-inner-container { + display: flex; + align-items: center; + justify-content: right; + gap: 8px; + + .animation-control { + display: flex; + align-items: center; + gap: 2px; + } + } } } diff --git a/templates/settings/appearance-settings/diceSoNice.hbs b/templates/settings/appearance-settings/diceSoNice.hbs index afe7dd5a..6cd4e52e 100644 --- a/templates/settings/appearance-settings/diceSoNice.hbs +++ b/templates/settings/appearance-settings/diceSoNice.hbs @@ -9,9 +9,16 @@ {{/if}}
      +
      - {{formInput fields.diceSoNice.fields.sfx.fields.critical.fields.class value=setting.diceSoNice.sfx.critical.class blank="" localize=true}} -
      +
      + {{formInput fields.diceSoNice.fields.sfx.fields.critical.fields.class value=setting.diceSoNice.sfx.critical.class blank="" localize=true}} +
      + +
      + {{formInput fields.diceSoNice.fields.sfx.fields.critical.fields.options.fields.muteSound value=setting.diceSoNice.sfx.critical.options.muteSound localize=true}} +
      +
      diff --git a/templates/settings/appearance-settings/diceSoNiceTab.hbs b/templates/settings/appearance-settings/diceSoNiceTab.hbs index a15b63ec..89b587af 100644 --- a/templates/settings/appearance-settings/diceSoNiceTab.hbs +++ b/templates/settings/appearance-settings/diceSoNiceTab.hbs @@ -42,9 +42,15 @@ {{#if animations}}

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

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

      {{source.name}}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      -

      Party

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

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

        +

        {{member.damageThresholds.major}}

        +

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

        +

        {{member.damageThresholds.severe}}

        +

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

        +
        + {{/unless}} +
        +
        +

        + {{member.name}} + +

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

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

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

        {{actor.name}}

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

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

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

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

        -

        {{actor.system.damageThresholds.major}}

        -

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

        -

        {{actor.system.damageThresholds.severe}}

        -

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

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

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

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

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

      +

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

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

      {{localize "Modifiers"}}

      +

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

      {{#each toggles as |toggle|}}
      From d12220c64f2b16f41c97610151f496fbbc82e751 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 11 Apr 2026 22:55:41 +0200 Subject: [PATCH 113/304] Fixes (#1790) * Fixes * . --- lang/en.json | 37 +++++++++++++++++-- .../settings/appearanceSettings.mjs | 9 ++++- .../applications/sheets/actors/adversary.mjs | 4 +- module/applications/sheets/actors/party.mjs | 2 +- .../sheets/api/application-mixin.mjs | 4 +- .../sidebar/tabs/actorDirectory.mjs | 2 +- module/applications/ui/fearTracker.mjs | 2 +- module/applications/ui/itemBrowser.mjs | 17 ++++++++- module/applications/ux/filter-menu.mjs | 2 +- module/config/generalConfig.mjs | 6 +-- module/config/itemBrowserConfig.mjs | 19 ++++++++-- module/config/settingsConfig.mjs | 10 ++--- module/data/fields/action/damageField.mjs | 2 +- module/documents/chatMessage.mjs | 4 +- templates/characterCreation/footer.hbs | 4 +- templates/dialogs/characterReset.hbs | 2 +- templates/dialogs/deathMove.hbs | 2 +- .../dialogs/dice-roll/damageSelection.hbs | 2 +- templates/dialogs/dice-roll/rollSelection.hbs | 2 +- templates/dialogs/downtime/downtime.hbs | 2 +- templates/dialogs/image-select/footer.hbs | 2 +- templates/dialogs/multiclassChoice.hbs | 2 +- templates/levelup/tabs/footer.hbs | 2 +- .../settings/automation-settings/footer.hbs | 4 +- templates/settings/downtime-config/footer.hbs | 2 +- .../settings/homebrew-settings/footer.hbs | 4 +- .../settings/metagaming-settings/footer.hbs | 4 +- templates/settings/variant-rules.hbs | 4 +- .../sheets/actors/character/inventory.hbs | 2 +- templates/sheets/actors/character/loadout.hbs | 2 +- templates/sheets/actors/party/inventory.hbs | 2 +- .../sheets/actors/party/party-members.hbs | 4 +- templates/ui/chat/parts/target-part.hbs | 2 +- templates/ui/itemBrowser/itemBrowser.hbs | 12 +++--- templates/ui/tooltip/action.hbs | 2 +- templates/ui/tooltip/adversary.hbs | 2 +- templates/ui/tooltip/battlepoints.hbs | 4 +- 37 files changed, 124 insertions(+), 66 deletions(-) diff --git a/lang/en.json b/lang/en.json index feeb65b2..1766b218 100755 --- a/lang/en.json +++ b/lang/en.json @@ -198,7 +198,10 @@ "type": { "label": "Type" } }, "hordeDamage": "Horde Damage", - "horderHp": "Horde/HP" + "horderHp": "Horde/HP", + "adversaryReactionRoll": { + "headerTitle": "Adversary Reaction Roll" + } }, "Character": { "advantageSources": { @@ -440,6 +443,10 @@ "defaultOwnershipTooltip": "The default player ownership of countdowns", "hideNewCountdowns": "Hide New Countdowns" }, + "CreateItemDialog": { + "createItem": "Create Item", + "browseCompendium": "Browse Compendium" + }, "DaggerheartMenu": { "title": "GM Tools", "refreshFeatures": "Refresh Features" @@ -653,6 +660,12 @@ "noPlayers": "No players to assign ownership to", "default": "Default Ownership" }, + "PendingReactionsDialog": { + "title": "Pending Reaction Rolls Found", + "unfinishedRolls": "Some Tokens still need to roll their Reaction Roll.", + "confirmation": "Are you sure you want to continue ?", + "warning": "Undone reaction rolls will be considered as failed" + }, "ReactionRoll": { "title": "Reaction Roll: {trait}" }, @@ -684,7 +697,13 @@ "damageNotRolled": "Damage not rolled in chat message yet", "insufficientHope": "The initiating character doesn't have enough hope", "createTagTeam": "Create TagTeam Roll", - "chatMessageRollTitle": "Roll" + "chatMessageRollTitle": "Roll", + "cancelConfirmTitle": "Cancel Tag Team Roll", + "cancelConfirmText": "Are you sure you want to cancel the Tag Team Roll? This will close it for all other players too.", + "hints": { + "completeRolls": "Set up and complete the rolls for the characters", + "selectRoll": "Select which roll value to be used for the Tag Team" + } }, "TokenConfig": { "actorSizeUsed": "Actor size is set, determining the dimensions" @@ -1215,6 +1234,11 @@ "on": "On", "onWithToggle": "On With Toggle" }, + "SceneRangeMeasurementTypes": { + "disable": "Disable Daggerheart Range Measurement", + "default": "Default", + "custom": "Custom" + }, "SelectAction": { "selectType": "Select Action Type", "selectAction": "Action Selection" @@ -2305,6 +2329,7 @@ "multiclass": "Multiclass", "newCategory": "New Category", "newThing": "New {thing}", + "next": "Next", "none": "None", "noTarget": "No current target", "partner": "Partner", @@ -2332,6 +2357,7 @@ "scalable": "Scalable", "scars": "Scars", "situationalBonus": "Situational Bonus", + "searchPlaceholder": "Search...", "spent": "Spent", "step": "Step", "stress": "Stress", @@ -2915,6 +2941,9 @@ "resourceRoll": { "playerMessage": "{user} rerolled their {name}" }, + "saveRoll": { + "reactionRollAllTargets": "Reaction Roll All Targets" + }, "tagTeam": { "title": "Tag Team", "membersTitle": "Members" @@ -2943,7 +2972,6 @@ "title": "Daggerheart Compendium Browser", "hint": "Select a Folder in sidebar to start browsing through the compendium", "browserSettings": "Browser Settings", - "searchPlaceholder": "Search...", "columnName": "Name", "tooltipFilters": "Filters", "tooltipErase": "Erase", @@ -2979,7 +3007,7 @@ "weapons": "Weapons", "armors": "Armors", "consumables": "Consumables", - "loots": "Loots" + "loots": "Loot" } }, "Notifications": { @@ -3070,6 +3098,7 @@ "companion": "Level {level} - {partner}", "companionNoPartner": "No Partner", "duplicateToNewTier": "Duplicate to New Tier", + "createAdversary": "Create Adversary", "pickTierTitle": "Pick a new tier for this adversary" }, "daggerheartMenu": { diff --git a/module/applications/settings/appearanceSettings.mjs b/module/applications/settings/appearanceSettings.mjs index 151648e1..64e5a076 100644 --- a/module/applications/settings/appearanceSettings.mjs +++ b/module/applications/settings/appearanceSettings.mjs @@ -118,8 +118,13 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App break; case 'footer': partContext.buttons = [ - { type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', label: 'Reset' }, - { type: 'submit', icon: 'fa-solid fa-floppy-disk', label: 'Save Changes' } + { + type: 'button', + action: 'reset', + icon: 'fa-solid fa-arrow-rotate-left', + label: game.i18n.localize('ACTIONS.Reset') + }, + { type: 'submit', icon: 'fa-solid fa-floppy-disk', label: game.i18n.localize('EDITOR.Save') } ]; break; } diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index d8a3df29..04be3efb 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -217,8 +217,8 @@ export default class AdversarySheet extends DHBaseActorSheet { static #reactionRoll(event) { const config = { event, - title: `Reaction Roll: ${this.actor.name}`, - headerTitle: 'Adversary Reaction Roll', + title: game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll'), + headerTitle: game.i18n.localize('DAGGERHEART.ACTORS.Adversary.adversaryReactionRoll.headerTitle'), roll: { type: 'trait' }, diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 1b1722db..53316c8b 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -229,7 +229,7 @@ export default class Party extends DHBaseActorSheet { title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`), icon: button.dataset.type === 'shortRest' ? 'fa-solid fa-utensils' : 'fa-solid fa-bed' }, - content: 'This will trigger a dialog to players make their downtime moves, are you sure?', + content: game.i18n.localize('DAGGERHEART.ACTORS.Party.triggerRestContent'), classes: ['daggerheart', 'dialog', 'dh-style'] }); diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index b18176ec..baa4d173 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -652,12 +652,12 @@ export default function DHApplicationMixin(Base) { buttons: [ { action: 'create', - label: 'Create Item', + label: game.i18n.localize('DAGGERHEART.APPLICATIONS.CreateItemDialog.createItem'), icon: 'fa-solid fa-plus' }, { action: 'browse', - label: 'Browse Compendium', + label: game.i18n.localize('DAGGERHEART.APPLICATIONS.CreateItemDialog.browseCompendium'), icon: 'fa-solid fa-book' } ] diff --git a/module/applications/sidebar/tabs/actorDirectory.mjs b/module/applications/sidebar/tabs/actorDirectory.mjs index 9d8f16e1..e9484553 100644 --- a/module/applications/sidebar/tabs/actorDirectory.mjs +++ b/module/applications/sidebar/tabs/actorDirectory.mjs @@ -76,7 +76,7 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs. window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' }, content, ok: { - label: 'Create Adversary', + label: 'DAGGERHEART.UI.Sidebar.actorDirectory.createAdversary', callback: (event, button, dialog) => Number(button.form.elements.tier.value) } }); diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs index 82dda215..4e5e1132 100644 --- a/module/applications/ui/fearTracker.mjs +++ b/module/applications/ui/fearTracker.mjs @@ -22,7 +22,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV tag: 'div', window: { frame: true, - title: 'Fear', + title: 'DAGGERHEART.GENERAL.fear', positioned: true, resizable: true, minimizable: false diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 2d2e8cdc..9ca328a0 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -207,8 +207,23 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { label: game.i18n.localize(col.label) })); + const splitPath = folderId?.split('.') ?? []; + const { pathLabels } = splitPath.reduce( + (acc, curr) => { + acc.currentPath = !acc.currentPath ? curr : [acc.currentPath, curr].join('.'); + if (curr === 'folder') return acc; + + const label = foundry.utils.getProperty(this.config, acc.currentPath)?.label; + if (label) acc.pathLabels.push(game.i18n.localize(label)); + + return acc; + }, + { pathLabels: [], currentPath: '' } + ); + this.selectedMenu = { - path: folderId?.split('.') ?? [], + path: splitPath, + pathLabels: pathLabels, data: { ...folderData, columns: columns diff --git a/module/applications/ux/filter-menu.mjs b/module/applications/ux/filter-menu.mjs index 065d08f9..791c0e1f 100644 --- a/module/applications/ux/filter-menu.mjs +++ b/module/applications/ux/filter-menu.mjs @@ -188,7 +188,7 @@ export default class FilterMenu extends foundry.applications.ux.ContextMenu { })); const damageTypeFilter = Object.values(CONFIG.DH.GENERAL.damageTypes).map(({ id, abbreviation }) => ({ - group: 'Damage Type', //TODO localize + group: game.i18n.localize('DAGGERHEART.GENERAL.damageType'), name: game.i18n.localize(abbreviation), filter: { field: 'system.damage.type', diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index f1c21d26..2557e562 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -943,14 +943,14 @@ export const countdownAppMode = { export const sceneRangeMeasurementSetting = { disable: { id: 'disable', - label: 'Disable Daggerheart Range Measurement' + label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.disable' }, default: { id: 'default', - label: 'Default' + label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.default' }, custom: { id: 'custom', - label: 'Custom' + label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.custom' } }; diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 3b10409c..0a4154a8 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -7,7 +7,12 @@ export const typeConfig = { }, { key: 'system.type', - label: 'DAGGERHEART.GENERAL.type' + label: 'DAGGERHEART.GENERAL.type', + format: type => { + if (!type) return '-'; + + return CONFIG.DH.ACTOR.allAdversaryTypes()[type].label; + } } ], filters: [ @@ -318,7 +323,14 @@ export const typeConfig = { }, { key: 'system.domains', - label: 'DAGGERHEART.GENERAL.Domain.plural' + label: 'DAGGERHEART.GENERAL.Domain.plural', + format: domains => { + const config = CONFIG.DH.DOMAIN.allDomains(); + return domains + .map(x => (x ? game.i18n.localize(config[x].label) : null)) + .filter(x => x) + .join(', '); + } } ], filters: [ @@ -367,7 +379,8 @@ export const typeConfig = { }, { key: 'system.spellcastingTrait', - label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait' + label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait', + format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-') } ], filters: [ diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index c19e6e26..8036d789 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -46,18 +46,14 @@ export const gameSettings = { export const actionAutomationChoices = { never: { id: 'never', - label: 'Never' + label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.never' }, showDialog: { id: 'showDialog', - label: 'Show Dialog only' + label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.showDialog' }, - // npcOnly: { - // id: "npcOnly", - // label: "Always for non-characters" - // }, always: { id: 'always', - label: 'Always' + label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.always' } }; diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 6439344b..6c091f8b 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -295,7 +295,7 @@ export class DHDamageData extends DHResourceData { required: true }), { - label: 'Type' + label: game.i18n.localize('DAGGERHEART.GENERAL.type') } ) }; diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 53921329..acc14439 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -197,8 +197,8 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { const pendingingSaves = targets.filter(t => t.saved.success === null); if (pendingingSaves.length) { const confirm = await foundry.applications.api.DialogV2.confirm({ - window: { title: 'Pending Reaction Rolls found' }, - content: `

      Some Tokens still need to roll their Reaction Roll.

      Are you sure you want to continue ?

      Undone reaction rolls will be considered as failed

      ` + window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') }, + content: `

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}

      ` }); if (!confirm) return; } diff --git a/templates/characterCreation/footer.hbs b/templates/characterCreation/footer.hbs index 95b86cfb..51eef110 100644 --- a/templates/characterCreation/footer.hbs +++ b/templates/characterCreation/footer.hbs @@ -1,8 +1,8 @@ \ No newline at end of file diff --git a/templates/dialogs/characterReset.hbs b/templates/dialogs/characterReset.hbs index 298826e5..59f88437 100644 --- a/templates/dialogs/characterReset.hbs +++ b/templates/dialogs/characterReset.hbs @@ -28,6 +28,6 @@
      - +
      \ No newline at end of file diff --git a/templates/dialogs/deathMove.hbs b/templates/dialogs/deathMove.hbs index 341659df..97900022 100644 --- a/templates/dialogs/deathMove.hbs +++ b/templates/dialogs/deathMove.hbs @@ -17,7 +17,7 @@
      - +
      {{/each}} {{#unless (empty @root.modifiers)}} diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index 5139c58a..569f5e16 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -175,7 +175,7 @@ {{/if}} {{#if (eq @root.rollType 'DualityRoll')}}{{localize "DAGGERHEART.GENERAL.situationalBonus"}}{{/if}} - + {{/if}} {{/unless}} diff --git a/templates/dialogs/downtime/downtime.hbs b/templates/dialogs/downtime/downtime.hbs index 9744ffea..edaa81c1 100644 --- a/templates/dialogs/downtime/downtime.hbs +++ b/templates/dialogs/downtime/downtime.hbs @@ -28,7 +28,7 @@
      - +
      \ No newline at end of file diff --git a/templates/dialogs/image-select/footer.hbs b/templates/dialogs/image-select/footer.hbs index cd7d3d1a..58a60cc4 100644 --- a/templates/dialogs/image-select/footer.hbs +++ b/templates/dialogs/image-select/footer.hbs @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/templates/dialogs/multiclassChoice.hbs b/templates/dialogs/multiclassChoice.hbs index 3c89ff1a..55365939 100644 --- a/templates/dialogs/multiclassChoice.hbs +++ b/templates/dialogs/multiclassChoice.hbs @@ -16,7 +16,7 @@
      - +
      diff --git a/templates/levelup/tabs/footer.hbs b/templates/levelup/tabs/footer.hbs index 2ee7a316..d487e657 100644 --- a/templates/levelup/tabs/footer.hbs +++ b/templates/levelup/tabs/footer.hbs @@ -20,7 +20,7 @@ {{/if}} {{#unless levelupAuto}} {{/unless}} diff --git a/templates/settings/automation-settings/footer.hbs b/templates/settings/automation-settings/footer.hbs index 54939c17..7e9d1991 100644 --- a/templates/settings/automation-settings/footer.hbs +++ b/templates/settings/automation-settings/footer.hbs @@ -1,10 +1,10 @@
      \ No newline at end of file diff --git a/templates/settings/downtime-config/footer.hbs b/templates/settings/downtime-config/footer.hbs index 5e5f31dd..199aea15 100644 --- a/templates/settings/downtime-config/footer.hbs +++ b/templates/settings/downtime-config/footer.hbs @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/templates/settings/homebrew-settings/footer.hbs b/templates/settings/homebrew-settings/footer.hbs index 954572de..ba1b5ada 100644 --- a/templates/settings/homebrew-settings/footer.hbs +++ b/templates/settings/homebrew-settings/footer.hbs @@ -1,10 +1,10 @@
      \ No newline at end of file diff --git a/templates/settings/metagaming-settings/footer.hbs b/templates/settings/metagaming-settings/footer.hbs index 54939c17..7e9d1991 100644 --- a/templates/settings/metagaming-settings/footer.hbs +++ b/templates/settings/metagaming-settings/footer.hbs @@ -1,10 +1,10 @@
      \ No newline at end of file diff --git a/templates/settings/variant-rules.hbs b/templates/settings/variant-rules.hbs index df7accb3..31316dc6 100644 --- a/templates/settings/variant-rules.hbs +++ b/templates/settings/variant-rules.hbs @@ -32,11 +32,11 @@
      diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index a05fed35..711d0c9f 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -5,7 +5,7 @@
      - + diff --git a/templates/sheets/actors/character/loadout.hbs b/templates/sheets/actors/character/loadout.hbs index 0319d56f..5e4c9f54 100644 --- a/templates/sheets/actors/character/loadout.hbs +++ b/templates/sheets/actors/character/loadout.hbs @@ -5,7 +5,7 @@
      - +
      diff --git a/templates/sheets/actors/party/inventory.hbs b/templates/sheets/actors/party/inventory.hbs index 92371b8d..186e2e99 100644 --- a/templates/sheets/actors/party/inventory.hbs +++ b/templates/sheets/actors/party/inventory.hbs @@ -5,7 +5,7 @@
      - +
      diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs index b5903cfc..84e0cddf 100644 --- a/templates/sheets/actors/party/party-members.hbs +++ b/templates/sheets/actors/party/party-members.hbs @@ -7,11 +7,11 @@
      {{!-- NOT YET IMPLEMENTED --}} {{!--
      {{/if}} - {{#if (and hasSave currentTargets.length)}}
      Reaction Roll All Targets
      {{/if}} + {{#if (and hasSave currentTargets.length)}}
      {{localize "DAGGERHEART.UI.Chat.saveRoll.reactionRollAllTargets"}}
      {{/if}} {{#each currentTargets}}
      diff --git a/templates/ui/itemBrowser/itemBrowser.hbs b/templates/ui/itemBrowser/itemBrowser.hbs index 137693fc..d4946c1f 100644 --- a/templates/ui/itemBrowser/itemBrowser.hbs +++ b/templates/ui/itemBrowser/itemBrowser.hbs @@ -1,14 +1,14 @@
      {{#if menu.path.length }}
      @@ -17,7 +17,7 @@
      - +
      diff --git a/templates/ui/tooltip/action.hbs b/templates/ui/tooltip/action.hbs index 29d44dde..959188f3 100644 --- a/templates/ui/tooltip/action.hbs +++ b/templates/ui/tooltip/action.hbs @@ -16,7 +16,7 @@ {{#if (gt item.cost.length 0)}} {{#each item.cost as | cost |}}
      - {{localize "Type"}} {{#with (lookup @root.config.GENERAL.abilityCosts cost.type) as | type |}}{{localize type.label}}{{/with}} + {{localize "DAGGERHEART.GENERAL.type"}} {{#with (lookup @root.config.GENERAL.abilityCosts cost.type) as | type |}}{{localize type.label}}{{/with}}
      {{localize "DAGGERHEART.GENERAL.value"}} {{cost.value}} diff --git a/templates/ui/tooltip/adversary.hbs b/templates/ui/tooltip/adversary.hbs index bba7e696..f96fe2a0 100644 --- a/templates/ui/tooltip/adversary.hbs +++ b/templates/ui/tooltip/adversary.hbs @@ -11,7 +11,7 @@ {{/with}}
      - + {{#with (lookup adversaryTypes item.system.type) as | type |}}
      {{localize type.label}}
      {{/with}} diff --git a/templates/ui/tooltip/battlepoints.hbs b/templates/ui/tooltip/battlepoints.hbs index d793fe8c..9672698a 100644 --- a/templates/ui/tooltip/battlepoints.hbs +++ b/templates/ui/tooltip/battlepoints.hbs @@ -1,6 +1,6 @@
      -

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

      +

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

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

      {{localize "Modifiers"}}

      +

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

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

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

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

      +

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

      {{else}} -

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

      +

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

      {{/if}}

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

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

      {{item.name}}

      - {{#if item.system.secondary}}{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}}{{else}}{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon"}}{{/if}} + {{#if item.system.secondary}}{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full"}}{{else}}{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon.full"}}{{/if}}
      {{#with (lookup config.GENERAL.burden item.system.burden) as | burden |}} From cc5bfbe27d5a5e679b101fae80a4f8cdc05ef9fd Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 12 Apr 2026 11:38:37 +0200 Subject: [PATCH 127/304] Fixed more missing translations (#1793) --- lang/en.json | 20 ++++++++++++----- module/applications/ui/itemBrowser.mjs | 2 +- module/config/itemBrowserConfig.mjs | 22 +++++++++++-------- .../footer.hbs | 2 +- templates/sheets/items/weapon/header.hbs | 4 ++-- templates/ui/tooltip/battlepoints.hbs | 6 ++--- templates/ui/tooltip/weapon.hbs | 2 +- 7 files changed, 36 insertions(+), 22 deletions(-) diff --git a/lang/en.json b/lang/en.json index dd2f31ab..d4ed02c6 100755 --- a/lang/en.json +++ b/lang/en.json @@ -71,9 +71,7 @@ "name": "Summon", "tooltip": "Create tokens in the scene.", "error": "You do not have permission to summon tokens or there is no active scene.", - "invalidDrop": "You can only drop Actor entities to summon.", - "chatMessageTitle": "Test2", - "chatMessageHeaderTitle": "Summoning" + "invalidDrop": "You can only drop Actor entities to summon." }, "transform": { "name": "Transform", @@ -1916,6 +1914,10 @@ "hint": "Multiply any damage dealt to you by this number" } }, + "Battlepoints": { + "full": "Battlepoints", + "short": "BP" + }, "Bonuses": { "rest": { "downtimeAction": "Downtime Action", @@ -2352,6 +2354,7 @@ "rollDamage": "Roll Damage", "rollWith": "{roll} Roll", "save": "Save", + "saveSettings": "Save Settings", "scalable": "Scalable", "scars": "Scars", "situationalBonus": "Situational Bonus", @@ -2506,8 +2509,14 @@ }, "Weapon": { "weaponType": "Weapon Type", - "primaryWeapon": "Primary Weapon", - "secondaryWeapon": "Secondary Weapon" + "primaryWeapon": { + "full": "Primary Weapon", + "short": "Primary" + }, + "secondaryWeapon": { + "full": "Secondary Weapon", + "short": "Secondary" + } } }, "MACROS": { @@ -2968,6 +2977,7 @@ }, "ItemBrowser": { "title": "Daggerheart Compendium Browser", + "windowTitle": "Compendium Browser", "hint": "Select a Folder in sidebar to start browsing through the compendium", "browserSettings": "Browser Settings", "columnName": "Name", diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 9ca328a0..22de38ab 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -37,7 +37,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { tag: 'div', window: { frame: true, - title: 'Compendium Browser', + title: 'DAGGERHEART.UI.ItemBrowser.windowTitle', icon: 'fa-solid fa-book-atlas', positioned: true, resizable: true diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 0a4154a8..f20e56d0 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -74,12 +74,13 @@ export const typeConfig = { columns: [ { key: 'type', - label: 'DAGGERHEART.GENERAL.type' + label: 'DAGGERHEART.GENERAL.type', + format: type => type ? `TYPES.Item.${type}` : '-' }, { key: 'system.secondary', label: 'DAGGERHEART.UI.ItemBrowser.subtype', - format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-') + format: isSecondary => (isSecondary ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' : '-') }, { key: 'system.tier', @@ -99,8 +100,8 @@ export const typeConfig = { key: 'system.secondary', label: 'DAGGERHEART.UI.ItemBrowser.subtype', choices: [ - { value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' }, - { value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' } + { value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' }, + { value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' } ] }, { @@ -258,11 +259,13 @@ export const typeConfig = { columns: [ { key: 'system.type', - label: 'DAGGERHEART.GENERAL.type' + label: 'DAGGERHEART.GENERAL.type', + format: type => type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-' }, { key: 'system.domain', - label: 'DAGGERHEART.GENERAL.Domain.single' + label: 'DAGGERHEART.GENERAL.Domain.single', + format: domain => domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-' }, { key: 'system.level', @@ -374,7 +377,7 @@ export const typeConfig = { columns: [ { key: 'system.linkedClass', - label: 'Class', + label: 'TYPES.Item.class', format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing' }, { @@ -386,7 +389,7 @@ export const typeConfig = { filters: [ { key: 'system.linkedClass.uuid', - label: 'Class', + label: 'TYPES.Item.class', choices: items => { const list = items .filter(item => item.system.linkedClass) @@ -410,7 +413,8 @@ export const typeConfig = { }, { key: 'system.mainTrait', - label: 'DAGGERHEART.GENERAL.Trait.single' + label: 'DAGGERHEART.GENERAL.Trait.single', + format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-') } ], filters: [ diff --git a/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs b/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs index 9dc61cbe..d9bb378e 100644 --- a/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs +++ b/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs @@ -1,3 +1,3 @@
      - +
      \ No newline at end of file diff --git a/templates/sheets/items/weapon/header.hbs b/templates/sheets/items/weapon/header.hbs index 349a9516..2f51d6d3 100644 --- a/templates/sheets/items/weapon/header.hbs +++ b/templates/sheets/items/weapon/header.hbs @@ -5,9 +5,9 @@

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

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

      +

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

      {{else}} -

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

      +

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

      {{/if}}

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

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

      {{item.name}}

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

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

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

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

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

      Until you or an ally rolls a failure with Fear.

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

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

      ", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -160,6 +162,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!Ef1JsUG50LIoKx2F.s7ma4TNgAvt0ZgEW" } ], diff --git a/templates/sheets/activeEffect/settings.hbs b/templates/sheets/activeEffect/settings.hbs index 9307ff65..09b78856 100644 --- a/templates/sheets/activeEffect/settings.hbs +++ b/templates/sheets/activeEffect/settings.hbs @@ -26,11 +26,11 @@ {{formGroup systemFields.duration.fields.type value=source.system.duration.type localize=true }} -
      -
      - {{formInput systemFields.duration.fields.description value=source.system.duration.description localize=true }} -
      +
      +
      + {{formInput systemFields.duration.fields.description value=source.system.duration.description localize=true }}
      +
      {{formGroup fields.start.fields.time value=source.start.time localize=true }} From c4d314171bc90d8598439d3b60af528593f82449 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:46:53 +0200 Subject: [PATCH 132/304] Corrected SRD (#1797) --- .../feature_Retract_UFR67BUOhNGLFyg9.json | 2 +- ...eature_Low_Light_Living_aMla3xQuCHEwORGD.json | 16 ++-------------- .../domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json | 4 ++-- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json index b17cd7da..9a1c434b 100644 --- a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json +++ b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json @@ -80,7 +80,7 @@ { "key": "system.disadvantageSources", "mode": 2, - "value": "Retract", + "value": "Action Rolls", "priority": null } ], diff --git a/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json b/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json index f1ed3ace..f40baa40 100644 --- a/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json +++ b/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json @@ -35,19 +35,7 @@ { "key": "system.advantageSources", "mode": 2, - "value": "In an area with low light or heavy shadow: hide, investigate, or perceive", - "priority": null - }, - { - "key": "system.advantageSources", - "mode": 2, - "value": "", - "priority": null - }, - { - "key": "", - "mode": 2, - "value": "", + "value": "Rolls to hide, investigate, or perceive details in low light", "priority": null } ], @@ -61,7 +49,7 @@ "startRound": null, "startTurn": null }, - "description": "", + "description": "

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

      ", "origin": null, "tint": "#ffffff", "transfer": true, diff --git a/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json b/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json index 432f9992..6ca03c41 100644 --- a/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json +++ b/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json @@ -138,7 +138,7 @@ { "key": "system.advantageSources", "mode": 2, - "value": "1", + "value": "Attack Rolls", "priority": null } ], @@ -152,7 +152,7 @@ "startRound": null, "startTurn": null }, - "description": "", + "description": "

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

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

      {{source.name}}

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

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

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

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

        + {{member.name}} + +

        + {{#if member.subtitle}} + {{member.subtitle}} + {{/if}} +
        +
      • + {{/each}} +
      + {{/if}} {{#unless document.system.partyMembers.length}}
      {{localize "DAGGERHEART.GENERAL.dropActorsHere"}} From 0128106de63e54ad55e45ecadd78a8dbda8e6715 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 4 May 2026 06:40:29 -0400 Subject: [PATCH 207/304] Don't show unusable non-weapons in character sidebar (#1862) --- module/applications/sheets/actors/character.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index b20fe513..7adffa1e 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -228,7 +228,7 @@ export default class CharacterSheet extends DHBaseActorSheet { context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; context.equippedItems = sortBy( - this.document.items.filter(i => i.system.equipped), + this.document.items.filter(i => i.system.equipped && (i.type === 'weapon' || i.usable)), i => (i.type === 'weapon' ? (i.system.secondary ? 1 : 0) : 2) ); From 94852cec2168491387fd361e3345aa1643b26add Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 4 May 2026 16:45:17 +0200 Subject: [PATCH 208/304] Fixed character sheet error:ing out if opened when the character doesn't have a spellcastModifierTrait --- module/applications/sheets/actors/character.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 7adffa1e..f40c144a 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -211,7 +211,7 @@ export default class CharacterSheet extends DHBaseActorSheet { ...this.document.system.traits[key], label: _loc(CONFIG.DH.ACTOR.abilities[key].label), verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x)), - isSpellcasting: this.document.system.spellcastModifierTrait.key === key + isSpellcasting: this.document.system.spellcastModifierTrait?.key === key }; return acc; From cca468e8af8ef79e44b082361e68f758132c187e Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 5 May 2026 21:21:27 +0200 Subject: [PATCH 209/304] Raised foundry version --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index ac581343..f7614790 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.2.3", "compatibility": { "minimum": "14.359", - "verified": "14.360", + "verified": "14.361", "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart", From fb5e3672dce1a9742bad2b56423ccd77e8b3ab8b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 5 May 2026 16:14:45 -0400 Subject: [PATCH 210/304] Disable spellcheck and autocorrect on all adversary sheets (#1869) --- templates/sheets/actors/adversary/header.hbs | 9 ++++++++- templates/sheets/actors/character/header.hbs | 9 ++++++++- templates/sheets/actors/companion/header.hbs | 2 ++ templates/sheets/actors/party/header.hbs | 4 +++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/templates/sheets/actors/adversary/header.hbs b/templates/sheets/actors/adversary/header.hbs index 5bdfa421..fba96980 100644 --- a/templates/sheets/actors/adversary/header.hbs +++ b/templates/sheets/actors/adversary/header.hbs @@ -1,7 +1,14 @@
      -

      {{source.name}}

      +

      {{source.name}}

      diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs index 1b1c4965..8010dfa5 100644 --- a/templates/sheets/actors/character/header.hbs +++ b/templates/sheets/actors/character/header.hbs @@ -1,7 +1,14 @@
      -

      {{source.name}}

      +

      {{source.name}}

      {{#if @root.editable}} diff --git a/templates/sheets/actors/companion/header.hbs b/templates/sheets/actors/companion/header.hbs index 8b63e396..d10c0640 100644 --- a/templates/sheets/actors/companion/header.hbs +++ b/templates/sheets/actors/companion/header.hbs @@ -6,6 +6,8 @@ name='name' value='{{document.name}}' placeholder='{{localize "DAGGERHEART.GENERAL.actorName"}}' + autocomplete="off" + spellcheck="false" />

      {{#if useResourcePips}} diff --git a/templates/sheets/actors/party/header.hbs b/templates/sheets/actors/party/header.hbs index 3fdb137d..c48902c8 100644 --- a/templates/sheets/actors/party/header.hbs +++ b/templates/sheets/actors/party/header.hbs @@ -2,7 +2,9 @@
      -

      +

      + +

      \ No newline at end of file From b7bc452bf5fd801781f5e859b09db01fc03d47c8 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 5 May 2026 22:15:21 +0200 Subject: [PATCH 211/304] [Fix] Improve Class-Subclass Linkage (#1846) * Initial thoughts * . * Fixed linting * Continue work on updating identifier * Change to uuid approach * Localization and minor fix * Fixed CompendiumBrowser Class filter for Subclass view * Fixed the class name display in the subclass view * Improved missing class visual for subclass * Fixed character creation * Rerender class sheets when subclass link is changed * Use compendium source over actual uuid in search --------- Co-authored-by: Carlos Fernandez --- lang/en.json | 5 +- .../characterCreation/characterCreation.mjs | 10 ++-- module/applications/sheets/items/class.mjs | 27 ++-------- module/applications/sheets/items/subclass.mjs | 32 ++++++++++++ module/applications/ui/itemBrowser.mjs | 11 ++-- module/config/itemBrowserConfig.mjs | 22 ++++---- module/data/item/base.mjs | 2 +- module/data/item/class.mjs | 19 ++++++- module/data/item/subclass.mjs | 5 +- module/helpers/utils.mjs | 8 +++ styles/less/global/feature-section.less | 50 +++++++++++-------- .../less/sheets/items/item-sheet-shared.less | 4 ++ templates/sheets/items/class/features.hbs | 16 +----- templates/sheets/items/class/header.hbs | 1 - .../sheets/items/subclass/description.hbs | 10 ++-- templates/sheets/items/subclass/features.hbs | 29 +++++++++++ 16 files changed, 167 insertions(+), 84 deletions(-) diff --git a/lang/en.json b/lang/en.json index aff0d60a..20c66a32 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2442,6 +2442,7 @@ "single": "Miss", "plural": "Miss" }, + "missingX": "Missing {x}", "maxWithThing": "Max {thing}", "missingDragDropThing": "Drop {thing} here", "multiclass": "Multiclass", @@ -2532,6 +2533,9 @@ "recovery": { "label": "Recovery" }, "type": { "label": "Type" }, "value": { "label": "Value" } + }, + "identifier": { + "label": "Identifier" } }, "Ancestry": { @@ -3219,7 +3223,6 @@ "subclassesAlreadyPresent": "You already have a class and multiclass subclass", "noDiceSystem": "Your selected dice {system} does not have a {faces} dice", "gmMenuRefresh": "You refreshed all actions and resources {types}", - "subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.", "gmRequired": "This action requires an online GM", "gmOnly": "This can only be accessed by the GM", "noActorOwnership": "You do not have permissions for this character", diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index 936bb79d..82ca9ccb 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -439,10 +439,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl 'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null } }; - if (type === 'subclasses') + if (type === 'subclasses') { + const classItem = this.setup.class; + const uuid = classItem?._stats.compendiumSource ?? classItem?.uuid; presets.filter = { - 'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', value: this.setup.class?.uuid } + 'system.linkedClass': { key: 'system.linkedClass', value: uuid } }; + } if (equipment.includes(type)) presets.filter = { @@ -610,7 +613,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl [foundry.utils.randomID()]: {} }; } else if (item.type === 'subclass' && event.target.closest('.subclass-card')) { - if (this.setup.class.system.subclasses.every(subclass => subclass.uuid !== item.uuid)) { + const classSubclasses = await this.setup.class.system.fetchSubclasses(); + if (classSubclasses.every(subclass => subclass.uuid !== item.uuid)) { ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass')); return; } diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index 05bb0229..25c631fe 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -104,9 +104,10 @@ export default class ClassSheet extends DHBaseItemSheet { } /**@inheritdoc */ - async _prepareContext(_options) { - const context = await super._prepareContext(_options); + async _prepareContext(options) { + const context = await super._prepareContext(options); context.domains = this.document.system.domains; + context.subclasses = await this.document.system.fetchSubclasses(); return context; } @@ -128,20 +129,8 @@ export default class ClassSheet extends DHBaseItemSheet { const item = await fromUuid(data.uuid); const itemType = data.type === 'ActiveEffect' ? data.type : item.type; const target = event.target.closest('fieldset.drop-section'); - if (itemType === 'subclass') { - if (item.system.linkedClass) { - return ui.notifications.warn( - game.i18n.format('DAGGERHEART.UI.Notifications.subclassAlreadyLinked', { - name: item.name, - class: this.document.name - }) - ); - } - await item.update({ 'system.linkedClass': this.document.uuid }); - await this.document.update({ - 'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid] - }); - } else if (['feature', 'ActiveEffect'].includes(itemType)) { + + if (['feature', 'ActiveEffect'].includes(itemType)) { super._onDrop(event); } else if (this.document.parent?.type !== 'character') { if (itemType === 'weapon') { @@ -200,12 +189,6 @@ export default class ClassSheet extends DHBaseItemSheet { static async #removeItemFromCollection(_event, element) { const { uuid, target } = element.dataset; const prop = foundry.utils.getProperty(this.document.system, target); - - if (target === 'subclasses') { - const subclass = await foundry.utils.fromUuid(uuid); - await subclass?.update({ 'system.linkedClass': null }); - } - await this.document.update({ [`system.${target}`]: prop.filter(i => i && i.uuid !== uuid).map(x => x.uuid) }); } diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index 5c731777..e9d8370e 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -40,4 +40,36 @@ export default class SubclassSheet extends DHBaseItemSheet { get relatedDocs() { return this.document.system.features.map(x => x.item); } + + async _prepareContext(options) { + const context = await super._prepareContext(options); + if (this.document.system.linkedClass) { + const classData = await fromUuid(this.document.system.linkedClass); + context.class = classData ?? { + name: _loc('DAGGERHEART.GENERAL.missingX', { x: _loc('TYPES.Item.class') }), + missing: true + }; + } + return context; + } + + async _onDrop(event) { + event.stopPropagation(); + const data = TextEditor.getDragEventData(event); + const item = await fromUuid(data.uuid); + const itemType = data.type === 'ActiveEffect' ? data.type : item.type; + if (itemType === 'class') { + const uuid = item._stats.compendiumSource ?? item.uuid; + if (this.document.system.linkedClass !== uuid) { + await this.document.update({ 'system.linkedClass': uuid }); + // Re-render all class sheets for instant feedback + for (const app of foundry.applications.instances.values()) { + if (app.document?.type === 'class') app.render(); + } + } + return; + } + + return super._onDrop(event); + } } diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 67a16f6a..99b9a23d 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -277,7 +277,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { (await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description)); } - this.fieldFilter = this._createFieldFilter(); + this.fieldFilter = await this._createFieldFilter(); if (this.presets?.filter) { Object.entries(this.presets.filter).forEach(([k, v]) => { @@ -355,12 +355,12 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { ); } - _createFieldFilter() { + async _createFieldFilter() { const filters = ItemBrowser.getFolderConfig(this.selectedMenu.data, 'filters'); - filters.forEach(f => { + for (const f of filters) { if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field); else if (typeof f.choices === 'function') { - f.choices = f.choices(this.items); + f.choices = await f.choices(this.items); } // Clear field label so template uses our custom label parameter @@ -370,7 +370,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { f.name ??= f.key; f.value = this.presets?.filter?.[f.name]?.value ?? null; - }); + } + return filters; } diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 04b387cb..3e40c97b 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -383,7 +383,8 @@ export const typeConfig = { { key: 'system.linkedClass', label: 'TYPES.Item.class', - format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing' + format: linkedClass => + foundry.utils.fromUuidSync(linkedClass)?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing' }, { key: 'system.spellcastingTrait', @@ -393,15 +394,18 @@ export const typeConfig = { ], filters: [ { - key: 'system.linkedClass.uuid', + key: 'system.linkedClass', label: 'TYPES.Item.class', - choices: items => { - const list = items - .filter(item => item.system.linkedClass) - .map(item => ({ - value: item.system.linkedClass.uuid, - label: item.system.linkedClass.name - })); + choices: async items => { + const list = []; + for (const item of items.filter(item => item.system.linkedClass)) { + const linkedClass = await foundry.utils.fromUuid(item.system.linkedClass); + list.push({ + value: linkedClass.uuid, + label: linkedClass.name + }); + } + return list.reduce((a, c) => { if (!a.find(i => i.value === c.value)) a.push(c); return a; diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index aebf33bf..ba114fda 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -200,7 +200,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { const features = []; for (let f of this.features) { const fBase = f.item ?? f; - const feature = fBase.system ? fBase : await foundry.utils.fromUuid(fBase.uuid); + const feature = fBase.pack ? await foundry.utils.fromUuid(fBase.uuid) : fBase; features.push( foundry.utils.mergeObject( feature.toObject(), diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index d3738318..7014e011 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -30,7 +30,6 @@ export default class DHClass extends BaseDataItem { }), evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }), features: new ItemLinkFields(), - subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), inventory: new fields.SchemaField({ take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), choiceA: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), @@ -70,6 +69,24 @@ export default class DHClass extends BaseDataItem { return this.features.filter(x => x.type === CONFIG.DH.ITEM.featureSubTypes.class).map(x => x.item); } + async fetchSubclasses() { + const uuids = [this.parent.uuid, this.parent._stats?.compendiumSource].filter(u => !!u); + const subclasses = game.items.filter(x => x.type === 'subclass' && uuids.includes(x.system.linkedClass)); + for (const pack of game.packs) { + const indexes = await pack.getIndex({ fields: ['system.linkedClass'] }); + for (const index of indexes) { + if (index.type !== 'subclass') continue; + if (!uuids.includes(index.system?.linkedClass)) continue; + if (subclasses.find(x => x.uuid === index.uuid)) continue; + + const subclass = await foundry.utils.fromUuid(index.uuid); + subclasses.push(subclass); + } + } + + return subclasses; + } + async _preCreate(data, options, user) { if (this.actor?.type === 'character') { const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto; diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index d421cc6d..12d85c1e 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -28,7 +28,7 @@ export default class DHSubclass extends BaseDataItem { features: new ItemLinkFields(), featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }), isMulticlass: new fields.BooleanField({ initial: false }), - linkedClass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true, initial: null }) + linkedClass: new fields.DocumentUUIDField({ type: 'Item', nullable: true, initial: null }) }; } @@ -83,7 +83,8 @@ export default class DHSubclass extends BaseDataItem { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass')); return false; } - if (actorClass.system.subclasses.every(x => x.uuid !== dataUuid)) { + + if ((await actorClass.system.fetchSubclasses()).every(x => x.uuid !== dataUuid)) { ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass')); return false; } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index cec493b4..90937db4 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -861,3 +861,11 @@ export function createShallowProxy(obj) { } }); } + +export function camelize(str) { + return str + .replace(/(?:^\w|[A-Z]|\b\w)/g, (part, index) => { + return index === 0 ? part.toLowerCase() : part.toUpperCase(); + }) + .replace(/\s+/g, ''); +} diff --git a/styles/less/global/feature-section.less b/styles/less/global/feature-section.less index 13feb92c..7d5099e1 100644 --- a/styles/less/global/feature-section.less +++ b/styles/less/global/feature-section.less @@ -19,28 +19,36 @@ &:last-child { margin-bottom: 0px; } - .feature-line { - display: grid; + } + .feature-line { + display: grid; + align-items: center; + grid-template-columns: 1fr 4fr 1fr; + h4 { + font-weight: lighter; + color: light-dark(@dark, @beige); + } + .image { + height: 40px; + width: 40px; + object-fit: cover; + border-radius: 6px; + border: none; + } + .image-icon { + font-size: 26px; + width: 40px; + height: 40px; + display: flex; + justify-content: center; align-items: center; - grid-template-columns: 1fr 4fr 1fr; - h4 { - font-weight: lighter; - color: light-dark(@dark, @beige); - } - .image { - height: 40px; - width: 40px; - object-fit: cover; - border-radius: 6px; - border: none; - } - .controls { - display: flex; - justify-content: center; - gap: 10px; - a { - text-shadow: none; - } + } + .controls { + display: flex; + justify-content: center; + gap: 10px; + a { + text-shadow: none; } } } diff --git a/styles/less/sheets/items/item-sheet-shared.less b/styles/less/sheets/items/item-sheet-shared.less index d0a8cc48..5155ad70 100644 --- a/styles/less/sheets/items/item-sheet-shared.less +++ b/styles/less/sheets/items/item-sheet-shared.less @@ -10,4 +10,8 @@ font-family: @font-body; color: light-dark(@chat-blue-bg, @beige-50); } + + button.plain.inline-control { + flex: 0 0 auto; + } } diff --git a/templates/sheets/items/class/features.hbs b/templates/sheets/items/class/features.hbs index 9d037b65..279ff52c 100644 --- a/templates/sheets/items/class/features.hbs +++ b/templates/sheets/items/class/features.hbs @@ -27,10 +27,7 @@
      {{localize "TYPES.Item.subclass"}}
      - {{#unless source.system.subclasses}} -
      {{localize "DAGGERHEART.GENERAL.missingDragDropThing" thing=(localize "DAGGERHEART.GENERAL.subclasses")}}
      - {{/unless}} - {{#each source.system.subclasses as |subclass index|}} + {{#each subclasses as |subclass index|}}
    3. @@ -44,16 +41,7 @@ data-item-uuid={{subclass.uuid}} data-tooltip='{{localize "DAGGERHEART.UI.Tooltip.openItemWorld"}}' > - - - - +
    4. diff --git a/templates/sheets/items/class/header.hbs b/templates/sheets/items/class/header.hbs index 6b60ada6..019825f8 100644 --- a/templates/sheets/items/class/header.hbs +++ b/templates/sheets/items/class/header.hbs @@ -3,7 +3,6 @@

      -

      {{localize 'TYPES.Item.class'}}

      {{localize "DAGGERHEART.GENERAL.Domain.plural"}} diff --git a/templates/sheets/items/subclass/description.hbs b/templates/sheets/items/subclass/description.hbs index 4591bd1a..0267eb9b 100644 --- a/templates/sheets/items/subclass/description.hbs +++ b/templates/sheets/items/subclass/description.hbs @@ -1,8 +1,10 @@
      -
      -

      {{localize "DAGGERHEART.ITEMS.Subclass.spellcastTrait"}}

      - {{spellcastTrait}} -
      + {{#if spellcastTrait}} +
      +

      {{localize "DAGGERHEART.ITEMS.Subclass.spellcastTrait"}}

      + {{spellcastTrait}} +
      + {{/if}}

      {{localize "DAGGERHEART.ITEMS.Subclass.foundationFeatures"}}

      {{#each foundationFeatures as | feature |}} diff --git a/templates/sheets/items/subclass/features.hbs b/templates/sheets/items/subclass/features.hbs index 1a75974e..c54e702e 100644 --- a/templates/sheets/items/subclass/features.hbs +++ b/templates/sheets/items/subclass/features.hbs @@ -3,6 +3,35 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > +
      + {{localize "TYPES.Item.class"}} + {{#if class}} +
      +
    5. + {{#if class.missing}} + + {{class.name}} + {{else}} + + {{class.name}} +
      + + + +
      + {{/if}} +
    6. +
      + {{else}} +
      {{localize "DAGGERHEART.GENERAL.missingDragDropThing" thing=(localize "TYPES.Item.class")}}
      + {{/if}} +
      +
      {{localize "DAGGERHEART.GENERAL.Tabs.foundation"}} From 40804f333982529473aaf76c862de51ef5d4b22b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 8 May 2026 08:58:14 -0400 Subject: [PATCH 212/304] Adjust spacing of character hope (#1864) --- styles/less/sheets/actors/character/header.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index ffd01c83..ea397220 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -160,7 +160,7 @@ .domains-section { position: relative; display: flex; - gap: 10px; + gap: 4px; background-color: light-dark(transparent, @dark-blue); color: light-dark(@dark-blue, @golden); padding: 5px 10px; @@ -175,6 +175,7 @@ font-weight: bold; text-transform: uppercase; color: light-dark(@dark-blue, @golden); + margin-right: 4px; } .domain { From 9ef492969325a60defad971cd0a8def80d6b37cb Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 8 May 2026 09:08:25 -0400 Subject: [PATCH 213/304] Implement another traits redesign (#1871) * Implement another traits redesign * Adjust traits shape --- .../less/sheets/actors/character/header.less | 110 ++++++++++++------ styles/less/utils/colors.less | 2 + styles/less/utils/fonts.less | 2 + templates/sheets/actors/character/header.hbs | 40 ++++--- 4 files changed, 105 insertions(+), 49 deletions(-) diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index ea397220..f912a7f0 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -212,59 +212,101 @@ display: flex; padding: 0; margin-bottom: 15px; - gap: 8px; + justify-content: space-between; + max-width: 38.5rem; + gap: 0.5rem; + padding-left: 0.5rem; .trait { - height: 3.625rem; + --color-border: light-dark(@semi-transparent-dark-blue, @golden-60); cursor: pointer; - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 6px; - flex: 1; position: relative; - background-color: light-dark(transparent, @dark-blue); display: flex; + align-items: center; justify-content: center; flex-direction: column; + min-width: 4.375rem; .trait-name { - display: flex; - align-items: center; - padding-top: 5px; - color: light-dark(@dark-blue, @golden); + position: relative; + background-color: light-dark(@semi-transparent-dark-blue, @golden-40); + border: 1px solid var(--color-border); + border-radius: 3px; + color: light-dark(var(--color-light-1), @golden); font-size: var(--font-size-12); font-weight: 600; + height: 1rem; + line-height: 1rem; + white-space: nowrap; + width: 100%; + padding: 0 0.1876px 0 0.375rem; + margin-right: 0.125rem; /* makes it center SLIGHTLY */ + text-shadow: 1px 1px 2px @light-black; + + display: flex; align-items: center; justify-content: center; - } - .trait-value { - font-style: normal; - font-weight: 600; - font-size: var(--font-size-20); - text-align: center; - margin-bottom: 0.375rem; - } - - .tier-mark, - .spellcasting-mark { - position: absolute; - opacity: 0.9; - color: light-dark(@dark-blue, @golden); - i { - line-height: 17px; - font-size: var(--font-size-11); + .tier-mark { + position: absolute; + background-color: @dark-blue; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 50%; + width: 1rem; + height: 1rem; + right: calc(100% - 0.4375rem); + display: flex; + justify-content: center; + align-items: center; + &.marked::before { + content: ' '; + position: absolute; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background-color: @golden; + } } } - .tier-mark { - bottom: 1px; - left: 3px; - } + .trait-value-area { + --background: light-dark(#e8e6e3, @dark-blue); + display: flex; + position: relative; + .trait-value { + position: absolute; + inset: 0; + display: flex; + align-items: center; - .spellcasting-mark { - bottom: 1px; - right: 3px; + justify-content: center; + font-style: normal; + font-weight: 600; + font-size: var(--font-size-20); + text-align: center; + margin-bottom: 0.375rem; + } + + .spellcasting-mark { + position: absolute; + border: 1px solid light-dark(@dark-blue, @golden); + color: @golden; + left: 0; + right: 0; + bottom: -0.375rem; + margin-inline: auto; + border-radius: 50%; + width: 1.125rem; + height: 1.125rem; + background: radial-gradient(190.63% 190.63% at 50% -80.63%, #18152E 70%, #4D4494 80%, #A0837E 90%, var(--color-border) 100%); + font-size: var(--font-size-9); + text-shadow: 0 0 2px @light-black; + + display: flex; + align-items: center; + justify-content: center; + } } &:hover { diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 8f6418bf..18b981ad 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -5,6 +5,7 @@ --golden: #f3c267; --golden-10: #f3c26710; --golden-40: #f3c26740; + --golden-60: #f3c26760; --golden-90: #f3c26790; --golden-bg: #f3c2671a; --golden-secondary: #eaaf42; @@ -89,6 +90,7 @@ @golden: var(--golden, #f3c267); @golden-10: var(--golden-10, #f3c26710); @golden-40: var(--golden-40, #f3c26740); +@golden-60: var(--golden-60, #f3c26760); @golden-90: var(--golden-90, #f3c26790); @golden-bg: var(--golden-bg, #f3c2671a); @golden-secondary: var(--golden-secondary, #eaaf42); diff --git a/styles/less/utils/fonts.less b/styles/less/utils/fonts.less index d4902508..201ea356 100755 --- a/styles/less/utils/fonts.less +++ b/styles/less/utils/fonts.less @@ -7,6 +7,8 @@ --dh-font-body: 'Montserrat'; /* Include missing font sizes */ + --font-size-8: 0.5rem; + --font-size-9: 0.5625rem; --font-size-22: 1.375rem; } diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs index 8010dfa5..2a19e2c3 100644 --- a/templates/sheets/actors/character/header.hbs +++ b/templates/sheets/actors/character/header.hbs @@ -132,27 +132,37 @@
      {{#each this.attributes as |attribute key|}} -
      -
      - {{attribute.label}} -
      -
      - {{#if (gt attribute.value 0)}} - +{{attribute.value}} - {{else}} - {{attribute.value}} - {{/if}} -
      + {{!--
      + {{#if attribute.tierMarked}}
      {{/if}} - {{#if isSpellcasting}} -
      - +
      --}} +
      +
      +
      + {{attribute.label}} +
      +
      + + + + +
      + {{#if (gt attribute.value 0)}} + +{{attribute.value}} + {{else}} + {{attribute.value}} + {{/if}}
      - {{/if}} + {{#if isSpellcasting}} +
      + +
      + {{/if}} +
      {{/each}}
      From e8828b70dbaa458438e2748a92a261a2005302f8 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 9 May 2026 05:15:35 -0400 Subject: [PATCH 214/304] Remove white border around level input in character sheet (#1870) --- styles/less/sheets/actors/character/header.less | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index f912a7f0..47297b59 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -25,13 +25,19 @@ .name-row { display: flex; - gap: 5px; + gap: 6px; align-items: start; justify-content: space-between; padding: 0; padding-top: 5px; flex: 1; + [contenteditable], + input { + border: 1px solid @soft-shadow; + background-color: light-dark(@dark-15, @dark-blue); + } + h1 { display: flex; flex: 1; @@ -57,14 +63,16 @@ .label { display: flex; - align-items: center; + align-items: baseline; gap: 4px; } input { + border: none; width: 40px; padding: 0; text-align: center; + font-weight: 600; } .level-button { From 4064701c164ad8b3470597cd258fb25487d37523 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 9 May 2026 16:25:43 +0200 Subject: [PATCH 215/304] Fixed so that the overridden ?? label is actually applied for private messages (#1872) --- module/dice/dhRoll.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 83dbbaf2..d6975f71 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -141,8 +141,8 @@ export default class DHRoll extends Roll { const metagamingSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming); const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options }); return foundry.applications.handlebars.renderTemplate(template, { - ...chatData, roll: this, + ...chatData, parent: chatData.parent, targetMode: chatData.targetMode, areas: chatData.action?.areas, From 80e314ca84809cd9a6730e933da319dc0d212a94 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 9 May 2026 16:28:43 +0200 Subject: [PATCH 216/304] Minor style changes for character/header.hbs --- styles/less/sheets/actors/character/header.less | 2 +- templates/sheets/actors/character/header.hbs | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index 47297b59..21ea4846 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -35,7 +35,7 @@ [contenteditable], input { border: 1px solid @soft-shadow; - background-color: light-dark(@dark-15, @dark-blue); + background-color: light-dark(@dark-15, @soft-white-shadow); } h1 { diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs index 2a19e2c3..dfc9af16 100644 --- a/templates/sheets/actors/character/header.hbs +++ b/templates/sheets/actors/character/header.hbs @@ -132,14 +132,6 @@
      {{#each this.attributes as |attribute key|}} - {{!--
      - - {{#if attribute.tierMarked}} -
      - -
      - {{/if}} -
      --}}
      From abd7824c967681f6752010c3c81f356cb7857a70 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 9 May 2026 16:33:45 +0200 Subject: [PATCH 217/304] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index f7614790..16a1f74b 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.2.3", + "version": "2.2.4", "compatibility": { "minimum": "14.359", "verified": "14.361", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.3/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.4/system.zip", "authors": [ { "name": "WBHarry" From 24813e7e4f8b5faf96fec302804fe8b197775300 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 11 May 2026 16:30:39 -0400 Subject: [PATCH 218/304] Make background tab glassy in character sheet (#1868) * Make fieldsets glassy in character sheet * Remove glassy from character features tab --- styles/less/global/elements.less | 1 + .../sheets/actors/character/biography.less | 10 ++++- .../sheets/actors/character/biography.hbs | 10 ++--- .../sheets/actors/character/features.hbs | 39 +++++++++---------- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index ff668bfd..05e679b5 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -269,6 +269,7 @@ border-radius: 3px; background-color: light-dark(@dark-blue, @golden); color: light-dark(@beige, @dark-blue); + margin-bottom: var(--spacer-4); } } diff --git a/styles/less/sheets/actors/character/biography.less b/styles/less/sheets/actors/character/biography.less index 12a662ff..b7c6ba6e 100644 --- a/styles/less/sheets/actors/character/biography.less +++ b/styles/less/sheets/actors/character/biography.less @@ -9,14 +9,20 @@ gap: 10px; height: 100%; overflow-y: auto; - mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%); - padding-bottom: 10px; + mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 98%, transparent 100%); + padding-top: 8px; + padding-bottom: 20px; height: 100%; scrollbar-width: thin; scrollbar-color: light-dark(@dark-blue, @golden) transparent; } + .characteristics-section { + gap: 20px; + padding: 0 10px; + } + .biography-section { prose-mirror { --min-height: 50px; diff --git a/templates/sheets/actors/character/biography.hbs b/templates/sheets/actors/character/biography.hbs index 6f2fe8c1..7da920e0 100644 --- a/templates/sheets/actors/character/biography.hbs +++ b/templates/sheets/actors/character/biography.hbs @@ -4,9 +4,7 @@ data-group='{{tabs.biography.group}}' >
      -
      - {{localize 'DAGGERHEART.ACTORS.Character.story.characteristics'}} - +
      {{localize 'DAGGERHEART.ACTORS.Character.pronouns'}} {{formInput systemFields.biography.fields.characteristics.fields.pronouns value=source.system.biography.characteristics.pronouns enriched=source.system.biography.characteristics.pronouns localize=true toggled=true}} @@ -21,14 +19,14 @@ {{localize 'DAGGERHEART.ACTORS.Character.faith'}} {{formInput systemFields.biography.fields.characteristics.fields.faith value=source.system.biography.characteristics.faith enriched=source.system.biography.characteristics.faith localize=true toggled=true}}
      -
      +
      -
      +
      {{localize 'DAGGERHEART.ACTORS.Character.story.backgroundTitle'}} {{formInput background.field value=background.value enriched=background.enriched toggled=true}}
      -
      +
      {{localize 'DAGGERHEART.ACTORS.Character.story.connectionsTitle'}} {{formInput connections.field value=connections.value enriched=connections.enriched toggled=true}}
      diff --git a/templates/sheets/actors/character/features.hbs b/templates/sheets/actors/character/features.hbs index 70544683..b2760900 100644 --- a/templates/sheets/actors/character/features.hbs +++ b/templates/sheets/actors/character/features.hbs @@ -2,26 +2,25 @@ data-group='{{tabs.features.group}}'>
      {{#each document.system.sheetLists as |category|}} - {{#if (eq category.type 'feature' )}} - {{> 'daggerheart.inventory-items' - title=category.title - type='feature' - actorType='character' - collection=category.values - canCreate=@root.editable - showActions=@root.editable - }} - {{else if category.values}} - {{> 'daggerheart.inventory-items' - title=category.title - type='feature' - actorType='character' - collection=category.values - canCreate=false - showActions=@root.editable - }} - - {{/if}} + {{#if (eq category.type 'feature' )}} + {{> 'daggerheart.inventory-items' + title=category.title + type='feature' + actorType='character' + collection=category.values + canCreate=@root.editable + showActions=@root.editable + }} + {{else if category.values}} + {{> 'daggerheart.inventory-items' + title=category.title + type='feature' + actorType='character' + collection=category.values + canCreate=false + showActions=@root.editable + }} + {{/if}} {{/each}}
      \ No newline at end of file From d86ab2053c7cd9b2fce3b18b81cf74998ac7b549 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 13 May 2026 12:34:38 +0200 Subject: [PATCH 219/304] [Feature] 1876 - Adversary Disposition Split (#1877) --- lang/en.json | 6 +++++- module/applications/ui/combatTracker.mjs | 8 ++++++-- module/documents/tooltipManager.mjs | 4 +++- templates/ui/combatTracker/combatTracker.hbs | 5 ++++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lang/en.json b/lang/en.json index 20c66a32..0a9448c4 100755 --- a/lang/en.json +++ b/lang/en.json @@ -406,7 +406,11 @@ "giveSpotlight": "Give The Spotlight", "requestingSpotlight": "Requesting The Spotlight", "requestSpotlight": "Request The Spotlight", - "openCountdowns": "Countdowns" + "openCountdowns": "Countdowns", + "adversaryCategories": { + "friendly": "Friendly", + "adversaries": "Adversaries" + } }, "CompendiumBrowserSettings": { "title": "Enable Compendiums", diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index fb19a17e..0989bcb8 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -56,7 +56,9 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C async _prepareTrackerContext(context, options) { await super._prepareTrackerContext(context, options); - const adversaries = context.turns?.filter(x => x.isNPC) ?? []; + const npcs = context.turns?.filter(x => x.isNPC) ?? []; + const adversaries = npcs.filter(x => x.disposition !== CONST.TOKEN_DISPOSITIONS.FRIENDLY); + const friendlies = npcs.filter(x => x.disposition === CONST.TOKEN_DISPOSITIONS.FRIENDLY); const characters = context.turns?.filter(x => !x.isNPC) ?? []; const spotlightQueueEnabled = game.settings.get( CONFIG.DH.id, @@ -75,6 +77,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C Object.assign(context, { actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens, adversaries, + friendlies, allCharacters: characters, characters: characters.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0), spotlightRequests @@ -129,7 +132,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C active: index === combat.turn, canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'), type: combatant.actor?.system?.type, - img: await this._getCombatantThumbnail(combatant) + img: await this._getCombatantThumbnail(combatant), + disposition: combatant.token.disposition }; turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin( diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index e10dc5fa..18c03169 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -350,7 +350,9 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti async getBattlepointHTML(combatId) { const combat = game.combats.get(combatId); const adversaries = - combat.turns?.filter(x => x.actor?.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? []; + combat.turns + ?.filter(x => x.actor?.isNPC && x.token.disposition === CONST.TOKEN_DISPOSITIONS.HOSTILE) + ?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? []; const characters = combat.turns?.filter(x => !x.isNPC && x.actor) ?? []; const nrCharacters = characters.length; diff --git a/templates/ui/combatTracker/combatTracker.hbs b/templates/ui/combatTracker/combatTracker.hbs index 91f7786b..4ec37134 100644 --- a/templates/ui/combatTracker/combatTracker.hbs +++ b/templates/ui/combatTracker/combatTracker.hbs @@ -5,7 +5,10 @@ {{#if (gt this.characters.length 0)}} {{> 'systems/daggerheart/templates/ui/combatTracker/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.GENERAL.Character.plural") turns=this.characters}} {{/if}} + {{#if (gt this.friendlies.length 0)}} + {{> 'systems/daggerheart/templates/ui/combatTracker/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.APPLICATIONS.CombatTracker.adversaryCategories.friendly") turns=this.friendlies}} + {{/if}} {{#if (gt this.adversaries.length 0)}} - {{> 'systems/daggerheart/templates/ui/combatTracker/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.GENERAL.Adversary.plural") turns=this.adversaries}} + {{> 'systems/daggerheart/templates/ui/combatTracker/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.APPLICATIONS.CombatTracker.adversaryCategories.adversaries") turns=this.adversaries}} {{/if}}
      \ No newline at end of file From 24993970da34e4291bfd009403f585ba6890345d Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 13 May 2026 19:21:06 -0400 Subject: [PATCH 220/304] Slightly improve visibility of countdowns and fix mixing light and dark modes (#1878) --- module/applications/ui/countdowns.mjs | 18 +- styles/less/ui/countdown/countdown.less | 220 ++++++++++++++---------- templates/ui/countdowns.hbs | 10 ++ 3 files changed, 138 insertions(+), 110 deletions(-) diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 052564cc..76e2b399 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -21,10 +21,10 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application static DEFAULT_OPTIONS = { id: 'countdowns', tag: 'div', - classes: ['daggerheart', 'dh-style', 'countdowns', 'faded-ui'], + classes: ['daggerheart', 'dh-style', 'countdowns'], window: { icon: 'fa-solid fa-clock-rotate-left', - frame: true, + frame: false, title: 'DAGGERHEART.UI.Countdowns.title', positioned: false, resizable: false, @@ -62,20 +62,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application if (iconOnly) frame.classList.add('icon-only'); else frame.classList.remove('icon-only'); - const header = frame.querySelector('.window-header'); - header.querySelector('button[data-action="close"]').remove(); - header.querySelector('button[data-action="toggleControls"]').remove(); - - if (game.user.isGM) { - const editTooltip = game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle'); - const editButton = ``; - header.insertAdjacentHTML('beforeEnd', editButton); - } - - const minimizeTooltip = game.i18n.localize('DAGGERHEART.UI.Countdowns.toggleIconMode'); - const minimizeButton = ``; - header.insertAdjacentHTML('beforeEnd', minimizeButton); - return frame; } diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 47f06eb7..380eb454 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -1,28 +1,47 @@ @import '../../utils/colors.less'; @import '../../utils/fonts.less'; -.theme-dark { +#interface.theme-dark { .daggerheart.dh-style.countdowns { - background-image: url(../assets/parchments/dh-parchment-dark.png); - - .window-header { - background-image: url(../assets/parchments/dh-parchment-dark.png); - } + --background: url(../assets/parchments/dh-parchment-dark.png); } } -.daggerheart.dh-style.countdowns { +#interface.theme-light { + .daggerheart.dh-style.countdowns { + --background: url('../assets/parchments/dh-parchment-light.png') no-repeat center; + } +} + +.daggerheart.dh-style.countdowns { position: relative; border: 0; - border-radius: 4px; box-shadow: none; + color: var(--color-text-primary); width: 300px; pointer-events: all; align-self: flex-end; transition: 0.3s right ease-in-out; - .window-title { - font-family: @font-body; + display: flex; + flex-direction: column; + + &::before { + content: ' '; + position: absolute; + inset: 0; + background: var(--background); + border-radius: 4px; + opacity: var(--ui-fade-opacity); + transition: opacity var(--ui-fade-duration); + } + + :not(.performance-low, .noblur) { + backdrop-filter: blur(5px); + } + + &:hover::before { + opacity: 1; } #ui-right:has(#effects-display .effect-container) & { @@ -34,123 +53,136 @@ min-width: 180px; } - .window-header { - cursor: default; - border-bottom: 0; + .countdowns-header, + .countdowns-container { + position: relative; // allow rendering over the background } - .window-content { - padding-top: 4px; - padding-bottom: 16px; + .countdowns-header { + display: flex; + align-items: center; + gap: 0.25rem; + flex: 0 0 36px; + padding: 0 0.5rem; + overflow: hidden; + font-size: var(--font-size-13); + .window-title { + font-family: @font-body; + flex: 1; + } + .header-control + .header-control { + margin-left: 8px; + } + } + + .countdowns-container { + display: flex; + flex-direction: column; + gap: 8px; + padding: 4px var(--spacer-16) var(--spacer-16) var(--spacer-16); overflow: auto; max-height: 312px; - .countdowns-container { + .countdown-container { display: flex; - flex-direction: column; - gap: 8px; + width: 100%; - .countdown-container { - display: flex; - width: 100%; + &.icon-only { + gap: 8px; - &.icon-only { - gap: 8px; + .countdown-main-container { + .countdown-content { + justify-content: center; - .countdown-main-container { - .countdown-content { - justify-content: center; - - .countdown-tools { - gap: 8px; - } + .countdown-tools { + gap: 8px; } } } + } - .countdown-main-container { - width: 100%; + .countdown-main-container { + width: 100%; + display: flex; + align-items: center; + gap: 16px; + + img { + width: 44px; + height: 44px; + border-radius: 6px; + } + + .countdown-content { + flex: 1; display: flex; - align-items: center; - gap: 16px; + flex-direction: column; + justify-content: space-between; - img { - width: 44px; - height: 44px; - border-radius: 6px; - } - - .countdown-content { - flex: 1; + .countdown-tools { display: flex; - flex-direction: column; + align-items: center; justify-content: space-between; - .countdown-tools { + .countdown-tool-controls { display: flex; align-items: center; - justify-content: space-between; + gap: 16px; + } - .countdown-tool-controls { - display: flex; - align-items: center; - gap: 16px; - } + .progress-tag { + border: 1px solid; + border-radius: 4px; + padding: 2px 4px; + background-color: light-dark(@beige, @dark-blue); + } - .progress-tag { - border: 1px solid; - border-radius: 4px; + .countdown-tool-icons { + display: flex; + align-items: center; + gap: 8px; + + .looping-container { + position: relative; + border: 1px solid light-dark(@dark-blue, white); + border-radius: 6px; padding: 2px 4px; - background-color: light-dark(@beige, @dark-blue); - } - .countdown-tool-icons { - display: flex; - align-items: center; - gap: 8px; + &.should-loop { + background: light-dark(@golden, @golden); - .looping-container { - position: relative; - border: 1px solid light-dark(@dark-blue, white); - border-radius: 6px; - padding: 2px 4px; - - &.should-loop { - background: light-dark(@golden, @golden); - - .loop-marker { - color: light-dark(@dark-blue, @dark-blue); - } + .loop-marker { + color: light-dark(@dark-blue, @dark-blue); } + } - .direction-marker { - position: absolute; - font-size: 10px; - filter: drop-shadow(0 0 3px black); - top: -3px; - } + .direction-marker { + position: absolute; + font-size: 10px; + filter: drop-shadow(0 0 3px black); + top: -3px; } } } } } - /* Keep incase we want to reintroduce the player pips */ - // .countdown-access-container { - // display: grid; - // grid-template-columns: 1fr 1fr 1fr; - // grid-auto-rows: min-content; - // width: 38px; - // gap: 4px; - - // .countdown-access { - // height: 10px; - // width: 10px; - // border-radius: 50%; - // border: 1px solid light-dark(@dark-blue, @beige-80); - // content: ''; - // } - // } } + /* Keep incase we want to reintroduce the player pips */ + // .countdown-access-container { + // display: grid; + // grid-template-columns: 1fr 1fr 1fr; + // grid-auto-rows: min-content; + // width: 38px; + // gap: 4px; + + // .countdown-access { + // height: 10px; + // width: 10px; + // border-radius: 50%; + // border: 1px solid light-dark(@dark-blue, @beige-80); + // content: ''; + // } + // } } } } diff --git a/templates/ui/countdowns.hbs b/templates/ui/countdowns.hbs index 18694e49..95067826 100644 --- a/templates/ui/countdowns.hbs +++ b/templates/ui/countdowns.hbs @@ -1,4 +1,14 @@
      +
      + + {{localize "DAGGERHEART.UI.Countdowns.title"}} + + + + + + +
      {{#each countdowns as | countdown id |}}
      From 829a6161ff8741d19ae4b6286ef6b3c0d079abbf Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 14 May 2026 18:46:18 -0400 Subject: [PATCH 221/304] Add hot reload configuration (#1881) --- .github/workflows/deploy.yml | 1 + system.json | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 553a1a17..4ffcc64d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -37,6 +37,7 @@ jobs: url: https://github.com/${{github.repository}} manifest: https://raw.githubusercontent.com/${{github.repository}}/v14/system.json download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip + flags.hotReload: false # Create a zip file with all files required by the module to add to the release - run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js build/tagify.css styles/daggerheart.css assets/ templates/ packs/ lang/ diff --git a/system.json b/system.json index 16a1f74b..e1daf56c 100644 --- a/system.json +++ b/system.json @@ -15,6 +15,11 @@ { "name": "WBHarry" }, + { + "name": "Supe", + "url": "https://github.com/CarlosFdez", + "discord": "supe" + }, { "name": "cptn-cosmo", "url": "https://github.com/cptn-cosmo", @@ -298,5 +303,11 @@ }, "background": "systems/daggerheart/assets/logos/FoundrybornBackgroundLogo.png", "primaryTokenAttribute": "resources.hitPoints", - "secondaryTokenAttribute": "resources.stress" + "secondaryTokenAttribute": "resources.stress", + "flags": { + "hotReload": { + "extensions": ["css", "hbs", "json"], + "paths": ["styles/daggerheart.css", "templates", "lang"] + } + } } From bc3c09fa2ec9e84cf7198547795cebb33814c916 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:01:03 -0400 Subject: [PATCH 222/304] Move scrollbar definition to global styling (#1887) --- styles/less/dialog/level-up/selections-container.less | 2 -- styles/less/dialog/level-up/summary-container.less | 2 -- styles/less/global/elements.less | 2 -- styles/less/global/feature-section.less | 2 -- styles/less/global/global.less | 5 +++++ styles/less/global/prose-mirror.less | 2 -- styles/less/sheets-settings/adversary-settings/features.less | 2 -- .../sheets-settings/environment-settings/adversaries.less | 2 -- .../less/sheets-settings/environment-settings/features.less | 2 -- styles/less/sheets/actors/adversary/actions.less | 3 --- styles/less/sheets/actors/adversary/effects.less | 3 --- styles/less/sheets/actors/adversary/sidebar.less | 3 +-- styles/less/sheets/actors/character/biography.less | 3 --- styles/less/sheets/actors/character/effects.less | 3 --- styles/less/sheets/actors/character/features.less | 3 --- styles/less/sheets/actors/character/inventory.less | 3 --- styles/less/sheets/actors/character/loadout.less | 3 --- styles/less/sheets/actors/character/sidebar.less | 4 +--- styles/less/sheets/actors/companion/effects.less | 3 --- styles/less/sheets/actors/environment/actions.less | 3 --- .../less/sheets/actors/environment/potentialAdversaries.less | 3 --- styles/less/sheets/actors/environment/sheet.less | 2 -- styles/less/sheets/actors/party/inventory.less | 3 --- styles/less/sheets/actors/party/sheet.less | 2 -- styles/less/sheets/items/domain-card.less | 2 -- styles/less/sheets/items/feature.less | 2 -- styles/less/ui/countdown/countdown-edit.less | 2 -- styles/less/ui/item-browser/item-browser.less | 2 -- styles/less/ui/settings/homebrew-settings/domains.less | 2 -- styles/less/ux/autocomplete/autocomplete.less | 2 -- styles/less/ux/tooltip/sheet.less | 3 --- 31 files changed, 7 insertions(+), 73 deletions(-) diff --git a/styles/less/dialog/level-up/selections-container.less b/styles/less/dialog/level-up/selections-container.less index 6a551865..4e8b9a48 100644 --- a/styles/less/dialog/level-up/selections-container.less +++ b/styles/less/dialog/level-up/selections-container.less @@ -5,8 +5,6 @@ .levelup-selections-container { overflow: auto; padding: 10px 0; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; max-height: 500px; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); diff --git a/styles/less/dialog/level-up/summary-container.less b/styles/less/dialog/level-up/summary-container.less index d67abff6..97353ba7 100644 --- a/styles/less/dialog/level-up/summary-container.less +++ b/styles/less/dialog/level-up/summary-container.less @@ -17,8 +17,6 @@ .levelup-summary-container { overflow: auto; padding: 10px 0; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; max-height: 700px; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 05e679b5..62896dd0 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -96,8 +96,6 @@ textarea { color: light-dark(@dark, @beige); - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } button:where(:not(.plain)) { diff --git a/styles/less/global/feature-section.less b/styles/less/global/feature-section.less index 7d5099e1..2fd4e20f 100644 --- a/styles/less/global/feature-section.less +++ b/styles/less/global/feature-section.less @@ -5,8 +5,6 @@ .tab.features { padding: 0 10px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .feature-list { display: flex; flex-direction: column; diff --git a/styles/less/global/global.less b/styles/less/global/global.less index b9af67c0..644d03b8 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -12,6 +12,11 @@ } .daggerheart.dh-style { + * { + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } + .hint { flex: 0 0 100%; margin: 0; diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index 8a663e28..8412235d 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -10,8 +10,6 @@ background-color: transparent; } .editor-content { - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; h1 { font-size: var(--font-size-32); } diff --git a/styles/less/sheets-settings/adversary-settings/features.less b/styles/less/sheets-settings/adversary-settings/features.less index 4e0f6a8f..15b1fa18 100644 --- a/styles/less/sheets-settings/adversary-settings/features.less +++ b/styles/less/sheets-settings/adversary-settings/features.less @@ -5,8 +5,6 @@ .tab.features { max-height: 450px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .add-feature-btn { width: 100%; diff --git a/styles/less/sheets-settings/environment-settings/adversaries.less b/styles/less/sheets-settings/environment-settings/adversaries.less index 1a27eaca..2ce4819a 100644 --- a/styles/less/sheets-settings/environment-settings/adversaries.less +++ b/styles/less/sheets-settings/environment-settings/adversaries.less @@ -5,8 +5,6 @@ .tab.adversaries { max-height: 450px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .add-action-btn { width: 100%; diff --git a/styles/less/sheets-settings/environment-settings/features.less b/styles/less/sheets-settings/environment-settings/features.less index d907837a..db6b544d 100644 --- a/styles/less/sheets-settings/environment-settings/features.less +++ b/styles/less/sheets-settings/environment-settings/features.less @@ -5,8 +5,6 @@ .tab.features { max-height: 450px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .add-feature-btn { width: 100%; diff --git a/styles/less/sheets/actors/adversary/actions.less b/styles/less/sheets/actors/adversary/actions.less index 00395ebd..af870d9b 100644 --- a/styles/less/sheets/actors/adversary/actions.less +++ b/styles/less/sheets/actors/adversary/actions.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/adversary/effects.less b/styles/less/sheets/actors/adversary/effects.less index 4afe2454..fbf74249 100644 --- a/styles/less/sheets/actors/adversary/effects.less +++ b/styles/less/sheets/actors/adversary/effects.less @@ -9,9 +9,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index 4e7535c1..ef99bc09 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -287,12 +287,11 @@ padding-top: 10px; padding-bottom: 20px; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); - scrollbar-width: thin; + scrollbar-gutter: stable; &:hover { overflow-y: auto; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } diff --git a/styles/less/sheets/actors/character/biography.less b/styles/less/sheets/actors/character/biography.less index b7c6ba6e..f8d56735 100644 --- a/styles/less/sheets/actors/character/biography.less +++ b/styles/less/sheets/actors/character/biography.less @@ -13,9 +13,6 @@ padding-top: 8px; padding-bottom: 20px; height: 100%; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } .characteristics-section { diff --git a/styles/less/sheets/actors/character/effects.less b/styles/less/sheets/actors/character/effects.less index ceadd05e..ae49fa2d 100644 --- a/styles/less/sheets/actors/character/effects.less +++ b/styles/less/sheets/actors/character/effects.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/character/features.less b/styles/less/sheets/actors/character/features.less index 6a6438ff..017254a3 100644 --- a/styles/less/sheets/actors/character/features.less +++ b/styles/less/sheets/actors/character/features.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/character/inventory.less b/styles/less/sheets/actors/character/inventory.less index 12f63753..c8d2b584 100644 --- a/styles/less/sheets/actors/character/inventory.less +++ b/styles/less/sheets/actors/character/inventory.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); padding: 20px 0; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/character/loadout.less b/styles/less/sheets/actors/character/loadout.less index eba55890..127d688a 100644 --- a/styles/less/sheets/actors/character/loadout.less +++ b/styles/less/sheets/actors/character/loadout.less @@ -92,9 +92,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%); padding: 20px 0; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index 0e6e3d97..e450891b 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -551,11 +551,9 @@ padding-bottom: 20px; mask-image: linear-gradient(0deg, transparent 0%, black 5%); scrollbar-gutter: stable; - scrollbar-width: thin; - + &:hover { overflow-y: auto; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } diff --git a/styles/less/sheets/actors/companion/effects.less b/styles/less/sheets/actors/companion/effects.less index 12e1d847..6d7fe061 100644 --- a/styles/less/sheets/actors/companion/effects.less +++ b/styles/less/sheets/actors/companion/effects.less @@ -9,9 +9,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/environment/actions.less b/styles/less/sheets/actors/environment/actions.less index 51385322..cc8a345a 100644 --- a/styles/less/sheets/actors/environment/actions.less +++ b/styles/less/sheets/actors/environment/actions.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/environment/potentialAdversaries.less b/styles/less/sheets/actors/environment/potentialAdversaries.less index 274362a5..f3c5776a 100644 --- a/styles/less/sheets/actors/environment/potentialAdversaries.less +++ b/styles/less/sheets/actors/environment/potentialAdversaries.less @@ -9,9 +9,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%); padding-bottom: 20px; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/environment/sheet.less b/styles/less/sheets/actors/environment/sheet.less index 3ea14bc7..a7c9605b 100644 --- a/styles/less/sheets/actors/environment/sheet.less +++ b/styles/less/sheets/actors/environment/sheet.less @@ -20,8 +20,6 @@ .tab { flex: 1; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; &.active { overflow: hidden; diff --git a/styles/less/sheets/actors/party/inventory.less b/styles/less/sheets/actors/party/inventory.less index ac59e1de..8af37a79 100644 --- a/styles/less/sheets/actors/party/inventory.less +++ b/styles/less/sheets/actors/party/inventory.less @@ -10,9 +10,6 @@ overflow-y: auto; mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); padding: 20px 0; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } } diff --git a/styles/less/sheets/actors/party/sheet.less b/styles/less/sheets/actors/party/sheet.less index 6b51de53..852b6cfc 100644 --- a/styles/less/sheets/actors/party/sheet.less +++ b/styles/less/sheets/actors/party/sheet.less @@ -20,8 +20,6 @@ .tab { flex: 1; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; scrollbar-gutter: stable; &.active { diff --git a/styles/less/sheets/items/domain-card.less b/styles/less/sheets/items/domain-card.less index a784b3a2..54378fd0 100644 --- a/styles/less/sheets/items/domain-card.less +++ b/styles/less/sheets/items/domain-card.less @@ -5,7 +5,5 @@ section.tab { height: 400px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } diff --git a/styles/less/sheets/items/feature.less b/styles/less/sheets/items/feature.less index b7493f15..f3c7cd49 100644 --- a/styles/less/sheets/items/feature.less +++ b/styles/less/sheets/items/feature.less @@ -14,7 +14,5 @@ section.tab { height: 400px; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } diff --git a/styles/less/ui/countdown/countdown-edit.less b/styles/less/ui/countdown/countdown-edit.less index d6c4da93..78ad3a06 100644 --- a/styles/less/ui/countdown/countdown-edit.less +++ b/styles/less/ui/countdown/countdown-edit.less @@ -60,8 +60,6 @@ overflow-y: auto; overflow-x: hidden; max-height: 500px; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; .countdown-edit-outer-container { display: flex; diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index f558a0ba..1142b8fd 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -237,8 +237,6 @@ .compendium-sidebar > .folder-list { overflow-y: auto; scrollbar-gutter: stable; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; } .item-list-header, diff --git a/styles/less/ui/settings/homebrew-settings/domains.less b/styles/less/ui/settings/homebrew-settings/domains.less index da258fcd..406294ac 100644 --- a/styles/less/ui/settings/homebrew-settings/domains.less +++ b/styles/less/ui/settings/homebrew-settings/domains.less @@ -55,8 +55,6 @@ gap: 8px; max-height: 184px; overflow: auto; - scrollbar-width: thin; - scrollbar-color: light-dark(#18162e, #f3c267) transparent; .domain-container { position: relative; diff --git a/styles/less/ux/autocomplete/autocomplete.less b/styles/less/ux/autocomplete/autocomplete.less index 7f799449..e778f0da 100644 --- a/styles/less/ux/autocomplete/autocomplete.less +++ b/styles/less/ux/autocomplete/autocomplete.less @@ -21,8 +21,6 @@ flex-direction: column; gap: 2px; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - .group { font-weight: bold; font-size: var(--font-size-14); diff --git a/styles/less/ux/tooltip/sheet.less b/styles/less/ux/tooltip/sheet.less index 59e4e638..ad774fcd 100644 --- a/styles/less/ux/tooltip/sheet.less +++ b/styles/less/ux/tooltip/sheet.less @@ -48,9 +48,6 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip), overflow-y: auto; position: relative; - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - .tooltip-tag { display: flex; gap: 10px; From 46e552eb3d49bd7f104f45270fbf0dcb8b72a091 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:01:28 -0400 Subject: [PATCH 223/304] Fix scroll restoration of party members (#1886) --- module/applications/sheets/actors/party.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 403960c0..c703ad85 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -44,7 +44,10 @@ export default class Party extends DHBaseActorSheet { static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, - partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' }, + partyMembers: { + template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs', + scrollable: [''] + }, /* NOT YET IMPLEMENTED */ // projects: { // template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs', From 6b4de71a0a1d1e791aff5ad0182ba31aefc34c65 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:04:22 -0400 Subject: [PATCH 224/304] Fix compendium browser sometimes spawning behind other windows (#1885) --- module/applications/ui/itemBrowser.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 99b9a23d..d98cf2da 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -109,8 +109,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position ); - options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position; + delete options.position.zIndex; if (!userPresetPosition) { const width = noFolder === true || lite === true ? 600 : 850; From 855f4549ec8dc9962b9d93fee7e83ae0d36a14bf Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:21:05 -0400 Subject: [PATCH 225/304] [Fix] Errors when updating max hp and implement bar update animations (#1883) * Fix an error that can break the canvas when importing or resetting a character * Animate bar updates * Use non-static mix function instead --- module/canvas/placeables/token.mjs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 77c178d6..68e325c2 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -249,9 +249,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { /** @inheritDoc */ _drawBar(number, bar, data) { - const val = Number(data.value); - const pct = Math.clamp(val, 0, data.max) / data.max; - // Determine sizing const { width, height } = this.document.getSize(); const s = canvas.dimensions.uiScale; @@ -259,17 +256,19 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { const bh = 8 * (this.document.height >= 2 ? 1.5 : 1) * s; // Determine the color to use - const fillColor = - number === 0 ? foundry.utils.Color.fromRGB([1, 0, 0]) : foundry.utils.Color.fromString('#0032b1'); + const Color = foundry.utils.Color; + const fillColor = number === 0 ? Color.fromRGB([1, 0, 0]) : Color.fromString('#0032b1'); + const emptyColor = Color.fromRGB([0, 0, 0]); - // Draw the bar - const widthUnit = bw / data.max; + // Draw the bar (accounting floating point numbers from bar animations) + const widthUnit = bw / Math.ceil(data.max); bar.clear().lineStyle(s, 0x000000, 1.0); - const sections = [...Array(data.max).keys()]; - for (let mark of sections) { + const sections = [...Array(Math.ceil(data.max)).keys()]; + for (const mark of sections) { const x = mark * widthUnit; - const marked = mark + 1 <= data.value; - const color = marked ? fillColor : foundry.utils.Color.fromRGB([0, 0, 0]); + const marked = mark < Math.ceil(data.value); + const remainder = mark === Math.ceil(data.value) - 1 ? data.value % 1 : 0; + const color = !marked ? emptyColor : remainder ? emptyColor.mix(fillColor, remainder) : fillColor; if (mark === 0 || mark === sections.length - 1) { bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s); // Would like drawRoundedRect, but it's very troublsome with the corners. Leaving for now. } else { From dd2aa108710a7df024dd92cdf723b7c2405f398f Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:23:55 -0400 Subject: [PATCH 226/304] [Fix] selecting multiclass and multiple sheet issues (#1884) * Fix error with adding multiclass * Make it more card like * Fix issues with responsiveness when resized * Fix cards spilling out of container when multiple lines * Remove mask and fix regression in scrollbar --- .../applications/levelup/characterLevelup.mjs | 14 ++-- .../selections-container.less | 3 - styles/less/dialog/index.less | 7 +- styles/less/dialog/level-up/index.less | 6 ++ .../dialog/level-up/selections-container.less | 44 ++++++----- styles/less/dialog/level-up/sheet.less | 22 +++--- styles/less/global/elements.less | 1 + templates/levelup/tabs/selections.hbs | 78 +++++++++---------- 8 files changed, 90 insertions(+), 85 deletions(-) create mode 100644 styles/less/dialog/level-up/index.less diff --git a/module/applications/levelup/characterLevelup.mjs b/module/applications/levelup/characterLevelup.mjs index f7ef2ffa..e8d6cf1c 100644 --- a/module/applications/levelup/characterLevelup.mjs +++ b/module/applications/levelup/characterLevelup.mjs @@ -156,6 +156,7 @@ export default class DhCharacterLevelUp extends LevelUpBase { if (multiclasses?.[0]) { const data = multiclasses[0]; const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {}; + const subclasses = (await multiclass?.system?.fetchSubclasses()) ?? []; context.multiclass = { ...data, @@ -175,13 +176,12 @@ export default class DhCharacterLevelUp extends LevelUpBase { alreadySelected }; }) ?? [], - subclasses: - multiclass?.system?.subclasses.map(subclass => ({ - ...subclass, - uuid: subclass.uuid, - selected: data.secondaryData.subclass === subclass.uuid, - disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid - })) ?? [], + subclasses: subclasses.map(subclass => ({ + ...subclass, + uuid: subclass.uuid, + selected: data.secondaryData.subclass === subclass.uuid, + disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid + })), compendium: 'classes', limit: 1 }; diff --git a/styles/less/dialog/character-creation/selections-container.less b/styles/less/dialog/character-creation/selections-container.less index 2bbac484..24217dbf 100644 --- a/styles/less/dialog/character-creation/selections-container.less +++ b/styles/less/dialog/character-creation/selections-container.less @@ -114,9 +114,6 @@ .card-preview-container { flex: 1; - } - - .card-preview-container { border-color: light-dark(@dark-blue, @golden); } diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index eb882eeb..11d9635e 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -1,10 +1,5 @@ @import './attribution/sheet.less'; -@import './level-up/navigation-container.less'; -@import './level-up/selections-container.less'; -@import './level-up/sheet.less'; -@import './level-up/summary-container.less'; -@import './level-up/tiers-container.less'; -@import './level-up/footer.less'; +@import './level-up/index.less'; @import './resource-dice/sheet.less'; diff --git a/styles/less/dialog/level-up/index.less b/styles/less/dialog/level-up/index.less new file mode 100644 index 00000000..849a4d36 --- /dev/null +++ b/styles/less/dialog/level-up/index.less @@ -0,0 +1,6 @@ +@import './navigation-container.less'; +@import './selections-container.less'; +@import './summary-container.less'; +@import './tiers-container.less'; +@import './footer.less'; +@import './sheet.less'; diff --git a/styles/less/dialog/level-up/selections-container.less b/styles/less/dialog/level-up/selections-container.less index 4e8b9a48..8c0dbaec 100644 --- a/styles/less/dialog/level-up/selections-container.less +++ b/styles/less/dialog/level-up/selections-container.less @@ -3,10 +3,7 @@ .daggerheart.levelup { .levelup-selections-container { - overflow: auto; padding: 10px 0; - max-height: 500px; - mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); .achievement-experience-cards { display: flex; @@ -43,20 +40,22 @@ .levelup-card-selection { display: flex; - flex-wrap: wrap; justify-content: center; gap: 40px; height: 190px; + align-items: stretch; .card-preview-container { - height: 100%; + height: 190px; max-width: 200px; } .levelup-domains-selection-container { - display: flex; - flex-direction: column; - gap: 8px; + display: grid; + grid-auto-flow: column; + grid-template-rows: repeat(2, minmax(0, 1fr)); + height: 100%; + gap: 4px; .levelup-domain-selection-container { display: flex; @@ -64,6 +63,8 @@ align-items: center; position: relative; cursor: pointer; + overflow: hidden; + width: 93px; &.disabled { pointer-events: none; @@ -72,16 +73,20 @@ .levelup-domain-label { position: absolute; + left: 0; + right: 0; + bottom: 0; text-align: center; - top: 4px; background: grey; - padding: 0 12px; - border-radius: 6px; + padding: 2px 12px; z-index: 2; + line-height: 1; } img { - height: 124px; + object-fit: cover; + width: auto; + height: auto; &.svg { filter: @beige-filter; @@ -90,17 +95,18 @@ .levelup-domain-selected { position: absolute; - height: 54px; - width: 54px; + height: 40px; + width: 40px; border-radius: 50%; - border: 2px solid; - font-size: var(--font-size-48); + border: 2px solid @golden; + font-size: var(--font-size-24); display: flex; align-items: center; justify-content: center; - background-image: url(../assets/parchments/dh-parchment-light.png); - color: var(--color-dark-5); - top: calc(50% - 29px); + background: @dark-golden; + color: @golden; + top: 10px; + z-index: 2; i { position: relative; diff --git a/styles/less/dialog/level-up/sheet.less b/styles/less/dialog/level-up/sheet.less index ade7c8a9..c663f304 100644 --- a/styles/less/dialog/level-up/sheet.less +++ b/styles/less/dialog/level-up/sheet.less @@ -11,9 +11,11 @@ }); .daggerheart.levelup { - .window-content { - max-height: 960px; + .tab.active { + flex: 1; overflow: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; } div[data-application-part='form'] { @@ -22,15 +24,13 @@ gap: 8px; } - section { - .section-container { - display: flex; - flex-direction: row; - justify-content: center; - gap: 20px 8px; - margin-top: 8px; - flex-wrap: wrap; - } + .section-container { + display: flex; + flex-direction: row; + justify-content: center; + gap: 20px 8px; + margin-top: 8px; + flex-wrap: wrap; } .levelup-footer { diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 62896dd0..181bd0d3 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -802,6 +802,7 @@ .preview-image-container { width: 100%; + min-height: 0; flex-grow: 1; object-fit: cover; border-radius: 4px 4px 0 0; diff --git a/templates/levelup/tabs/selections.hbs b/templates/levelup/tabs/selections.hbs index 7ebe32bb..deb7f6c0 100644 --- a/templates/levelup/tabs/selections.hbs +++ b/templates/levelup/tabs/selections.hbs @@ -43,45 +43,6 @@
      {{/if}} - {{#if (gt this.domainCards.length 0)}} -
      -
      - -

      {{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.domainCards"}}

      - -
      -
      - -
      - -
      - {{#each this.domainCards}} - {{#> "systems/daggerheart/templates/components/card-preview.hbs" this }} - {{#each this.emptySubtexts}} -
      {{this}}
      - {{/each}} - {{/"systems/daggerheart/templates/components/card-preview.hbs"}} - {{/each}} -
      -
      - {{/if}} - - {{#if (gt this.subclassCards.length 0)}} -
      -
      - -

      {{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.subclass"}}

      - -
      - -
      - {{#each this.subclassCards}} - {{> "systems/daggerheart/templates/levelup/parts/selectable-card-preview.hbs" img=this.img header=this.featureLabel name=this.name path=this.path selected=this.selected uuid=this.uuid isMulticlass=this.isMulticlass featureState=this.featureState disabled=this.disabled }} - {{/each}} -
      -
      - {{/if}} - {{#if this.multiclass}}
      @@ -128,6 +89,45 @@
      {{/if}} + {{#if (gt this.domainCards.length 0)}} +
      +
      + +

      {{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.domainCards"}}

      + +
      +
      + +
      + +
      + {{#each this.domainCards}} + {{#> "systems/daggerheart/templates/components/card-preview.hbs" this }} + {{#each this.emptySubtexts}} +
      {{this}}
      + {{/each}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}} + {{/each}} +
      +
      + {{/if}} + + {{#if (gt this.subclassCards.length 0)}} +
      +
      + +

      {{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.subclass"}}

      + +
      + +
      + {{#each this.subclassCards}} + {{> "systems/daggerheart/templates/levelup/parts/selectable-card-preview.hbs" img=this.img header=this.featureLabel name=this.name path=this.path selected=this.selected uuid=this.uuid isMulticlass=this.isMulticlass featureState=this.featureState disabled=this.disabled }} + {{/each}} +
      +
      + {{/if}} + {{#if this.vicious}}

      {{localize "DAGGERHEART.APPLICATIONS.Levelup.summary.vicious"}}

      From 88e64531b4aa481ed00a93464354519da8d180c1 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 15 May 2026 03:32:00 -0400 Subject: [PATCH 227/304] [UI] Minor updates to tag team initialization visuals (#1882) * Visual updates to tag team initialization * Center players and handlet he case where is 5 or 6 players --- module/applications/dialogs/tagTeamDialog.mjs | 3 + .../dialog/group-roll-dialog/_common.less | 4 +- .../tag-team-dialog/initialization.less | 70 +++++++++++++++---- styles/less/dialog/tag-team-dialog/sheet.less | 8 ++- .../dialogs/tagTeamDialog/initialization.hbs | 14 +++- templates/dialogs/tagTeamDialog/result.hbs | 5 +- 6 files changed, 84 insertions(+), 20 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index e06cbe48..ba76831f 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -38,6 +38,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio tag: 'form', classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'], position: { width: 550, height: 'auto' }, + window: { + icon: 'fa-solid fa-user-group' + }, actions: { toggleSelectMember: TagTeamDialog.#toggleSelectMember, startTagTeamRoll: TagTeamDialog.#startTagTeamRoll, diff --git a/styles/less/dialog/group-roll-dialog/_common.less b/styles/less/dialog/group-roll-dialog/_common.less index 41573718..b04f6893 100644 --- a/styles/less/dialog/group-roll-dialog/_common.less +++ b/styles/less/dialog/group-roll-dialog/_common.less @@ -1,9 +1,7 @@ h1 { color: light-dark(@dark-blue, @golden); - font-family: var(--dh-font-subtitle); - font-size: var(--font-size-24); + font: 700 var(--font-size-24) var(--dh-font-subtitle); text-align: center; - font-weight: 700; } header { diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index 8557d231..2d015141 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -7,39 +7,85 @@ } .daggerheart.dialog.dh-style.views.tag-team-dialog { - .initialization-container { + .initialization-container.active { + display: flex; + flex-direction: column; + gap: var(--spacer-4); + h2 { text-align: center; } .members-container { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; + display: flex; + flex-wrap: wrap; + justify-content: center; gap: 8px; + // Force 3 columns for 5 -> 6 players + &:has(> :nth-child(5)):not(:has(> :nth-child(7))) { + padding-left: 10%; + padding-right: 10%; + } + .member-container { position: relative; display: flex; + align-items: stretch; justify-content: center; + border-radius: 6px; + border: 1px solid light-dark(@dark-blue, @golden); + overflow: hidden; + height: 11.5rem; + width: 122px; &.inactive { - opacity: 0.4; + border-color: light-dark(@dark-blue-40, @golden-40); + img { + opacity: 0.4; + } } .member-name { + --shadow-color: light-dark(white, black); position: absolute; - padding: 0 2px; - border: 1px solid; - border-radius: 6px; - margin-top: 4px; - color: light-dark(@dark, @beige); - background-image: url('../assets/parchments/dh-parchment-dark.png'); + bottom: 0; + left: 0; + right: 0; + + display: flex; + flex-direction: column; + justify-content: flex-end; + min-height: 4rem; + padding: 5rem 4px var(--spacer-8) 4px; text-align: center; + + color: var(--color-text-primary); + text-shadow: 1px 1px 2px var(--shadow-color), 0 0 10px var(--shadow-color); + + // Basic "scrim" gradient + background-image: linear-gradient( + to top, + var(--shadow-color), + rgba(from var(--shadow-color) r g b / 0.834) 10.6%, + rgba(from var(--shadow-color) r g b / 0.541) 34%, + rgba(from var(--shadow-color) r g b / 0.382) 47%, + rgba(from var(--shadow-color) r g b / 0.194) 65%, + transparent 100% + ); } img { - border-radius: 6px; - border: 1px solid light-dark(@dark-blue, @golden); + object-fit: cover; + object-position: top center; + flex: 1; + } + + .leader-mark { + position: absolute; + top: 4px; + right: 4px; + text-shadow: var(--shadow-text-stroke), 0 0 20px black; } } } diff --git a/styles/less/dialog/tag-team-dialog/sheet.less b/styles/less/dialog/tag-team-dialog/sheet.less index dc8f16dc..a8dffbd2 100644 --- a/styles/less/dialog/tag-team-dialog/sheet.less +++ b/styles/less/dialog/tag-team-dialog/sheet.less @@ -1,4 +1,10 @@ -.daggerheart.dialog.dh-style.views.tag-team-dialog { +.daggerheart.dialog.dh-style.views.tag-team-dialog .window-content { + h1 { + color: light-dark(@dark-blue, @golden); + font: 700 var(--font-size-24) var(--dh-font-subtitle); + text-align: center; + } + .team-container { display: flex; gap: 16px; diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs index 7ccdf566..0b92e68e 100644 --- a/templates/dialogs/tagTeamDialog/initialization.hbs +++ b/templates/dialogs/tagTeamDialog/initialization.hbs @@ -1,13 +1,18 @@
      -

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

      +

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

      @@ -30,10 +35,13 @@
      -
      {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.openDialogForAll"}}
      +
      \ No newline at end of file diff --git a/templates/dialogs/tagTeamDialog/result.hbs b/templates/dialogs/tagTeamDialog/result.hbs index 79b5138e..151c8c87 100644 --- a/templates/dialogs/tagTeamDialog/result.hbs +++ b/templates/dialogs/tagTeamDialog/result.hbs @@ -32,7 +32,10 @@
      - +
      \ No newline at end of file From e6c27926d0aaa8ddfba7162089712b273c364f49 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 15 May 2026 09:48:41 +0200 Subject: [PATCH 228/304] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index e1daf56c..7c1321fb 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.2.4", + "version": "2.2.5", "compatibility": { "minimum": "14.359", "verified": "14.361", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.4/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.5/system.zip", "authors": [ { "name": "WBHarry" From d372f3df9b15b5e600a924eb3690b3ac52618873 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 16 May 2026 12:38:07 -0400 Subject: [PATCH 229/304] Fix creating domain cards on character sheet (#1890) --- .../sheets/api/application-mixin.mjs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 36477821..c79db99b 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -89,7 +89,7 @@ export default function DHApplicationMixin(Base) { classes: ['daggerheart', 'sheet', 'dh-style'], actions: { triggerContextMenu: DHSheetV2.#triggerContextMenu, - createDoc: DHSheetV2.#createDoc, + createDoc: DHSheetV2.#onCreateDoc, editDoc: DHSheetV2.#editDoc, deleteDoc: DHSheetV2.#deleteDoc, toChat: DHSheetV2.#toChat, @@ -97,8 +97,8 @@ export default function DHApplicationMixin(Base) { viewItem: DHSheetV2.#viewItem, toggleEffect: DHSheetV2.#toggleEffect, toggleExtended: DHSheetV2.#toggleExtended, - addNewItem: DHSheetV2.#addNewItem, - browseItem: DHSheetV2.#browseItem, + addNewItem: DHSheetV2.#onAddNewItem, + browseItem: DHSheetV2.#onBrowseItem, editAttribution: DHSheetV2.#editAttribution }, contextMenus: [ @@ -639,7 +639,7 @@ export default function DHApplicationMixin(Base) { /* Application Clicks Actions */ /* -------------------------------------------- */ - static async #addNewItem(event, target) { + static async #onAddNewItem(event, target) { const createChoice = await foundry.applications.api.DialogV2.wait({ classes: ['dh-style', 'two-big-buttons'], buttons: [ @@ -658,11 +658,11 @@ export default function DHApplicationMixin(Base) { if (!createChoice) return; - if (createChoice === 'browse') return DHSheetV2.#browseItem.call(this, event, target); - else return DHSheetV2.#createDoc.call(this, event, target); + if (createChoice === 'browse') return DHSheetV2.#onBrowseItem.call(this, event, target); + else return DHSheetV2.#onCreateDoc.call(this, event, target); } - static async #browseItem(event, target) { + static async #onBrowseItem(_event, target) { const type = target.dataset.compendium ?? target.dataset.type; const presets = { @@ -713,7 +713,7 @@ export default function DHApplicationMixin(Base) { * Create an embedded document. * @type {ApplicationClickAction} */ - static async #createDoc(event, target) { + static async #onCreateDoc(event, target) { const { documentClass, type, inVault, disabled } = target.dataset; const parentIsItem = this.document.documentName === 'Item'; const featureOnCharacter = this.document.parent?.type === 'character' && type === 'feature'; @@ -725,7 +725,7 @@ export default function DHApplicationMixin(Base) { : null : this.document; - let systemData = null; + let systemData = {}; if (featureOnCharacter) { systemData = { originItemType: this.document.type, @@ -738,15 +738,18 @@ export default function DHApplicationMixin(Base) { const data = { name: cls.defaultName({ type, parent }), - type + type, + system: systemData }; - - if (systemData) data.system = systemData; - - if (inVault) data['system.inVault'] = true; if (disabled) data.disabled = true; - if (type === 'domainCard' && parent?.system.domains?.length) { - data.system.domain = parent.system.domains[0]; + + if (type === 'domainCard') { + if (parent?.system.domains?.length) data.system.domain = parent.system.domains[0]; + if (inVault) data.system.inVault = true; + } else if (type === 'weapon') { + // Passing an empty system object to weapon causes validation failure due to attack action initialization + // todo: determine why, fix it at its source, then remove this fallback + delete data.system; } const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey }); From 47960fdd61258c4f4b12eb75877acab15e869ef3 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 17 May 2026 07:27:10 -0400 Subject: [PATCH 230/304] Fix uses of font awesome and adjust browser filter spacing (#1896) --- styles/less/global/global.less | 2 +- styles/less/ui/chat/sheet.less | 2 +- styles/less/ui/item-browser/item-browser.less | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/styles/less/global/global.less b/styles/less/global/global.less index 644d03b8..c0e7f3fc 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -38,7 +38,7 @@ } &:before { - font-family: 'Font Awesome 6 Pro'; + font-family: var(--font-awesome); content: '\f110'; position: absolute; height: 100%; diff --git a/styles/less/ui/chat/sheet.less b/styles/less/ui/chat/sheet.less index 66b539b4..fb8cc104 100644 --- a/styles/less/ui/chat/sheet.less +++ b/styles/less/ui/chat/sheet.less @@ -195,7 +195,7 @@ fieldset.daggerheart.chat { &:before, &:after { content: '\f0d8'; - font-family: 'Font Awesome 6 Pro'; + font-family: var(--font-awesome); } } &.expanded { diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index 1142b8fd..066da73b 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -125,6 +125,7 @@ .form-group { white-space: nowrap; + gap: 0; } .filter-header { @@ -168,6 +169,10 @@ } } } + + .filter-content { + margin-top: 8px; + } } .folder-list { @@ -272,7 +277,7 @@ div[data-sort-key] { &:after { - font-family: 'Font Awesome 6 Pro'; + font-family: var(--font-awesome); margin-left: 5px; } From 0492507bd1a25ae29c5d90d885c11b8bc87117ce Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 17 May 2026 07:43:55 -0400 Subject: [PATCH 231/304] Further decrease sheet opacity (#1897) --- styles/less/global/sheet.less | 4 ++-- styles/less/utils/colors.less | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index 0c400564..5ccb8788 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -4,8 +4,8 @@ // Theme handling .appTheme({ - background: @dark-blue-90; - backdrop-filter: blur(8px); + background: @dark-blue-d0; + backdrop-filter: blur(7px); }, { background: url('../assets/parchments/dh-parchment-light.png') no-repeat center; }); diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 18b981ad..80519a5b 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -51,6 +51,7 @@ --dark-blue-50: #18162e50; --dark-blue-60: #18162e60; --dark-blue-90: #18162e90; + --dark-blue-d0: #18162ed0; --semi-transparent-dark-blue: rgba(24, 22, 46, 0.33); --dark: #222222; @@ -136,6 +137,7 @@ @dark-blue-50: var(--dark-blue-50, #18162e50); @dark-blue-60: var(--dark-blue-60, #18162e60); @dark-blue-90: var(--dark-blue-90, #18162e90); +@dark-blue-d0: var(--dark-blue-d0, #18162ed0); @semi-transparent-dark-blue: var(--semi-transparent-dark-blue, rgba(24, 22, 46, 0.33)); @dark: var(--dark, #222222); From ab412367f997079911bd031ead92abed1268932c Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sun, 17 May 2026 14:07:54 -0400 Subject: [PATCH 232/304] Add tier/type headers and filters to environments in browser (#1895) --- module/config/itemBrowserConfig.mjs | 46 ++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 3e40c97b..c87b4c4d 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -70,6 +70,49 @@ export const typeConfig = { } ] }, + environments: { + columns: [ + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular' + }, + { + key: 'system.type', + label: 'DAGGERHEART.GENERAL.type', + format: type => { + if (!type) return '-'; + + return CONFIG.DH.ACTOR.environmentTypes[type].label; + } + } + ], + filters: [ + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular', + field: 'system.api.models.actors.DhEnvironment.schema.fields.tier' + }, + { + key: 'system.type', + label: 'DAGGERHEART.GENERAL.type', + field: 'system.api.models.actors.DhEnvironment.schema.fields.type' + }, + { + key: 'system.difficulty', + name: 'difficulty.min', + label: 'DAGGERHEART.UI.ItemBrowser.difficultyMin', + field: 'system.api.models.actors.DhEnvironment.schema.fields.difficulty', + operator: 'gte' + }, + { + key: 'system.difficulty', + name: 'difficulty.max', + label: 'DAGGERHEART.UI.ItemBrowser.difficultyMax', + field: 'system.api.models.actors.DhEnvironment.schema.fields.difficulty', + operator: 'lte' + } + ] + }, items: { columns: [ { @@ -559,7 +602,8 @@ export const compendiumConfig = { id: 'environments', keys: ['environments'], label: 'DAGGERHEART.UI.ItemBrowser.folders.environments', - type: ['environment'] + type: ['environment'], + listType: 'environments' }, beastforms: { id: 'beastforms', From 98049bd76b7e0e6b1125c313c1ad192a8ee3cf1b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 19 May 2026 04:13:49 -0400 Subject: [PATCH 233/304] Remove unused resources tab styling (#1902) --- styles/less/sheets/actors/party/party-members.less | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index a29f7c88..a3ec90ec 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -2,16 +2,6 @@ @import '../../../utils/fonts.less'; @import '../../../utils/mixin.less'; -body.game:is(.performance-low, .noblur) { - .application.sheet.daggerheart.actor.dh-style.party .tab.resources .actors-list .actor-resources { - background: light-dark(@dark-blue, @dark-golden); - - .actor-name { - background: light-dark(@dark-blue, @dark-golden); - } - } -} - .application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers { overflow: auto; From d78c6b1183d5da73f439508e1826ef276271b655 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 19 May 2026 10:14:03 +0200 Subject: [PATCH 234/304] Fixed so that already destroyed companion tokens that foundry still lists are not considered (#1901) --- module/applications/hud/tokenHUD.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs index 943f3506..671b01a1 100644 --- a/module/applications/hud/tokenHUD.mjs +++ b/module/applications/hud/tokenHUD.mjs @@ -124,7 +124,9 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { const animationDuration = 500; const scene = game.scenes.get(game.user.viewedScene); /* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */ - const activeTokens = actors.flatMap(member => member.getDependentTokens({ scenes: scene }).filter(x => x._id)); + const activeTokens = actors.flatMap(member => + member.getDependentTokens({ scenes: scene }).filter(x => x._id && !x._destroyed) + ); const { x: actorX, y: actorY } = this.document; if (activeTokens.length > 0) { for (let token of activeTokens) { From ac5f84fff77a2dfee992e48e5eb49c9fcba49422 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 19 May 2026 10:16:50 +0200 Subject: [PATCH 235/304] Fixed so that the tokenHUD matches the current V14 foundry layout. Fixed so that the tooltips have the correct translations (#1894) --- templates/hud/tokenHUD.hbs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/templates/hud/tokenHUD.hbs b/templates/hud/tokenHUD.hbs index ab0872bf..b620ab11 100644 --- a/templates/hud/tokenHUD.hbs +++ b/templates/hud/tokenHUD.hbs @@ -4,10 +4,6 @@
      - - {{#if canChangeLevel}}
      {{/if}} + + {{#if hasCompanion}} {{/if}} @@ -49,8 +49,9 @@
      {{#if isGM}} - {{/if}} @@ -119,13 +120,15 @@ {{/each}}
      - {{#if canToggleCombat}} - {{/if}}

      From b91d943dd1f702c19b70ba1ced4b6cbcaf75557a Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 19 May 2026 10:45:31 +0200 Subject: [PATCH 236/304] Raised foundry minimum version --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index 7c1321fb..9ae54190 100644 --- a/system.json +++ b/system.json @@ -4,7 +4,7 @@ "description": "An unofficial implementation of the Daggerheart system", "version": "2.2.5", "compatibility": { - "minimum": "14.359", + "minimum": "14.361", "verified": "14.361", "maximum": "14" }, From d152bfc906aa56ba69ef2ca870856d85f1596444 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 19 May 2026 05:11:38 -0400 Subject: [PATCH 237/304] Fix party rerenders from shields and weapons (#1899) --- module/data/item/armor.mjs | 10 ---------- module/documents/actor.mjs | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index b0e4847f..21c56f9a 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -141,16 +141,6 @@ export default class DHArmor extends AttachableItem { } } - _onUpdate(a, b, c) { - super._onUpdate(a, b, c); - - if (this.actor?.type === 'character') { - for (const party of this.actor.parties) { - party.render(); - } - } - } - /** @inheritDoc */ static migrateDocumentData(source) { if (!source.system.armor) { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 5df87b6c..91bf7190 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -112,10 +112,22 @@ export default class DhpActor extends Actor { this.updateSource(update); } + /** Perform a render, debounced in order to prevent overloading repeat render requests */ + renderDebounced = foundry.utils.debounce(options => { + return this.render(options); + }, 10); + + _onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) { + super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId); + for (const party of this.parties) { + party.renderDebounced({ parts: ['partyMembers'] }); + } + } + _onUpdate(changes, options, userId) { super._onUpdate(changes, options, userId); for (const party of this.parties) { - party.render({ parts: ['partyMembers'] }); + party.renderDebounced({ parts: ['partyMembers'] }); } } @@ -134,7 +146,7 @@ export default class DhpActor extends Actor { _onDelete(options, userId) { super._onDelete(options, userId); for (const party of this.parties) { - party.render({ parts: ['partyMembers'] }); + party.renderDebounced({ parts: ['partyMembers'] }); } } From 4504379fcf6df8682acd6df2c7cf5525215a2e6b Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 19 May 2026 05:13:59 -0400 Subject: [PATCH 238/304] Fix journey end calculation and hope reduction when gaining scars (#1900) --- module/applications/dialogs/deathMove.mjs | 3 ++- module/data/actor/character.mjs | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/module/applications/dialogs/deathMove.mjs b/module/applications/dialogs/deathMove.mjs index 69ff758e..4a949b99 100644 --- a/module/applications/dialogs/deathMove.mjs +++ b/module/applications/dialogs/deathMove.mjs @@ -57,6 +57,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar'); if (config.roll.fate.value <= this.actor.system.levelData.level.current) { + const maxHope = this.actor.system.resources.hope.max + this.actor.system.scars; const newScarAmount = this.actor.system.scars + 1; await this.actor.update({ system: { @@ -64,7 +65,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV } }); - if (newScarAmount >= this.actor.system.resources.hope.max) { + if (newScarAmount >= maxHope) { await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id); return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount }); } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index a1cd13e8..70f83236 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -840,12 +840,13 @@ export default class DhCharacter extends DhCreature { const newHopeMax = this.resources.hope.max + diff; const newHopeValue = Math.min(newHopeMax, this.resources.hope.value); if (newHopeValue != this.resources.hope.value) { - if (!changes.system.resources.hope) changes.system.resources.hope = { value: 0 }; - - changes.system.resources.hope = { - ...changes.system.resources.hope, - value: changes.system.resources.hope.value + newHopeValue - }; + changes.system = foundry.utils.mergeObject(changes.system ?? {}, { + resources: { + hope: { + value: (changes.system?.resources?.hope?.value ?? 0) + newHopeMax + } + } + }); } } From 6a2d09caac5c076b1ea76ecf7352f722160c82d8 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 19 May 2026 17:18:10 -0400 Subject: [PATCH 239/304] Create sourcemaps for css files (#1904) --- .gitignore | 1 + gulpfile.js | 8 +- package-lock.json | 477 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 4 files changed, 483 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 9a22c0ce..e41a67d3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Build build foundry styles/daggerheart.css +styles/daggerheart.css.map diff --git a/gulpfile.js b/gulpfile.js index 1a81f5ae..2e873ced 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,9 +1,15 @@ // Less configuration var gulp = require('gulp'); var less = require('gulp-less'); +var sourcemaps = require('gulp-sourcemaps'); gulp.task('less', function (cb) { - gulp.src('styles/daggerheart.less').pipe(less()).on('error', console.error.bind(console)).pipe(gulp.dest('styles')); + gulp.src('styles/daggerheart.less') + .pipe(sourcemaps.init()) + .pipe(less()) + .on('error', console.error.bind(console)) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('styles')); cb(); }); diff --git a/package-lock.json b/package-lock.json index 47c5dede..28223032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "autocompleter": "^9.3.2", "gulp": "^5.0.0", "gulp-less": "^5.0.0", + "gulp-sourcemaps": "^3.0.0", "rollup": "^4.40.0" }, "devDependencies": { @@ -202,6 +203,132 @@ "node": ">17.0.0" } }, + "node_modules/@gulp-sourcemaps/identity-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", + "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", + "license": "MIT", + "dependencies": { + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha512-o/EatdaGt8+x2qpb0vFLC/2Gug/xYPRXb6a+ET1wGYKozKN3krDWC/zZFZAtrzxJHuDL12mwdfEFKcKMNvc55A==", + "license": "MIT", + "dependencies": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, "node_modules/@gulpjs/messages": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", @@ -894,6 +1021,18 @@ "node": ">= 10.13.0" } }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autocompleter": { "version": "9.3.2", "resolved": "https://registry.npmjs.org/autocompleter/-/autocompleter-9.3.2.tgz", @@ -1482,6 +1621,12 @@ "node": ">= 10.13.0" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1496,6 +1641,17 @@ "node": ">= 8" } }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -1667,6 +1823,19 @@ "node": ">=8.0.0" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -1700,6 +1869,35 @@ } } }, + "node_modules/debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "license": "MIT", + "dependencies": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + } + }, + "node_modules/debug-fabulous/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1741,6 +1939,15 @@ "node": ">=0.10.0" } }, + "node_modules/detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -1905,6 +2112,58 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2106,6 +2365,21 @@ "node": ">=6" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", @@ -2176,6 +2450,16 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", @@ -2194,6 +2478,15 @@ "node": ">=0.10.0" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2820,6 +3113,86 @@ "node": ">=6" } }, + "node_modules/gulp-sourcemaps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", + "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", + "license": "ISC", + "dependencies": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gulp-sourcemaps/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/gulp-sourcemaps/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-sourcemaps/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/gulp-sourcemaps/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, "node_modules/gulplog": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", @@ -3248,6 +3621,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -3333,6 +3712,12 @@ "node": ">=0.10.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3647,6 +4032,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -3692,6 +4086,25 @@ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "dev": true }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3779,8 +4192,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mute-stdout": { "version": "2.0.0", @@ -3846,6 +4258,12 @@ "node": ">= 4.4.x" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -4827,6 +5245,12 @@ "node": ">=6.0.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/promise.series": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", @@ -5376,7 +5800,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -5390,6 +5813,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "license": "MIT", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, "node_modules/sparkles": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", @@ -5515,6 +5949,15 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-inject": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", @@ -5650,6 +6093,19 @@ "readable-stream": "3" } }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/tinyexec": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", @@ -5696,6 +6152,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5989,6 +6451,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 52bb4ce7..73a7fe99 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "autocompleter": "^9.3.2", "gulp": "^5.0.0", "gulp-less": "^5.0.0", + "gulp-sourcemaps": "^3.0.0", "rollup": "^4.40.0" }, "scripts": { From 10a608a1a548181a3d6903483a40006decc6d009 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 20 May 2026 10:49:45 -0400 Subject: [PATCH 240/304] Refocus armor slots when using number keys (#1908) --- templates/sheets/actors/character/sidebar.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index 91779e9c..313c81f9 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -44,7 +44,7 @@
      {{else}}
      - + / {{document.system.armorScore.max}}
      From 2f589c1b8ebf0013e5c80e5e5645297c4996c554 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 20 May 2026 10:54:22 -0400 Subject: [PATCH 241/304] Minor visual tweaks to daggerheart setting dialogs (#1905) --- styles/less/global/elements.less | 66 ++++--------------- .../settings/homebrew-settings/resources.less | 2 +- styles/less/ui/settings/settings.less | 66 ++++++++++++++++++- .../settings/metagaming-settings/general.hbs | 6 +- 4 files changed, 80 insertions(+), 60 deletions(-) diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 181bd0d3..7d46d627 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -7,12 +7,12 @@ input[type='text'], input[type='number'], textarea, + file-picker, .input[contenteditable] { background: light-dark(transparent, transparent); border-radius: 6px; box-shadow: 0 4px 30px @soft-shadow; backdrop-filter: blur(9.5px); - -webkit-backdrop-filter: blur(9.5px); outline: 2px solid transparent; color: light-dark(@dark-blue, @golden); border: 1px solid light-dark(@dark, @beige); @@ -98,7 +98,7 @@ color: light-dark(@dark, @beige); } - button:where(:not(.plain)) { + button:where(:not(.plain, color-picker *, file-picker *)) { background: light-dark(transparent, @golden); border: 1px solid light-dark(@dark-blue, @dark-blue); color: light-dark(@dark-blue, @dark-blue); @@ -252,6 +252,15 @@ text-shadow: 0 0 1px currentColor, 0 0 1px currentColor, 0 0 8px light-dark(@dark-blue, @golden); } + file-picker, color-picker { + > input[type=text] { + background: transparent; + border: none; + outline: none; + backdrop-filter: unset; + } + } + fieldset { align-items: center; margin-top: 5px; @@ -597,59 +606,6 @@ } } -.application.setting.dh-style { - h2, - h3, - h4 { - margin: 8px 0 4px; - text-align: center; - } - - footer { - margin-top: 8px; - display: flex; - gap: 8px; - - button { - flex: 1; - } - } - - .form-group { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.25rem 0.5rem; - flex-wrap: wrap; - - label { - font-size: var(--font-size-14); - font-weight: normal; - } - - .form-fields { - display: flex; - gap: 4px; - align-items: center; - } - - &.setting-two-values { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 0.25rem 0.5rem; - - .form-group { - justify-content: end; - flex-wrap: nowrap; - } - - .hint { - grid-column: 1 / -1; - } - } - } -} - .system-daggerheart { .tagify { background: light-dark(transparent, transparent); diff --git a/styles/less/ui/settings/homebrew-settings/resources.less b/styles/less/ui/settings/homebrew-settings/resources.less index 5333e54d..1184904b 100644 --- a/styles/less/ui/settings/homebrew-settings/resources.less +++ b/styles/less/ui/settings/homebrew-settings/resources.less @@ -24,7 +24,7 @@ .resource-icons-container { display: flex; justify-content: space-between; - gap: 8px; + gap: 10px; width: 100%; .resource-icon-container { diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index 34f17d53..d08f74e6 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -1,6 +1,67 @@ @import '../../utils/colors.less'; .daggerheart.dh-style.setting { + --color-form-label: var(--color-text-primary); + + h2, + h3, + h4 { + margin: 8px 0 4px; + text-align: center; + } + + footer { + margin-top: 8px; + display: flex; + gap: 8px; + + button { + flex: 1; + } + } + + .standard-form { + gap: var(--spacer-8); + .form-group .form-fields { + width: unset; + } + } + + .form-group { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.25rem 0.5rem; + flex-wrap: wrap; + + label { + font-size: var(--font-size-14); + font-weight: normal; + line-height: var(--input-height); + } + + .form-fields { + display: flex; + gap: 4px; + align-items: center; + } + + &.setting-two-values { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.25rem 0.5rem; + + .form-group { + justify-content: end; + flex-wrap: nowrap; + } + + .hint { + grid-column: 1 / -1; + } + } + } + fieldset { display: flex; flex-direction: column; @@ -19,7 +80,10 @@ &.three-columns { display: grid; grid-template-columns: 1fr 1fr 1fr; - gap: 2px; + gap: 4px; + .form-group label { + line-height: unset; + } } &.six-columns { diff --git a/templates/settings/metagaming-settings/general.hbs b/templates/settings/metagaming-settings/general.hbs index 775ff016..9f5bd514 100644 --- a/templates/settings/metagaming-settings/general.hbs +++ b/templates/settings/metagaming-settings/general.hbs @@ -1,4 +1,4 @@ -
      +
      {{formGroup settingFields.schema.fields.hideObserverPermissionInChat value=settingFields._source.hideObserverPermissionInChat localize=true}} - {{formGroup settingFields.schema.fields.hidePartyStats value=settingFields._source.hidePartyStats localize=true}} -
      \ No newline at end of file + {{formGroup settingFields.schema.fields.hidePartyStats value=settingFields._source.hidePartyStats localize=true}} +
      From b23095cb2f2a16ae2ac63c77166fdb150fc52a14 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 20 May 2026 12:45:42 -0400 Subject: [PATCH 242/304] [Fix] finishing levelup with a multiclass (#1906) * Fix finishing levelup with a multiclass * Fix removal when de-leveling * Also delete multiclass related stuff if reducing below the minimum multiclass level --- .../applications/sheets/actors/character.mjs | 2 +- module/data/item/subclass.mjs | 54 ++++++++----------- module/documents/actor.mjs | 42 ++++++++------- 3 files changed, 47 insertions(+), 51 deletions(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index f40c144a..2a557411 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -718,7 +718,7 @@ export default class CharacterSheet extends DHBaseActorSheet { ? { 'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', - value: this.document.system.class.value._stats.compendiumSource + value: this.document.system.class.value?._stats.compendiumSource } } : undefined, diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 12d85c1e..ecf72de3 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -56,38 +56,30 @@ export default class DHSubclass extends BaseDataItem { if (allowed === false) return; if (this.actor?.type === 'character') { - const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `Item.${data._id}`; - if (this.actor.system.class.subclass) { - if (this.actor.system.multiclass.subclass) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent')); - return false; - } else { - const multiclass = this.actor.items.find(x => x.type === 'class' && x.system.isMulticlass); - if (!multiclass) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingMulticlass')); - return false; - } + const { value: actorClass, subclass: existingSubclass } = this.actor.system.class; + const { value: multiclass, subclass: existingMultisubclass } = this.actor.system.multiclass; + if (!actorClass && !multiclass) { + ui.notifications.warn('DAGGERHEART.UI.Notifications.missingClass', { localize: true }); + return false; + } + if (existingSubclass && existingMultisubclass) { + ui.notifications.warn('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent', { localize: true }); + return false; + } + if (existingSubclass && !multiclass) { + ui.notifications.warn('DAGGERHEART.UI.Notifications.missingMulticlass', { localize: true }); + return false; + } - if (multiclass.system.subclasses.every(x => x.uuid !== dataUuid)) { - ui.notifications.error( - game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInMulticlass') - ); - return false; - } - - await this.updateSource({ isMulticlass: true }); - } - } else { - const actorClass = this.actor.items.find(x => x.type === 'class' && !x.system.isMulticlass); - if (!actorClass) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass')); - return false; - } - - if ((await actorClass.system.fetchSubclasses()).every(x => x.uuid !== dataUuid)) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass')); - return false; - } + const match = [multiclass, actorClass].find( + c => c && (c._stats.compendiumSource ?? c.uuid) === this.linkedClass + ); + if (!match) { + const key = multiclass ? 'subclassNotInMulticlass' : 'subclassNotInClass'; + ui.notifications.warn(`DAGGERHEART.UI.Notifications.${key}`, { localize: true }); + return false; + } else if (match.system.isMulticlass) { + await this.updateSource({ isMulticlass: true }); } } } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 91bf7190..e4c11a5c 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -153,10 +153,13 @@ export default class DhpActor extends Actor { async updateLevel(newLevel) { if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return; + const tiers = Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers); + const maxLevel = tiers.reduce((acc, tier) => Math.max(acc, tier.levels.end), 0); + const multiclassMinLevel = Math.min( + maxLevel, + ...tiers.filter(t => t.options.multiclass).map(t => t.levels.start) + ); if (newLevel > this.system.levelData.level.current) { - const maxLevel = Object.values( - game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers - ).reduce((acc, tier) => Math.max(acc, tier.levels.end), 0); if (newLevel > maxLevel) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.tooHighLevel')); } @@ -231,18 +234,19 @@ export default class DhpActor extends Actor { this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass }); } - if (multiclass) { - const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid); - const multiclassFeatures = this.items.filter( - x => x.system.originItemType === 'class' && x.system.multiclassOrigin - ); - const subclassFeatures = this.items.filter( - x => x.system.originItemType === 'subclass' && x.system.multiclassOrigin + // Remove multiclass if we're removing a multiclass feature or if we're below the multiclass minimum level + // Multclasses cannot be manually removed on the sheet, so this allows recovering in the case of errors + if (multiclass || newLevel < multiclassMinLevel) { + const multiclassItems = this.items.filter( + x => + x.uuid === multiclass?.itemUuid || + x.system.isMulticlass || + (['class', 'subclass'].includes(x.system.originItemType) && x.system.multiclassOrigin) ); this.deleteEmbeddedDocuments( 'Item', - [multiclassItem, ...multiclassFeatures, ...subclassFeatures].map(x => x.id) + multiclassItems.map(x => x.id) ); this.update({ @@ -281,6 +285,7 @@ export default class DhpActor extends Actor { async levelUp(levelupData) { const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto; + const getStatsWithSource = document => ({ ...(document._stats ?? {}), compendiumSource: document.uuid }); const levelups = {}; for (var levelKey of Object.keys(levelupData)) { @@ -393,8 +398,8 @@ export default class DhpActor extends Actor { const embeddedItem = await this.createEmbeddedDocuments('Item', [ { ...multiclassData, - uuid: multiclassItem.uuid, - _stats: multiclassItem._stats, + uuid: multiclassItem.uuid, // todo: replace with setting an id and using keepId + _stats: getStatsWithSource(multiclassItem), system: { ...multiclassData.system, features: multiclassData.system.features.filter(x => x.type !== 'hope'), @@ -407,8 +412,8 @@ export default class DhpActor extends Actor { await this.createEmbeddedDocuments('Item', [ { ...subclassData, - uuid: subclassItem.uuid, - _stats: subclassItem._stats, + uuid: subclassItem.uuid, // todo: replace with setting an id and using keepId + _stats: getStatsWithSource(subclassItem), system: { ...subclassData.system, isMulticlass: true @@ -428,8 +433,8 @@ export default class DhpActor extends Actor { const embeddedItem = await this.createEmbeddedDocuments('Item', [ { ...cardData, - uuid: cardItem.uuid, - _stats: cardItem._stats, + uuid: cardItem.uuid, // todo: replace with setting an id and using keepId + _stats: getStatsWithSource(cardItem), system: { ...cardData.system, inVault: true @@ -450,8 +455,7 @@ export default class DhpActor extends Actor { const embeddedItem = await this.createEmbeddedDocuments('Item', [ { ...cardData, - uuid: cardItem.uuid, - _stats: cardItem._stats, + _stats: getStatsWithSource(cardItem), system: { ...cardData.system, inVault: true From b145f515d0441ccf34b00b17cca83dd7bb9bf930 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 20 May 2026 21:43:11 +0200 Subject: [PATCH 243/304] Fixed so that a non-existing class link uuid on a subclass doesn't make the subclass filter error (#1910) --- module/config/itemBrowserConfig.mjs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index c87b4c4d..ae5fa71b 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -443,10 +443,12 @@ export const typeConfig = { const list = []; for (const item of items.filter(item => item.system.linkedClass)) { const linkedClass = await foundry.utils.fromUuid(item.system.linkedClass); - list.push({ - value: linkedClass.uuid, - label: linkedClass.name - }); + if (linkedClass) { + list.push({ + value: linkedClass.uuid, + label: linkedClass.name + }); + } } return list.reduce((a, c) => { From b631525b6e989c6334250ae9ed368388e8c1d892 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 20 May 2026 21:44:50 +0200 Subject: [PATCH 244/304] Fixed so that Reaction roll chat messages do not mention 'withHope'/'withFear' (#1912) --- templates/ui/chat/parts/roll-part.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index 78f4dcd9..14e3eaa6 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -6,7 +6,7 @@ {{#if roll.isCritical}} {{localize "DAGGERHEART.GENERAL.criticalShort"}} {{else}} - {{#if (and roll.dHope (not (eq roll.type "reaction")))}} + {{#if (and roll.dHope (not (eq roll.options.roll.type "reaction")))}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}} {{/if}} {{/if}} From ed53d9ed4cfefda31288afcf917d6156f10d16e7 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 20 May 2026 16:18:40 -0400 Subject: [PATCH 245/304] [Feature] Add way to delete unbound character creation items (#1907) * Add way to delete unbound character creation items * Temporarily reduce functionality * Fixed missing fetchSubclass call * Revert "Fixed missing fetchSubclass call" This reverts commit 4fc9ee39b6e9b246ee0a05be3faa4a38c16251f6. --------- Co-authored-by: WBHarry --- .../applications/sheets/actors/character.mjs | 58 +++++++++++++++++++ templates/sheets/actors/character/header.hbs | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 2a557411..5f6c854b 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -57,6 +57,14 @@ export default class CharacterSheet extends DHBaseActorSheet { } ], contextMenus: [ + { + handler: CharacterSheet.#getCreationMainContextOptions, + selector: '.character-details [data-action="editDoc"]', + options: { + parentClassHooks: false, + fixed: true + } + }, { handler: CharacterSheet.#getDomainCardContextOptions, selector: '[data-item-uuid][data-type="domainCard"]', @@ -319,6 +327,56 @@ export default class CharacterSheet extends DHBaseActorSheet { /* Context Menu */ /* -------------------------------------------- */ + static #getCreationMainContextOptions() { + /** Returns true if the item is managed by the level up wizard. Such items shouldn't allow things like manual removal */ + function isItemWizardManaged(item) { + const actor = item?.actor; + if (!actor) return false; + + // If levelup automation is off in general or for this character, all items are unmanaged + // This is disabled until we have proper granted feature removal, for now this feature is to correct errors + // const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto; + // if (!levelupAuto) return false; + + // Core items aren't part of levelup data. TODO: add some way to flag a specific character as no auto leveling + const classPair = actor.system.class; + const coreItems = [actor.system.ancestry, actor.system.community, classPair?.value, classPair?.subclass]; + if (coreItems.includes(item)) return true; + + const levelups = Object.values(actor.system.levelData?.levelups) ?? []; + const uuid = item.uuid; + const sourceUuid = item._stats.compendiumSource; // on older characters this may be missing + return levelups.some(data => { + if (item.type === 'subclass') { + const selectedSubclasses = data.selections.map(s => s.secondaryData?.subclass).filter(s => !!s); + return sourceUuid + ? selectedSubclasses.includes(sourceUuid) + : selectedSubclasses.length && item.system.isMulticlass; + } + + const matchesCard = data.achievements.domainCards.some(i => i.itemUuid === uuid); + const matchesSelection = data.selections.some(s => s.itemUuid === uuid); + return matchesCard || matchesSelection; + }); + } + + return [ + { + label: 'CONTROLS.CommonDelete', + icon: 'fa-solid fa-trash', + visible: target => { + const doc = getDocFromElementSync(target); + return doc?.isOwner && !isItemWizardManaged(doc); + }, + callback: async (target, event) => { + const doc = await getDocFromElement(target); + if (event.shiftKey) return doc.delete(); + else return doc.deleteDialog(); + } + } + ]; + } + /** * Get the set of ContextMenu options for DomainCards. * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs index dfc9af16..459911af 100644 --- a/templates/sheets/actors/character/header.hbs +++ b/templates/sheets/actors/character/header.hbs @@ -64,7 +64,7 @@ {{/if}}
      - {{#if document.system.multiclass.value}} + {{#if (or document.system.multiclass.value document.system.multiclass.subclass)}}
      {{#if document.system.multiclass.value}} {{document.system.multiclass.value.name}} From da06381748c1f876860130a25bf787445be510b3 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Wed, 20 May 2026 23:08:18 +0200 Subject: [PATCH 246/304] [Fix] Remove System Slugify (#1913) --- module/applications/settings/homebrewSettings.mjs | 7 +++---- module/helpers/utils.mjs | 4 ---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 9f0b22c4..40ea0301 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -1,6 +1,5 @@ import { DhHomebrew } from '../../data/settings/_module.mjs'; import { Resource } from '../../data/settings/Homebrew.mjs'; -import { slugify } from '../../helpers/utils.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -403,12 +402,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli const domainName = button.form.elements.domainName.value; if (!domainName) return; - const newSlug = slugify(domainName); + const newSlug = domainName.slugify(); const existingDomains = [ ...Object.values(this.settings.domains), ...Object.values(CONFIG.DH.DOMAIN.domains) ]; - if (existingDomains.find(x => slugify(game.i18n.localize(x.label)) === newSlug)) { + if (existingDomains.find(x => x.id === newSlug)) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.domains.duplicateDomain')); return; } @@ -529,7 +528,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli const identifier = button.form.elements.identifier.value; if (!identifier) return; - const sluggedIdentifier = slugify(identifier); + const sluggedIdentifier = identifier.slugify(); await this.settings.updateSource({ [`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier) diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 90937db4..3452640c 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -453,10 +453,6 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) { await actor.createEmbeddedDocuments('Item', effectData); } -export const slugify = name => { - return name.toLowerCase().replaceAll(' ', '-').replaceAll('.', ''); -}; - export function shuffleArray(array) { let currentIndex = array.length; while (currentIndex != 0) { From f4c21a6a1b040363040e5deff99033d646f191fe Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 21 May 2026 01:33:07 +0200 Subject: [PATCH 247/304] [Fix] CompendiumBrowser Pack Toggling (#1909) * Fixed so that CompendiumBrowserSettings saves source/pack names as slugified version to avoid foundrdy not saving names with dots in the middle * Updated excludedPacks with another layer of TypedObjectField * Renmamed variable * Update module/applications/dialogs/CompendiumBrowserSettings.mjs Co-authored-by: Carlos Fernandez --------- Co-authored-by: Carlos Fernandez --- .../dialogs/CompendiumBrowserSettings.mjs | 26 ++++++++++--------- module/data/compendiumBrowserSettings.mjs | 14 +++++----- module/helpers/utils.mjs | 1 - .../compendiumBrowserSettingsDialog/packs.hbs | 2 +- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/module/applications/dialogs/CompendiumBrowserSettings.mjs b/module/applications/dialogs/CompendiumBrowserSettings.mjs index bef54a6f..b0d83fb3 100644 --- a/module/applications/dialogs/CompendiumBrowserSettings.mjs +++ b/module/applications/dialogs/CompendiumBrowserSettings.mjs @@ -50,7 +50,7 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi const excludedSourceData = this.browserSettings.excludedSources; const excludedPackData = this.browserSettings.excludedPacks; context.typePackCollections = game.packs.reduce((acc, pack) => { - const { type, label, packageType, packageName: basePackageName, id } = pack.metadata; + const { type, label, packageType, packageName: basePackageName, name, id } = pack.metadata; if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc; const isWorldPack = packageType === 'world'; @@ -68,13 +68,15 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi if (!acc[type].sources[packageName]) acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] }; - const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type); + const included = + !excludedPackData[packageName] || + !excludedPackData[packageName][name]?.excludedDocumentTypes.includes(type); acc[type].sources[packageName].packs.push({ - pack: id, + name, type, label: id === game.system.id ? game.system.title : game.i18n.localize(label), - checked: checked + checked: included }); return acc; @@ -106,16 +108,16 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi toggleTypedPack(event) { event.stopPropagation(); - const { type, pack } = event.target.dataset; - const currentlyExcluded = this.browserSettings.excludedPacks[pack] - ? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type) + const { type, source, packName } = event.target.dataset; + const currentlyExcluded = this.browserSettings.excludedPacks[source]?.[packName] + ? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.includes(type) : false; - if (!this.browserSettings.excludedPacks[pack]) - this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] }; - this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded - ? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type) - : [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type]; + this.browserSettings.excludedPacks[source] ??= {}; + this.browserSettings.excludedPacks[source][packName] ??= { excludedDocumentTypes: [] }; + this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes = currentlyExcluded + ? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.filter(x => x !== type) + : [...(this.browserSettings.excludedPacks[source][packName]?.excludedDocumentTypes ?? []), type]; this.render(); } diff --git a/module/data/compendiumBrowserSettings.mjs b/module/data/compendiumBrowserSettings.mjs index ea71c439..ed70c88f 100644 --- a/module/data/compendiumBrowserSettings.mjs +++ b/module/data/compendiumBrowserSettings.mjs @@ -11,11 +11,13 @@ export default class CompendiumBrowserSettings extends foundry.abstract.DataMode }) ), excludedPacks: new fields.TypedObjectField( - new fields.SchemaField({ - excludedDocumentTypes: new fields.ArrayField( - new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES }) - ) - }) + new fields.TypedObjectField( + new fields.SchemaField({ + excludedDocumentTypes: new fields.ArrayField( + new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES }) + ) + }) + ) ) }; } @@ -28,7 +30,7 @@ export default class CompendiumBrowserSettings extends foundry.abstract.DataMode const excludedSourceData = this.excludedSources[packageName]; if (excludedSourceData && excludedSourceData.excludedDocumentTypes.includes(pack.metadata.type)) return true; - const excludedPackData = this.excludedPacks[item.pack]; + const excludedPackData = this.excludedPacks[packageName]?.[pack.metadata.name]; if (excludedPackData && excludedPackData.excludedDocumentTypes.includes(pack.metadata.type)) return true; return false; diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 3452640c..7bc5fa25 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -449,7 +449,6 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) { effects: data.effects?.map(effect => effect.toObject()) }); } - await actor.createEmbeddedDocuments('Item', effectData); } diff --git a/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs b/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs index dcda8108..ca2739b2 100644 --- a/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs +++ b/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs @@ -22,7 +22,7 @@
      {{#each source.packs as |pack|}}
      - +
      {{/each}} From d782b2525423182a299ad9e2235f7076647906dd Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 21 May 2026 01:38:31 +0200 Subject: [PATCH 248/304] [Feature] Class/Multiclass Feature Split (#1911) * Changed so that multiclass features and multiclassSubclass features are displayed in separate fieldsets from the base class features in the character sheet * Changed to tertiaries for class/multiclass feature divide --- module/data/actor/character.mjs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 70f83236..10d53c13 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -577,6 +577,8 @@ export default class DhCharacter extends DhCreature { communityFeatures = [], classFeatures = [], subclassFeatures = [], + multiclassFeatures = [], + multiclassSubclassFeatures = [], companionFeatures = [], features = []; @@ -586,9 +588,9 @@ export default class DhCharacter extends DhCreature { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) { communityFeatures.push(item); } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) { - classFeatures.push(item); + (item.system.multiclassOrigin ? multiclassFeatures : classFeatures).push(item); } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { - subclassFeatures.push(item); + (item.system.multiclassOrigin ? multiclassSubclassFeatures : subclassFeatures).push(item); } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) { companionFeatures.push(item); } else if (item.type === 'feature' && !item.system.type) { @@ -617,6 +619,24 @@ export default class DhCharacter extends DhCreature { type: 'subclass', values: subclassFeatures }, + ...(multiclassFeatures.length + ? { + multiclassFeatures: { + title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`, + type: 'multiclass', + values: multiclassFeatures + } + } + : {}), + ...(multiclassSubclassFeatures.length + ? { + multiclassSubclassFeatures: { + title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`, + type: 'multiclassSubclass', + values: multiclassSubclassFeatures + } + } + : {}), companionFeatures: { title: game.i18n.localize('DAGGERHEART.ACTORS.Character.companionFeatures'), type: 'companion', From 273f66678496fdde7478e988c54723a076710b12 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 21 May 2026 01:44:13 +0200 Subject: [PATCH 249/304] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 9ae54190..d28b38b7 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.2.5", + "version": "2.2.6", "compatibility": { "minimum": "14.361", "verified": "14.361", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.5/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.6/system.zip", "authors": [ { "name": "WBHarry" From bae9006f64c0639cffd32957d0218ae7b9a39d8a Mon Sep 17 00:00:00 2001 From: WBHarry Date: Thu, 21 May 2026 19:15:30 +0200 Subject: [PATCH 250/304] Raised foundry verified version --- system.json | 2 +- .../ui/sceneNavigation/scene-navigation.hbs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/system.json b/system.json index d28b38b7..613c26e7 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.2.6", "compatibility": { "minimum": "14.361", - "verified": "14.361", + "verified": "14.362", "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart", diff --git a/templates/ui/sceneNavigation/scene-navigation.hbs b/templates/ui/sceneNavigation/scene-navigation.hbs index 5bdbbdad..1b65c289 100644 --- a/templates/ui/sceneNavigation/scene-navigation.hbs +++ b/templates/ui/sceneNavigation/scene-navigation.hbs @@ -12,15 +12,20 @@ {{/with}} {{#if scenes.levels}} - + {{#each scenes.levels}}
    7. - {{#with button}} - - {{/with}}
      {{ name }} + {{#if users}} +
        + {{#each users}} +
      • {{ letter }}
      • + {{/each}} +
      + {{/if}}
    8. {{/each}} @@ -40,7 +45,8 @@ {{#*inline ".scene"}}
    9. -
      +
      {{ name }} {{#if users}}
        From 53e8da77c6127b4df34373b50147d6e9de229913 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 23 May 2026 05:52:23 -0400 Subject: [PATCH 251/304] Use the main deleteDoc handler instead of the party sheet specific one (#1920) --- module/applications/sheets/actors/party.mjs | 20 ------------------- .../global/partials/inventory-item-V2.hbs | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index c703ad85..cec1e1f0 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -26,7 +26,6 @@ export default class Party extends DHBaseActorSheet { actions: { openDocument: Party.#openDocument, deletePartyMember: Party.#deletePartyMember, - deleteItem: Party.#deleteItem, toggleHope: Party.#toggleHope, toggleHitPoints: Party.#toggleHitPoints, toggleStress: Party.#toggleStress, @@ -509,23 +508,4 @@ export default class Party extends DHBaseActorSheet { const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid); await this.document.update({ 'system.partyMembers': newMembersList }); } - - static async #deleteItem(event, target) { - const doc = await getDocFromElement(target.closest('.inventory-item')); - if (!event.shiftKey) { - const confirmed = await foundry.applications.api.DialogV2.confirm({ - window: { - title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: game.i18n.localize('TYPES.Actor.party'), - name: doc.name - }) - }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name }) - }); - - if (!confirmed) return; - } - - this.document.deleteEmbeddedDocuments('Item', [doc.id]); - } } diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index d76f2897..9330386b 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -117,7 +117,7 @@ Parameters: {{#if (not isActor)}} - + {{else if (eq type 'adversary')}} From 2931377d53e1f67d05c9934b42a1ff0589b2370d Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 23 May 2026 05:53:44 -0400 Subject: [PATCH 252/304] Remove edit and remove icons from adversary and party features (#1919) --- templates/sheets/actors/adversary/features.hbs | 1 + templates/sheets/actors/environment/features.hbs | 1 + .../sheets/global/partials/inventory-fieldset-items-V2.hbs | 3 +++ templates/sheets/global/partials/inventory-item-V2.hbs | 4 +++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/templates/sheets/actors/adversary/features.hbs b/templates/sheets/actors/adversary/features.hbs index d320b0d8..3b495e74 100644 --- a/templates/sheets/actors/adversary/features.hbs +++ b/templates/sheets/actors/adversary/features.hbs @@ -6,6 +6,7 @@ type='feature' collection=@root.features hideContextMenu=true + hideModifyControls=true canCreate=@root.editable showActions=@root.editable }} diff --git a/templates/sheets/actors/environment/features.hbs b/templates/sheets/actors/environment/features.hbs index 3fd512da..35fcb038 100644 --- a/templates/sheets/actors/environment/features.hbs +++ b/templates/sheets/actors/environment/features.hbs @@ -9,6 +9,7 @@ type='feature' collection=@root.features hideContextMenu=true + hideModifyControls=true canCreate=@root.editable showActions=@root.editable }} diff --git a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs index 3f58b80b..db2fb6b7 100644 --- a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs +++ b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs @@ -19,6 +19,8 @@ Parameters: - showLabels {boolean} : If true, show label-tags else show simple tags. - hideTooltip {boolean} : If true, disables the tooltip on the item image. - hideControls {boolean} : If true, hides the controls inside inventory-item partials. +- hideContextMenu {boolean}: If true, hides the context menu dropdown button +- hideModifyControls {boolean}: If true, hides the edit and delete options - hideDescription {boolean} : If true, hides the item's description. - hideResources {boolean} : If true, hides the item's resources. - showActions {boolean} : If true show feature's actions. @@ -59,6 +61,7 @@ Parameters: actorType=(ifThen ../actorType ../actorType @root.document.type) hideControls=../hideControls hideContextMenu=../hideContextMenu + hideModifyControls=../hideModifyControls isActor=../isActor categoryAdversary=../categoryAdversary hideTooltip=../hideTooltip diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index 9330386b..523e9304 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -12,6 +12,8 @@ Parameters: - hideTags {boolean} : If true, hide simple-tags else show simple-tags. - hideTooltip {boolean} : If true, disables the tooltip on the item image. - hideControls {boolean} : If true, hides the controls inside inventory-item partials. +- hideContextMenu {boolean}: If true, hides the context menu dropdown button +- hideModifyControls {boolean}: If true, hides the edit and delete options (todo: swap to show, only party cares to show this) - hideDescription {boolean} : If true, hides the item's description. - hideResources {boolean} : If true, hides the item's resources. - showActions {boolean} : If true show feature's actions. @@ -112,7 +114,7 @@ Parameters: - {{else if @root.editable}} + {{else if (and @root.editable (not hideModifyControls))}} From e4a3f105dcd99b7c58fe83d18ddf612831c6801c Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 23 May 2026 12:16:25 +0200 Subject: [PATCH 253/304] [Feature] V14 Cleanup (#1918) * Fixedin PrototypeToken preview * Fixed translations * Fixed tokenSize linking to token.depth * Fixed beastform depth * Raised foundry version --- lang/en.json | 9 ++- module/data/activeEffect/beastformEffect.mjs | 4 +- module/data/item/beastform.mjs | 8 ++- module/documents/scene.mjs | 4 +- module/documents/token.mjs | 5 +- module/documents/tokenManager.mjs | 1 + system.json | 2 +- templates/hud/tokenHUD.hbs | 9 ++- .../token-config/appearance.hbs | 61 +++++++++++++------ templates/sheets/items/beastform/settings.hbs | 7 +++ .../ui/combatTracker/combatTrackerSection.hbs | 8 +-- 11 files changed, 85 insertions(+), 33 deletions(-) diff --git a/lang/en.json b/lang/en.json index 0a9448c4..a06c46c2 100755 --- a/lang/en.json +++ b/lang/en.json @@ -778,7 +778,9 @@ "title": "Group Roll" }, "TokenConfig": { - "actorSizeUsed": "Actor size is set, determining the dimensions" + "actorSizeUsed": "Actor size is set, determining the dimensions", + "tokenSize": "Token Size", + "sizeCategory": "Size Category" } }, "CLASS": { @@ -2565,10 +2567,11 @@ "tokenImg": { "label": "Token Image" }, "tokenRingImg": { "label": "Subject Texture" }, "tokenSize": { - "placeholder": "Using character dimensions", - "disabledPlaceholder": "Set by character size", + "placeholder": "Token Size", + "disabledPlaceholder": "Token Size", "height": { "label": "Height" }, "width": { "label": "Width" }, + "depth": { "label": "Depth" }, "scale": { "label": "Token Scale" } }, "evolved": { diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 128c0c52..0fbea122 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -35,6 +35,7 @@ export default class BeastformEffect extends BaseEffect { static migrateData(source) { if (!source.characterTokenData.tokenSize.height) source.characterTokenData.tokenSize.height = 1; if (!source.characterTokenData.tokenSize.width) source.characterTokenData.tokenSize.width = 1; + if (!source.characterTokenData.tokenSize.depth) source.characterTokenData.tokenSize.depth = 1; return super.migrateData(source); } @@ -52,7 +53,8 @@ export default class BeastformEffect extends BaseEffect { if (this.parent.parent.type === 'character') { const baseUpdate = { height: this.characterTokenData.tokenSize.height, - width: this.characterTokenData.tokenSize.width + width: this.characterTokenData.tokenSize.width, + depth: this.characterTokenData.tokenSize.depth }; const update = { ...baseUpdate, diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index d30d6046..ee9d9839 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -51,7 +51,8 @@ export default class DHBeastform extends BaseDataItem { }), scale: new fields.NumberField({ nullable: false, min: 0.2, max: 3, step: 0.05, initial: 1 }), height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }), - width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }) + width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }), + depth: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }) }), mainTrait: new fields.StringField({ required: true, @@ -192,7 +193,8 @@ export default class DHBeastform extends BaseDataItem { tokenSize: { scale: this.parent.parent.prototypeToken.texture.scaleX, height: this.parent.parent.prototypeToken.height, - width: this.parent.parent.prototypeToken.width + width: this.parent.parent.prototypeToken.width, + depth: this.parent.parent.prototypeToken.depth } }, advantageOn: this.advantageOn, @@ -211,10 +213,12 @@ export default class DHBeastform extends BaseDataItem { : null; const width = autoTokenSize ?? this.tokenSize.width; const height = autoTokenSize ?? this.tokenSize.height; + const depth = autoTokenSize ?? this.tokenSize.depth; const prototypeTokenUpdate = { height, width, + depth, texture: { src: this.tokenImg, scaleX: this.tokenSize.scale, diff --git a/module/documents/scene.mjs b/module/documents/scene.mjs index 59b8091f..bf700610 100644 --- a/module/documents/scene.mjs +++ b/module/documents/scene.mjs @@ -20,7 +20,7 @@ export default class DhScene extends Scene { const prototype = tokenDoc.actor?.prototypeToken ?? tokenDoc; this.#sizeSyncBatch.set(tokenDoc.id, { size: tokenSize, - prototypeSize: { width: prototype.width, height: prototype.height }, + prototypeSize: { width: prototype.width, height: prototype.height, depth: prototype.depth }, position: { x: tokenDoc.x, y: tokenDoc.y, elevation: tokenDoc.elevation } }); this.#processSyncBatch(); @@ -36,11 +36,13 @@ export default class DhScene extends Scene { const tokenSize = tokenSizes[size]; const width = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.width; const height = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.height; + const depth = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.depth; const updatedPosition = DHToken.getSnappedPositionInSquareGrid(this.grid, position, width, height); return { _id, width, height, + depth, ...updatedPosition }; }); diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 4ee7ce05..30862724 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -66,7 +66,8 @@ export default class DHToken extends CONFIG.Token.documentClass { if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) { document.updateSource({ width: tokenSize, - height: tokenSize + height: tokenSize, + depth: tokenSize }); } } @@ -90,7 +91,7 @@ export default class DHToken extends CONFIG.Token.documentClass { ) { const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; const tokenSize = tokenSizes[update.system.size]; - if (tokenSize !== this.width || tokenSize !== this.height) { + if (tokenSize !== this.width || tokenSize !== this.height || tokenSize !== this.depth) { this.parent?.syncTokenDimensions(this, update.system.size); } } diff --git a/module/documents/tokenManager.mjs b/module/documents/tokenManager.mjs index 3ccff4e2..7678d2c7 100644 --- a/module/documents/tokenManager.mjs +++ b/module/documents/tokenManager.mjs @@ -15,6 +15,7 @@ export default class DhTokenManager { if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) { tokenData.width = tokenSize; tokenData.height = tokenSize; + tokenData.depth = tokenSize; } } diff --git a/system.json b/system.json index 613c26e7..bbee2c09 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.2.6", "compatibility": { "minimum": "14.361", - "verified": "14.362", + "verified": "14.363", "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart", diff --git a/templates/hud/tokenHUD.hbs b/templates/hud/tokenHUD.hbs index b620ab11..cf43e15d 100644 --- a/templates/hud/tokenHUD.hbs +++ b/templates/hud/tokenHUD.hbs @@ -17,7 +17,7 @@ {{/if}} {{#if hasCompanion}} @@ -26,6 +26,13 @@ {{/if}} + {{#if isGM}} + + {{/if}} + {{#if canConfigure}} + {{imagePreview.current}} / {{imagePreview.total}} + +
      +
      + {{/if}}
    10. - {{/if}} +
      - {{localize "Token Size"}} + {{localize "DAGGERHEART.APPLICATIONS.TokenConfig.tokenSize"}} {{#if usesActorSize}} -
      - +
      + -
      -
      - {{/if}} - - {{#if isItemEffect}} - {{formGroup fields.transfer value=source.transfer rootId=rootId label=legacyTransfer.label hint=(localize "DAGGERHEART.EFFECTS.Attachments.transferHint")}} + {{formGroup fields.origin value=source.origin rootId=rootId disabled=true}} + {{else if isItemEffect}} + {{formGroup fields.transfer value=source.transfer rootId=rootId}} {{/if}} {{formGroup fields.statuses value=source.statuses options=statuses rootId=rootId classes="statuses"}} From 0e8c3dc74a265477fa04583b495ac9862e9620e3 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 25 May 2026 17:55:57 -0400 Subject: [PATCH 257/304] [UI] Adjust actor sheet headers (#1923) --- .../tag-team-dialog/initialization.less | 14 +- .../sheets/actors/actor-sheet-shared.less | 2 +- .../less/sheets/actors/companion/header.less | 15 ++- .../sheets/actors/environment/header.less | 126 ++++++++++-------- .../less/sheets/actors/environment/sheet.less | 4 - styles/less/sheets/actors/party/header.less | 35 ++--- styles/less/utils/colors.less | 4 + styles/less/utils/mixin.less | 45 +++++++ .../sheets/actors/environment/header.hbs | 42 +++--- templates/sheets/actors/party/header.hbs | 4 +- 10 files changed, 172 insertions(+), 119 deletions(-) diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index 2d015141..f53a7af4 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -1,3 +1,5 @@ +@import '../../utils/mixin.less'; + .theme-light .daggerheart.dialog.dh-style.views.tag-team-dialog { .initialization-container .members-container .member-container { .member-name { @@ -62,17 +64,7 @@ color: var(--color-text-primary); text-shadow: 1px 1px 2px var(--shadow-color), 0 0 10px var(--shadow-color); - - // Basic "scrim" gradient - background-image: linear-gradient( - to top, - var(--shadow-color), - rgba(from var(--shadow-color) r g b / 0.834) 10.6%, - rgba(from var(--shadow-color) r g b / 0.541) 34%, - rgba(from var(--shadow-color) r g b / 0.382) 47%, - rgba(from var(--shadow-color) r g b / 0.194) 65%, - transparent 100% - ); + .smooth-gradient-ease-in-out(background-image, to bottom, var(--shadow-color), 100%); } img { diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index bd82ef83..6ef73035 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -34,7 +34,7 @@ .attribution-header-label { font-style: italic; font-family: @font-body; - color: light-dark(@chat-blue-bg, @beige-50); + color: @color-text-subtle; } .tab.inventory { diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index 2a162a25..6cf886ab 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -11,7 +11,7 @@ .profile { height: 235px; cursor: pointer; - mask-image: linear-gradient(0deg, transparent 0%, black 10%); + .smooth-gradient-ease-in-out(mask-image, to top, black, 2.25rem); } .actor-name { @@ -24,11 +24,16 @@ margin-bottom: -30px; input[type='text'] { + backdrop-filter: none; + border: none; + font-family: @font-title; font-size: var(--font-size-24); - height: 32px; - text-align: center; - border: 1px solid transparent; outline: 2px solid transparent; + box-shadow: unset; + text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); + + height: 2rem; + text-align: center; transition: all 0.3s ease; &:hover { @@ -243,7 +248,7 @@ .companion-navigation { display: flex; gap: 8px; - align-items: center; + align-items: baseline; width: 100%; } } diff --git a/styles/less/sheets/actors/environment/header.less b/styles/less/sheets/actors/environment/header.less index 670f6c92..ce7e6163 100644 --- a/styles/less/sheets/actors/environment/header.less +++ b/styles/less/sheets/actors/environment/header.less @@ -1,5 +1,6 @@ @import '../../../utils/colors.less'; @import '../../../utils/fonts.less'; +@import '../../../utils/mixin.less'; .application.sheet.daggerheart.actor.dh-style.environment { .environment-header-sheet { @@ -10,60 +11,83 @@ .profile { height: 235px; - mask-image: linear-gradient(0deg, transparent 0%, black 10%); cursor: pointer; + .smooth-gradient-ease-in-out(mask-image, to top, black, 3.5rem); } .item-container { - display: flex; + display: grid; + grid-auto-flow: row; + grid-template-columns: 1fr min-content; + align-items: center; position: relative; - top: -45px; - gap: 20px; + top: -36px; + gap: 0 var(--spacer-12); padding: 0 20px; - margin-bottom: -30px; + margin-bottom: -26px; - .item-info { + .item-name input[type='text'] { + backdrop-filter: none; + border: none; + font-family: @font-title; + font-size: var(--font-size-32); + text-align: start; + transition: all 0.3s ease; + outline: 2px solid transparent; + box-shadow: none; + text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); + + padding-left: 0; + height: 2.625rem; + + &:hover[type='text'], + &:focus[type='text'] { + box-shadow: none; + outline: 2px solid light-dark(@dark-blue, @golden); + } + } + + .flexrow { + align-items: baseline; + grid-column: span 2; + } + + .tags { display: flex; - flex-direction: column; - gap: 8px; + gap: 10px; + padding-bottom: 0; + flex: 0; - .tags { + .tag { display: flex; - gap: 10px; - padding-bottom: 0; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: var(--font-size-12); + font: @font-body; + white-space: nowrap; - .tag { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 3px 5px; - font-size: var(--font-size-12); - font: @font-body; - - background: light-dark(@dark-15, @beige-15); - border: 1px solid light-dark(@dark, @beige); - border-radius: 3px; - } - - .label { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - font-size: var(--font-size-12); - } + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; } - .attribution-header-label { - text-align: left; - position: relative; - top: 4px; - margin-bottom: -6px; + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: var(--font-size-12); } } + .attribution-header-label { + text-align: right; + position: relative; + } + .status-number { display: flex; align-items: center; @@ -81,7 +105,7 @@ font-size: 1.2rem; align-items: center; justify-content: center; - background: light-dark(transparent, @dark-blue); + background: light-dark(#e8e6e3, @dark-blue); z-index: 2; &.armor-slots { @@ -93,7 +117,7 @@ .status-label { padding: 2px 10px; width: 100%; - border-radius: 3px; + border-radius: 0 0 3px 3px; background: light-dark(@dark-blue, @golden); h4 { @@ -105,37 +129,23 @@ } } } - - .item-name { - input[type='text'] { - font-size: var(--font-size-32); - height: 42px; - text-align: start; - transition: all 0.3s ease; - outline: 2px solid transparent; - border: 1px solid transparent; - - &:hover[type='text'], - &:focus[type='text'] { - box-shadow: none; - outline: 2px solid light-dark(@dark-blue, @golden); - } - } - } } .environment-info { display: flex; flex-direction: column; - gap: 12px; + gap: var(--spacer-8); padding: 10px 20px; } .environment-navigation { display: flex; gap: 20px; - align-items: center; + align-items: baseline; padding: 0 20px; + .tab-navigation { + margin-top: 0; + } } } } diff --git a/styles/less/sheets/actors/environment/sheet.less b/styles/less/sheets/actors/environment/sheet.less index a7c9605b..2d9cc188 100644 --- a/styles/less/sheets/actors/environment/sheet.less +++ b/styles/less/sheets/actors/environment/sheet.less @@ -5,10 +5,6 @@ .appTheme({ &.environment { background-image: url('../assets/parchments/dh-parchment-dark.png'); - - .attribution-header-label { - background-image: url('../assets/parchments/dh-parchment-dark.png'); - } } }, { &.environment { diff --git a/styles/less/sheets/actors/party/header.less b/styles/less/sheets/actors/party/header.less index 9a2c7350..18d69834 100644 --- a/styles/less/sheets/actors/party/header.less +++ b/styles/less/sheets/actors/party/header.less @@ -1,5 +1,6 @@ @import '../../../utils/colors.less'; @import '../../../utils/fonts.less'; +@import '../../../utils/mixin.less'; .party-header-sheet { display: flex; @@ -9,26 +10,30 @@ .profile { height: 235px; - mask-image: linear-gradient(0deg, transparent 0%, black 10%); cursor: pointer; + .smooth-gradient-ease-in-out(mask-image, to top, black, 3.5rem); } .item-container { - .item-name { - padding: 0 20px; - input[type='text'] { - font-size: 32px; - height: 42px; - text-align: center; - transition: all 0.3s ease; - outline: 2px solid transparent; - border: 1px solid transparent; + margin-top: -2rem; + z-index: 1; + input.item-name[type='text'] { + backdrop-filter: none; + border: none; + font-family: @font-title; + font-size: var(--font-size-32); + outline: 2px solid transparent; + box-shadow: unset; + text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); - &:hover[type='text'], - &:focus[type='text'] { - box-shadow: none; - outline: 2px solid light-dark(@dark-blue, @golden); - } + text-align: center; + transition: all 0.3s ease; + width: calc(100% - 40px); + height: 2.625rem; + + &:hover[type='text'], + &:focus[type='text'] { + outline: 2px solid light-dark(@dark-blue, @golden); } } diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 80519a5b..d35ad8b3 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -83,6 +83,8 @@ --gradient-stress: linear-gradient(15deg, rgb(130, 59, 1) 0%, rgb(252, 142, 69) 65%, rgb(190, 0, 0) 100%); --primary-color-fear: rgba(9, 71, 179, 0.75); + + --dh-color-text-subtle: light-dark(#555, #a29086); } @primary-blue: var(--primary-blue, #1488cc); @@ -190,3 +192,5 @@ box-shadow: 0 0 2px 2px @dark-blue; } } + +@color-text-subtle: var(--dh-color-text-subtle); \ No newline at end of file diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index 49e97a1f..b37bfc06 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -114,3 +114,48 @@ font-family: @font-body; } } + + +// A simple ease-out mask +.smooth-gradient-ease-out(@param, @to, @destination, @length) { + @{param}+: linear-gradient( + @to, + transparent 0%, + rgb(from @destination r g b / ~"calc(alpha * 0.013)") calc(0.008 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.049)") calc(0.029 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.104)") calc(0.064 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.259)") calc(0.166 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.45)") calc(0.304 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.741)") calc(0.554 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.825)") calc(0.644 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.896)") calc(0.735 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.951)") calc(0.825 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.987)") calc(0.914 * @length), + @destination @length + ); +} + +/** + * A simple ease in and out mask. + * @param - the parameter to add to + * @param - direction, such as "to top" + * @destination - the color at the destination. The origin is always transparent + * @length - the value at the destination + */ +.smooth-gradient-ease-in-out(@param, @to, @destination, @length: 100%) { + @{param}+: linear-gradient( + @to, + transparent 0%, + rgb(from @destination r g b / ~"calc(alpha * 0.013)") calc(0.081 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.049)") calc(0.155 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.104)") calc(0.225 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.259)") calc(0.353 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.45)") calc(0.471 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.741)") calc(0.647 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.825)") calc(0.71 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.896)") calc(0.775 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.951)") calc(0.845 * @length), + rgb(from @destination r g b / ~"calc(alpha * 0.987)") calc(0.914 * @length), + @destination @length + ); +} diff --git a/templates/sheets/actors/environment/header.hbs b/templates/sheets/actors/environment/header.hbs index b7eab3db..2c6bbb5a 100644 --- a/templates/sheets/actors/environment/header.hbs +++ b/templates/sheets/actors/environment/header.hbs @@ -1,28 +1,7 @@
      -
      -

      -
      -
      -
      - - {{localize (concat 'DAGGERHEART.GENERAL.Tiers.' source.system.tier)}} - -
      - {{#if source.system.type}} -
      - - {{localize (concat 'DAGGERHEART.CONFIG.EnvironmentType.' source.system.type '.label')}} - -
      - {{/if}} -
      - {{#if (and showAttribution document.system.attributionLabel)}} - - {{/if}} -
      -
      +

      {{#if source.system.difficulty}} @@ -35,6 +14,25 @@

      {{localize "DAGGERHEART.GENERAL.difficulty"}}

      +
      +
      +
      + + {{localize (concat 'DAGGERHEART.GENERAL.Tiers.' source.system.tier)}} + +
      + {{#if source.system.type}} +
      + + {{localize (concat 'DAGGERHEART.CONFIG.EnvironmentType.' source.system.type '.label')}} + +
      + {{/if}} +
      + {{#if (and showAttribution document.system.attributionLabel)}} + + {{/if}} +
      diff --git a/templates/sheets/actors/party/header.hbs b/templates/sheets/actors/party/header.hbs index c48902c8..efa6e5b8 100644 --- a/templates/sheets/actors/party/header.hbs +++ b/templates/sheets/actors/party/header.hbs @@ -2,9 +2,7 @@
      -

      - -

      +
      \ No newline at end of file From e529dd0f883c3bdd21eb6f7abd8913e7146779cb Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 26 May 2026 00:49:46 +0200 Subject: [PATCH 258/304] Fixed so that advantage dice do not get duplicated (#1929) --- module/applications/dialogs/d20RollDialog.mjs | 14 +++++------ module/dice/dualityRoll.mjs | 24 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 067aa473..76b2e751 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -175,14 +175,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.disadvantage = advantage === -1; this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage; + if (this.config.roll.advantage === 0) return this.render(); - if (this.config.roll.advantage === 1 && this.config.data.rules.roll.defaultAdvantageDice) { - const faces = Number.parseInt(this.config.data.rules.roll.defaultAdvantageDice); - this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces; - } else if (this.config.roll.advantage === -1 && this.config.data.rules.roll.defaultDisadvantageDice) { - const faces = Number.parseInt(this.config.data.rules.roll.defaultDisadvantageDice); - this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces; - } + const defaultFaces = + this.config.roll.advantage === 1 + ? this.config.data.rules.roll.defaultAdvantageDice + : this.config.data.rules.roll.defaultDisadvantageDice; + const faces = Number.parseInt(defaultFaces); + this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces; this.render(); } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 5e03a680..d58811fe 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -148,14 +148,22 @@ export default class DualityRoll extends D20Roll { } applyAdvantage() { - if (this.hasAdvantage || this.hasDisadvantage) { - const dieFaces = this.advantageFaces, - advDie = new foundry.dice.terms.Die({ faces: dieFaces, number: this.advantageNumber }); - if (this.advantageNumber > 1) advDie.modifiers = ['kh']; - this.terms.push( - new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }), - advDie - ); + const advDieClass = this.hasAdvantage + ? game.system.api.dice.diceTypes.AdvantageDie + : this.hasDisadvantage + ? game.system.api.dice.diceTypes.DisadvantageDie + : null; + if (advDieClass) { + const advDie = new advDieClass({ faces: this.advantageFaces, number: this.advantageNumber }); + if (this.terms.length < 4) { + if (this.advantageNumber > 1) advDie.modifiers = ['kh']; + this.terms.push( + new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }), + advDie + ); + } else { + this.terms[4] = advDie; + } } if (this.rallyFaces) this.terms.push( From ccc4186e42e96771ed5c00d4e9c3f8479f97792d Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 06:27:17 -0400 Subject: [PATCH 259/304] Disable contenteditable inputs when read only (#1935) --- module/applications/sheets/actors/character.mjs | 3 +++ module/applications/sheets/api/base-actor.mjs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index d85838e1..19b82712 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -184,6 +184,9 @@ export default class CharacterSheet extends DHBaseActorSheet { for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) { input.disabled = disabled; } + for (const element of form.querySelectorAll('.input[contenteditable]')) { + element.classList.toggle('disabled', disabled); + } } /** @inheritDoc */ diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index e23a4426..5cd0f6a5 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -166,6 +166,15 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { } } + /** Add support for input content editables */ + _toggleDisabled(disabled) { + super._toggleDisabled(disabled); + const form = this.form; + for (const element of form.querySelectorAll('.input[contenteditable]')) { + element.classList.toggle('disabled', disabled); + } + } + /* -------------------------------------------- */ /* Context Menu */ /* -------------------------------------------- */ From b9416ead5a31b28ea187f883432b5362dfa5c439 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 09:41:26 -0400 Subject: [PATCH 260/304] Fix usable checks on adversary features and locked compendium actors (#1934) --- module/data/action/baseAction.mjs | 8 ++++++-- module/documents/item.mjs | 6 ++++-- .../sheets/global/partials/inventory-item-compact.hbs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index b3775dc9..acd104a7 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -148,10 +148,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel : null; } - /** Returns true if the action is usable */ + /** + * Returns true if the action is usable. + * An action is usable on any actor type. For example, an adversary might have a base attack action. + */ get usable() { const actor = this.actor; - return this.isOwner && actor?.type === 'character'; + const pack = actor?.pack ? game.packs.get(actor.pack) : null; + return !pack?.locked && this.isOwner; } static getRollType(parent) { diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 93aa3b28..603ca594 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -73,8 +73,10 @@ export default class DHItem extends foundry.documents.Item { /** Returns true if the item can be used */ get usable() { const actor = this.actor; - const actionsList = this.system.actionsList; - return this.isOwner && actor?.type === 'character' && (actionsList?.size || actionsList?.length); + const pack = actor?.pack ? game.packs.get(actor.pack) : null; + const hasActions = this.system.actionsList?.size || this.system.actionsList?.length; + const isValidType = actor?.type === 'character' || this.type === 'feature'; + return !pack?.locked && this.isOwner && isValidType && hasActions; } /** @inheritdoc */ diff --git a/templates/sheets/global/partials/inventory-item-compact.hbs b/templates/sheets/global/partials/inventory-item-compact.hbs index daba6721..78eaf087 100644 --- a/templates/sheets/global/partials/inventory-item-compact.hbs +++ b/templates/sheets/global/partials/inventory-item-compact.hbs @@ -5,7 +5,7 @@ (hasProperty item "toChat" ) "toChat" "editDoc" ) }}' {{#unless hideTooltip}} {{#if (eq type 'attack' )}} data-tooltip="#attack#{{item.actor.uuid}}" {{else}} data-tooltip="#item#{{item.uuid}}" {{/if}} {{/unless}}> - {{#if (or item.system.actionsList.size item.system.actionsList.length item.actionType)}} + {{#if item.usable}} {{#if @root.isNPC}} d20 {{else}} From de0ab9d0476d7a1118c7d9152e8fcf1eb2285e41 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 09:43:36 -0400 Subject: [PATCH 261/304] Include more item types when viewing compendium browser from item tab (#1931) --- module/applications/ui/itemBrowser.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index d98cf2da..45e8863f 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -606,7 +606,16 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { items: { folder: 'equipments', render: { - noFolder: true + folders: [ + 'equipments', + 'ancestries', + 'classes', + 'subclasses', + 'domains', + 'communities', + 'beastforms' + // excluded: features + ] } }, compendium: {} From c2f8b34ef25a4e51e12585fb72c9de3ef4a26342 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 09:46:08 -0400 Subject: [PATCH 262/304] Show actor sheet when clicking actors in item browser (#1930) --- module/applications/ui/itemBrowser.mjs | 12 ++++++++++-- styles/less/ui/item-browser/item-browser.less | 2 +- templates/ui/itemBrowser/itemContainer.hbs | 16 +++++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 45e8863f..8f38918a 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -1,3 +1,4 @@ +import { getDocFromElement } from '../../helpers/utils.mjs'; import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -47,7 +48,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { expandContent: this.expandContent, resetFilters: this.resetFilters, sortList: this.sortList, - openSettings: this.openSettings + openSettings: this.openSettings, + viewSheet: this.#onViewSheet }, position: { left: 100, @@ -306,7 +308,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { { items: this.items, menu: this.selectedMenu, - formatLabel: this.formatLabel + formatLabel: this.formatLabel, + viewSheet: this.items[0] instanceof Actor } ); @@ -568,6 +571,11 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { } } + static async #onViewSheet(_, target) { + const document = await getDocFromElement(target); + document?.sheet?.render(true); + } + _createDragProcess() { new foundry.applications.ux.DragDrop.implementation({ dragSelector: '.item-container', diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index 066da73b..a40c0fae 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -245,7 +245,7 @@ } .item-list-header, - .item-list [data-action='expandContent'] { + .item-list .item-info[data-action] { display: flex; > * { diff --git a/templates/ui/itemBrowser/itemContainer.hbs b/templates/ui/itemBrowser/itemContainer.hbs index 8dd75156..a5f067e8 100644 --- a/templates/ui/itemBrowser/itemContainer.hbs +++ b/templates/ui/itemBrowser/itemContainer.hbs @@ -1,7 +1,7 @@ {{#each items}}
      -
      +
      {{name}} {{#each ../menu.data.columns}} @@ -9,11 +9,13 @@ {{/each}}
      -
      - - {{{system.enrichedTags}}} - {{{system.enrichedDescription}}} - -
      + {{#unless viewSheet}} +
      + + {{{system.enrichedTags}}} + {{{system.enrichedDescription}}} + +
      + {{/unless}}
      {{/each}} \ No newline at end of file From fa6f9d56b807cb893062632a5d413e697d23dfb7 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 16:18:52 -0400 Subject: [PATCH 263/304] Add emphatic color variable and set up scoped based overrides for core variables (#1932) --- .../character-creation/tab-navigation.less | 2 +- .../compendiumBrowserPackDialog/sheet.less | 1 - .../less/dialog/dice-roll/roll-selection.less | 4 ++-- .../dialog/downtime/downtime-container.less | 2 +- .../dialog/group-roll-dialog/_common.less | 2 +- .../less/dialog/group-roll-dialog/main.less | 2 +- .../dialog/level-up/navigation-container.less | 2 +- .../less/dialog/multiclass-choice/sheet.less | 2 +- .../tag-team-dialog/initialization.less | 2 +- styles/less/dialog/tag-team-dialog/sheet.less | 8 ++++---- styles/less/global/dialog.less | 4 ++-- styles/less/global/elements.less | 12 +++++------ styles/less/global/filter-menu.less | 4 ++-- styles/less/global/resource-bar.less | 2 +- styles/less/global/sheet.less | 2 +- styles/less/global/tab-navigation.less | 2 +- styles/less/hud/token-hud/token-hud.less | 2 +- styles/less/sheets-settings/header.less | 2 +- .../sheets/actors/actor-sheet-shared.less | 10 +++++----- .../less/sheets/actors/adversary/sidebar.less | 4 ++-- .../less/sheets/actors/character/header.less | 8 ++++---- .../less/sheets/actors/character/loadout.less | 2 +- .../less/sheets/actors/character/sidebar.less | 10 +++++----- .../sheets/actors/party/party-members.less | 14 ++++++------- styles/less/ui/chat/group-roll.less | 6 +++--- styles/less/ui/chat/sheet.less | 2 +- styles/less/ui/countdown/countdown.less | 2 +- styles/less/ui/item-browser/item-browser.less | 6 +++--- .../settings/homebrew-settings/resources.less | 2 +- styles/less/ui/settings/settings.less | 2 +- styles/less/ui/sidebar/daggerheartMenu.less | 2 +- styles/less/utils/colors.less | 20 +++++++++++++++++-- styles/less/ux/tooltip/armorManagement.less | 2 +- .../less/ux/tooltip/resource-management.less | 4 ++-- styles/less/ux/tooltip/sheet.less | 2 +- styles/less/ux/tooltip/tooltip.less | 2 +- 36 files changed, 86 insertions(+), 71 deletions(-) diff --git a/styles/less/dialog/character-creation/tab-navigation.less b/styles/less/dialog/character-creation/tab-navigation.less index 36b89d5a..85541db7 100644 --- a/styles/less/dialog/character-creation/tab-navigation.less +++ b/styles/less/dialog/character-creation/tab-navigation.less @@ -7,7 +7,7 @@ border-top: 0; a { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &[disabled] { opacity: 0.4; diff --git a/styles/less/dialog/compendiumBrowserPackDialog/sheet.less b/styles/less/dialog/compendiumBrowserPackDialog/sheet.less index dfe375b5..b16f1086 100644 --- a/styles/less/dialog/compendiumBrowserPackDialog/sheet.less +++ b/styles/less/dialog/compendiumBrowserPackDialog/sheet.less @@ -67,7 +67,6 @@ i { font-size: 18px; - // color: light-dark(@dark-blue, @golden); } } } diff --git a/styles/less/dialog/dice-roll/roll-selection.less b/styles/less/dialog/dice-roll/roll-selection.less index a1a01e6b..e3551902 100644 --- a/styles/less/dialog/dice-roll/roll-selection.less +++ b/styles/less/dialog/dice-roll/roll-selection.less @@ -56,7 +56,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .label { font-style: normal; @@ -129,7 +129,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .label { font-style: normal; diff --git a/styles/less/dialog/downtime/downtime-container.less b/styles/less/dialog/downtime/downtime-container.less index eb615ef0..a7945d4c 100644 --- a/styles/less/dialog/downtime/downtime-container.less +++ b/styles/less/dialog/downtime/downtime-container.less @@ -37,7 +37,7 @@ .activity-marker { font-size: 0.5rem; flex: none; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; margin-right: 4px; } diff --git a/styles/less/dialog/group-roll-dialog/_common.less b/styles/less/dialog/group-roll-dialog/_common.less index b04f6893..f74ab8a0 100644 --- a/styles/less/dialog/group-roll-dialog/_common.less +++ b/styles/less/dialog/group-roll-dialog/_common.less @@ -1,5 +1,5 @@ h1 { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font: 700 var(--font-size-24) var(--dh-font-subtitle); text-align: center; } diff --git a/styles/less/dialog/group-roll-dialog/main.less b/styles/less/dialog/group-roll-dialog/main.less index f266dcc7..e30f4e29 100644 --- a/styles/less/dialog/group-roll-dialog/main.less +++ b/styles/less/dialog/group-roll-dialog/main.less @@ -110,7 +110,7 @@ display: flex; flex-direction: row; button { - --button-text-color: var(--color-text-primary); + --button-text-color: @color-text-primary; --button-size: 1.5em; padding: 0 var(--spacer-4); img { diff --git a/styles/less/dialog/level-up/navigation-container.less b/styles/less/dialog/level-up/navigation-container.less index 282d683f..6bf80d7c 100644 --- a/styles/less/dialog/level-up/navigation-container.less +++ b/styles/less/dialog/level-up/navigation-container.less @@ -19,7 +19,7 @@ a, span { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/dialog/multiclass-choice/sheet.less b/styles/less/dialog/multiclass-choice/sheet.less index 1f38449a..d848f203 100644 --- a/styles/less/dialog/multiclass-choice/sheet.less +++ b/styles/less/dialog/multiclass-choice/sheet.less @@ -35,7 +35,7 @@ width: 120px; height: 120px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.selected { background: light-dark(@dark-blue-40, @golden-40); diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index f53a7af4..14a3f41b 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -62,7 +62,7 @@ padding: 5rem 4px var(--spacer-8) 4px; text-align: center; - color: var(--color-text-primary); + color: @color-text-primary; text-shadow: 1px 1px 2px var(--shadow-color), 0 0 10px var(--shadow-color); .smooth-gradient-ease-in-out(background-image, to bottom, var(--shadow-color), 100%); } diff --git a/styles/less/dialog/tag-team-dialog/sheet.less b/styles/less/dialog/tag-team-dialog/sheet.less index a8dffbd2..22f0d0bb 100644 --- a/styles/less/dialog/tag-team-dialog/sheet.less +++ b/styles/less/dialog/tag-team-dialog/sheet.less @@ -1,6 +1,6 @@ .daggerheart.dialog.dh-style.views.tag-team-dialog .window-content { h1 { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font: 700 var(--font-size-24) var(--dh-font-subtitle); text-align: center; } @@ -64,7 +64,7 @@ .roll-title { font-size: var(--font-size-20); font-weight: bold; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; text-align: center; display: flex; align-items: center; @@ -72,7 +72,7 @@ &::before, &::after { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; content: ''; flex: 1; height: 2px; @@ -202,7 +202,7 @@ justify-content: center; i { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-size: 48px; &.inactive { diff --git a/styles/less/global/dialog.less b/styles/less/global/dialog.less index a3400700..fb4097e7 100644 --- a/styles/less/global/dialog.less +++ b/styles/less/global/dialog.less @@ -37,7 +37,7 @@ &:hover { border: 1px solid light-dark(@dark-blue, @golden); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } @@ -81,7 +81,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .label { font-style: normal; diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 7d46d627..d918e809 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -14,7 +14,7 @@ box-shadow: 0 4px 30px @soft-shadow; backdrop-filter: blur(9.5px); outline: 2px solid transparent; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; border: 1px solid light-dark(@dark, @beige); transition: all 0.3s ease; @@ -107,7 +107,7 @@ &:hover { background: light-dark(@light-black, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } &.glow { @@ -128,7 +128,7 @@ &.reverted { background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; border: 1px solid light-dark(@dark, transparent); &:hover { background: light-dark(transparent, @golden); @@ -175,7 +175,7 @@ height: inherit; .tag { padding: 0.3rem 0.5rem; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; background-color: light-dark(@dark-blue-10, @golden-40); border-radius: 3px; transition: 0.13s ease-out; @@ -353,7 +353,7 @@ legend { font-weight: bold; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.with-icon { display: flex; @@ -571,7 +571,7 @@ border: 0; &:hover { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } &:not(:first-child) { diff --git a/styles/less/global/filter-menu.less b/styles/less/global/filter-menu.less index 65a184f8..a0545950 100644 --- a/styles/less/global/filter-menu.less +++ b/styles/less/global/filter-menu.less @@ -13,7 +13,7 @@ legend { font-weight: bold; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-size: var(--font-size-12); } @@ -25,7 +25,7 @@ button { background: light-dark(@light-black, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; outline: none; box-shadow: none; border: 1px solid light-dark(@dark-blue, @dark-blue); diff --git a/styles/less/global/resource-bar.less b/styles/less/global/resource-bar.less index be9bc68b..ef411eee 100644 --- a/styles/less/global/resource-bar.less +++ b/styles/less/global/resource-bar.less @@ -53,7 +53,7 @@ border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; z-index: 1; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; width: fit-content; .slot { diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index 5ccb8788..e04c7573 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -43,7 +43,7 @@ body.game:is(.performance-low, .noblur) { &:hover { border-color: light-dark(@dark-blue, @golden); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/global/tab-navigation.less b/styles/less/global/tab-navigation.less index 3f8844f2..038a9749 100755 --- a/styles/less/global/tab-navigation.less +++ b/styles/less/global/tab-navigation.less @@ -20,7 +20,7 @@ white-space: nowrap; a { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/hud/token-hud/token-hud.less b/styles/less/hud/token-hud/token-hud.less index e31ede4a..3cb94e1e 100644 --- a/styles/less/hud/token-hud/token-hud.less +++ b/styles/less/hud/token-hud/token-hud.less @@ -24,7 +24,7 @@ .palette-category-title { grid-column: span var(--effect-columns); font-weight: bold; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/sheets-settings/header.less b/styles/less/sheets-settings/header.less index 82f3c488..04e2fa90 100644 --- a/styles/less/sheets-settings/header.less +++ b/styles/less/sheets-settings/header.less @@ -13,7 +13,7 @@ font-size: var(--font-size-24); margin: 0; text-align: center; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 6ef73035..5d669d4d 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -127,7 +127,7 @@ .title-name { text-align: start; font-size: var(--font-size-28); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; text-align: center; } } @@ -180,7 +180,7 @@ display: flex; gap: 10px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -194,7 +194,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } .domain { @@ -206,7 +206,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } img { @@ -230,7 +230,7 @@ padding: 10px; border-radius: 5px; min-width: 90px; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; background-color: light-dark(@dark-blue-10, @golden-40); } } diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index ef99bc09..8bb9834c 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -65,7 +65,7 @@ display: flex; gap: 10px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -77,7 +77,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.threshold-value { color: light-dark(@dark, @beige); diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index 21ea4846..9bd3d1ff 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -103,7 +103,7 @@ padding: 5px 0; margin-bottom: 8px; font-size: var(--font-size-12); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .missing-header-feature { opacity: 0.5; @@ -170,7 +170,7 @@ display: flex; gap: 4px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -182,7 +182,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; margin-right: 4px; } @@ -195,7 +195,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } img { diff --git a/styles/less/sheets/actors/character/loadout.less b/styles/less/sheets/actors/character/loadout.less index 127d688a..5c0abef3 100644 --- a/styles/less/sheets/actors/character/loadout.less +++ b/styles/less/sheets/actors/character/loadout.less @@ -54,7 +54,7 @@ span { margin: 1px; width: 26px; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.list-icon { i { diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index e450891b..a4c7c0db 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -286,7 +286,7 @@ h4, i { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } @@ -314,7 +314,7 @@ z-index: 1; background: @dark-blue; justify-content: center; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .armor-slot { cursor: pointer; @@ -348,7 +348,7 @@ .label, .value, i { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } @@ -513,7 +513,7 @@ align-self: center; gap: 10px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -525,7 +525,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.threshold-value { color: light-dark(@dark, @beige); diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index a3ec90ec..380f98b1 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -79,7 +79,7 @@ display: flex; gap: 4px; background-color: light-dark(var(--color-light-1), @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 4px 6px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 3px; @@ -93,7 +93,7 @@ &.threshold-label { font-size: var(--font-size-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } &.threshold-value { @@ -116,7 +116,7 @@ width: 100%; z-index: 1; font-size: var(--font-size-20); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-weight: bold; } @@ -132,7 +132,7 @@ .hope-section { display: flex; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 3px 6px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 3px; @@ -144,7 +144,7 @@ font-size: var(--font-size-12); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; margin-right: 3px; } @@ -212,7 +212,7 @@ gap: 4px; background-color: light-dark(@dark-blue-10, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 2px 5px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 0 6px 6px 0; @@ -260,7 +260,7 @@ justify-content: space-between; gap: 3px; .label { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } .value { font-weight: 600; diff --git a/styles/less/ui/chat/group-roll.less b/styles/less/ui/chat/group-roll.less index 9ed87220..98f0cfac 100644 --- a/styles/less/ui/chat/group-roll.less +++ b/styles/less/ui/chat/group-roll.less @@ -31,7 +31,7 @@ } i { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } } } @@ -71,7 +71,7 @@ align-items: center; justify-content: center; gap: 10px; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .main-value { font-size: var(--font-size-24); @@ -153,7 +153,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; &.finished { background-color: initial; diff --git a/styles/less/ui/chat/sheet.less b/styles/less/ui/chat/sheet.less index fb8cc104..bdf22364 100644 --- a/styles/less/ui/chat/sheet.less +++ b/styles/less/ui/chat/sheet.less @@ -93,7 +93,7 @@ } a[href] { - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } a[href]:hover, diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 380eb454..66a6c88a 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -17,7 +17,7 @@ position: relative; border: 0; box-shadow: none; - color: var(--color-text-primary); + color: @color-text-primary; width: 300px; pointer-events: all; align-self: flex-end; diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index a40c0fae..90da7ed3 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -200,7 +200,7 @@ font-weight: bold; border-radius: 3px; background-color: light-dark(@dark-blue-40, @golden-40); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } .subfolder-list { @@ -218,7 +218,7 @@ font-weight: bold; border-radius: 3px; background-color: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; } .wrapper { @@ -265,7 +265,7 @@ .item-list-header { align-items: center; background-color: light-dark(@dark-15, @dark-golden-80); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 3px; min-height: 30px; diff --git a/styles/less/ui/settings/homebrew-settings/resources.less b/styles/less/ui/settings/homebrew-settings/resources.less index 1184904b..9d562756 100644 --- a/styles/less/ui/settings/homebrew-settings/resources.less +++ b/styles/less/ui/settings/homebrew-settings/resources.less @@ -61,7 +61,7 @@ display: flex; align-items: center; gap: 4px; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; i { font-size: 14px; diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index d08f74e6..35c48480 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -1,7 +1,7 @@ @import '../../utils/colors.less'; .daggerheart.dh-style.setting { - --color-form-label: var(--color-text-primary); + --color-form-label: @color-text-primary; h2, h3, diff --git a/styles/less/ui/sidebar/daggerheartMenu.less b/styles/less/ui/sidebar/daggerheartMenu.less index 88b139c5..280d5ad3 100644 --- a/styles/less/ui/sidebar/daggerheartMenu.less +++ b/styles/less/ui/sidebar/daggerheartMenu.less @@ -34,7 +34,7 @@ cursor: pointer; padding: 5px; background: light-dark(@dark-blue-10, @golden-10); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; .label { font-style: normal; diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index d35ad8b3..3eeb4d54 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -83,8 +83,21 @@ --gradient-stress: linear-gradient(15deg, rgb(130, 59, 1) 0%, rgb(252, 142, 69) 65%, rgb(190, 0, 0) 100%); --primary-color-fear: rgba(9, 71, 179, 0.75); +} - --dh-color-text-subtle: light-dark(#555, #a29086); +@scope (.theme-light) to (.themed) { + .dh-style, + .duality { + --color-text-emphatic: @dark-blue; + --color-text-subtle: #555; + } +} +@scope (.theme-dark) to (.themed) { + .dh-style, + .duality { + --color-text-emphatic: @golden; + --color-text-subtle: #a29086; + } } @primary-blue: var(--primary-blue, #1488cc); @@ -193,4 +206,7 @@ } } -@color-text-subtle: var(--dh-color-text-subtle); \ No newline at end of file +// LESS variable versions of core foundry color variables +@color-text-emphatic: var(--color-text-emphatic); +@color-text-primary: var(--color-text-primary); +@color-text-subtle: var(--color-text-subtle); diff --git a/styles/less/ux/tooltip/armorManagement.less b/styles/less/ux/tooltip/armorManagement.less index e1ac6bb9..ca26e2e8 100644 --- a/styles/less/ux/tooltip/armorManagement.less +++ b/styles/less/ux/tooltip/armorManagement.less @@ -124,7 +124,7 @@ background: @dark-blue; align-items: center; justify-content: center; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; min-height: 30px; width: 100%; diff --git a/styles/less/ux/tooltip/resource-management.less b/styles/less/ux/tooltip/resource-management.less index ff1f4dd2..5daccd32 100644 --- a/styles/less/ux/tooltip/resource-management.less +++ b/styles/less/ux/tooltip/resource-management.less @@ -9,7 +9,7 @@ display: flex; gap: 10px; background-color: light-dark(transparent, @dark-blue); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; padding: 5px 10px; border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; @@ -22,7 +22,7 @@ font-size: var(--font-size-14); font-weight: bold; text-transform: uppercase; - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; margin: 0; } diff --git a/styles/less/ux/tooltip/sheet.less b/styles/less/ux/tooltip/sheet.less index ad774fcd..cc4166da 100644 --- a/styles/less/ux/tooltip/sheet.less +++ b/styles/less/ux/tooltip/sheet.less @@ -3,7 +3,7 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip), #tooltip.bordered-tooltip { .tooltip-title { font-size: var(--font-size-20); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-weight: 700; } diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less index 1566059f..541b3160 100644 --- a/styles/less/ux/tooltip/tooltip.less +++ b/styles/less/ux/tooltip/tooltip.less @@ -132,7 +132,7 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip.card-style) { border-radius: 3px; padding: 3px; background: light-dark(@dark-blue-60, @rustic-brown-80); - color: light-dark(@dark-blue, @golden); + color: @color-text-emphatic; font-size: 12px; margin-bottom: 10px; } From 48f9ffc318e3eda42d68baea51e0f19f7fba63eb Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Tue, 26 May 2026 21:58:05 -0400 Subject: [PATCH 264/304] Fix recent regression for scope rules (#1938) --- styles/less/utils/colors.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 3eeb4d54..dd626358 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -86,6 +86,8 @@ } @scope (.theme-light) to (.themed) { + .dh-style :scope, + :scope.dh-style, .dh-style, .duality { --color-text-emphatic: @dark-blue; @@ -93,6 +95,8 @@ } } @scope (.theme-dark) to (.themed) { + .dh-style :scope, + :scope.dh-style, .dh-style, .duality { --color-text-emphatic: @golden; From 1ab8170d2ff562693295fe1ad68ea3d4f18b7e71 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 27 May 2026 16:20:16 -0400 Subject: [PATCH 265/304] [Refactor] Define more border and input color variables (#1937) * Define more border and input color variables * Rename custom color variables * Fix assignment of variables * Apply border color variable to matching borders * Add trait header colors and shadow contrast --- styles/less/dialog/beastform/sheet.less | 10 +- .../selections-container.less | 14 +-- .../damage-reduction-container.less | 4 +- .../dialog/downtime/downtime-container.less | 4 +- styles/less/dialog/image-select/sheet.less | 2 +- .../less/dialog/multiclass-choice/sheet.less | 2 +- .../tag-team-dialog/initialization.less | 2 +- styles/less/dialog/tag-team-dialog/sheet.less | 2 +- styles/less/global/dialog.less | 2 +- styles/less/global/elements.less | 32 ++---- styles/less/global/inventory-item.less | 2 +- styles/less/global/resource-bar.less | 6 +- styles/less/global/sheet.less | 2 +- .../sheets/actors/actor-sheet-shared.less | 2 +- .../less/sheets/actors/adversary/sidebar.less | 6 +- .../less/sheets/actors/character/header.less | 16 +-- .../less/sheets/actors/character/loadout.less | 2 +- .../less/sheets/actors/character/sidebar.less | 18 +-- .../less/sheets/actors/companion/header.less | 106 +----------------- .../sheets/actors/environment/header.less | 7 +- styles/less/sheets/actors/party/header.less | 5 +- .../sheets/actors/party/party-members.less | 12 +- styles/less/ui/effects-display/sheet.less | 2 +- styles/less/ui/item-browser/item-browser.less | 2 +- .../settings/homebrew-settings/domains.less | 4 +- .../ui/settings/homebrew-settings/types.less | 2 +- styles/less/utils/colors.less | 35 +++++- styles/less/utils/mixin.less | 9 +- styles/less/ux/tooltip/armorManagement.less | 4 +- .../less/ux/tooltip/resource-management.less | 2 +- styles/less/ux/tooltip/tooltip.less | 2 +- 31 files changed, 120 insertions(+), 200 deletions(-) diff --git a/styles/less/dialog/beastform/sheet.less b/styles/less/dialog/beastform/sheet.less index 0e1fe746..6d1a8a2a 100644 --- a/styles/less/dialog/beastform/sheet.less +++ b/styles/less/dialog/beastform/sheet.less @@ -43,7 +43,7 @@ text-align: center; font-size: var(--font-size-16); margin: 0 4px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); @@ -58,7 +58,7 @@ position: relative; display: flex; justify-content: center; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; cursor: pointer; width: 120px; @@ -164,7 +164,7 @@ .hybrid-data { padding: 0 2px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); @@ -191,7 +191,7 @@ flex-direction: column; gap: 4px; padding: 0 4px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); @@ -226,7 +226,7 @@ gap: 4px; .trait-card { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; padding: 2px; opacity: 0.4; diff --git a/styles/less/dialog/character-creation/selections-container.less b/styles/less/dialog/character-creation/selections-container.less index 24217dbf..ebf12eda 100644 --- a/styles/less/dialog/character-creation/selections-container.less +++ b/styles/less/dialog/character-creation/selections-container.less @@ -79,7 +79,7 @@ font-weight: bold; padding: 0 2px; background-image: url(../assets/parchments/dh-parchment-light.png); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@beige, @dark); opacity: 0.4; @@ -203,7 +203,7 @@ height: 16px; width: 110px; min-height: unset; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; color: light-dark(@beige, @beige); background-color: light-dark(var(--color-warm-3), var(--color-warm-3)); @@ -230,7 +230,7 @@ .suggested-trait-container { width: 56px; white-space: nowrap; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@beige, @dark); background-image: url('../assets/parchments/dh-parchment-light.png'); @@ -345,7 +345,7 @@ display: flex; justify-content: center; position: relative; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; .nav-section-text { @@ -383,7 +383,7 @@ width: 56px; text-align: center; line-height: 1; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@beige, @dark); background-image: url(../assets/parchments/dh-parchment-light.png); @@ -447,7 +447,7 @@ height: 100%; .simple-equipment { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 8px; position: relative; display: flex; @@ -466,7 +466,7 @@ top: -8px; font-size: var(--font-size-12); white-space: nowrap; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: @dark; background-image: url('../assets/parchments/dh-parchment-light.png'); diff --git a/styles/less/dialog/damage-reduction/damage-reduction-container.less b/styles/less/dialog/damage-reduction/damage-reduction-container.less index e8242bdd..6f7ffb51 100644 --- a/styles/less/dialog/damage-reduction/damage-reduction-container.less +++ b/styles/less/dialog/damage-reduction/damage-reduction-container.less @@ -81,7 +81,7 @@ .mark-container { cursor: pointer; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; height: 26px; padding: 0 1px; @@ -126,7 +126,7 @@ width: 100%; .chip-inner-container { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; height: 26px; padding: 0 4px; diff --git a/styles/less/dialog/downtime/downtime-container.less b/styles/less/dialog/downtime/downtime-container.less index a7945d4c..33d153fd 100644 --- a/styles/less/dialog/downtime/downtime-container.less +++ b/styles/less/dialog/downtime/downtime-container.less @@ -55,7 +55,7 @@ .activity-selected-marker { font-size: var(--font-size-14); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url(../assets/parchments/dh-parchment-dark.png); @@ -78,7 +78,7 @@ } .refreshable-container { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); diff --git a/styles/less/dialog/image-select/sheet.less b/styles/less/dialog/image-select/sheet.less index 3ed4f583..7a3a8468 100644 --- a/styles/less/dialog/image-select/sheet.less +++ b/styles/less/dialog/image-select/sheet.less @@ -12,7 +12,7 @@ img { width: 136px; height: 136px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; opacity: 0.4; diff --git a/styles/less/dialog/multiclass-choice/sheet.less b/styles/less/dialog/multiclass-choice/sheet.less index d848f203..0c487cbc 100644 --- a/styles/less/dialog/multiclass-choice/sheet.less +++ b/styles/less/dialog/multiclass-choice/sheet.less @@ -57,7 +57,7 @@ display: flex; flex-wrap: wrap; font-style: italic; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; padding: 4px 4px; } diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index 14a3f41b..d6f7ad29 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -36,7 +36,7 @@ align-items: stretch; justify-content: center; border-radius: 6px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; overflow: hidden; height: 11.5rem; width: 122px; diff --git a/styles/less/dialog/tag-team-dialog/sheet.less b/styles/less/dialog/tag-team-dialog/sheet.less index 22f0d0bb..3a112146 100644 --- a/styles/less/dialog/tag-team-dialog/sheet.less +++ b/styles/less/dialog/tag-team-dialog/sheet.less @@ -42,7 +42,7 @@ img { height: 64px; border-radius: 6px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; } .member-name { diff --git a/styles/less/global/dialog.less b/styles/less/global/dialog.less index fb4097e7..1313d68b 100644 --- a/styles/less/global/dialog.less +++ b/styles/less/global/dialog.less @@ -36,7 +36,7 @@ padding: 0; &:hover { - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; color: @color-text-emphatic; } } diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index d918e809..7af8becd 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -2,8 +2,6 @@ @import '../utils/fonts.less'; .dh-style { - border: 1px solid light-dark(@dark-blue, @golden); - input[type='text'], input[type='number'], textarea, @@ -14,12 +12,12 @@ box-shadow: 0 4px 30px @soft-shadow; backdrop-filter: blur(9.5px); outline: 2px solid transparent; - color: @color-text-emphatic; - border: 1px solid light-dark(@dark, @beige); + color: @input-color-text; + border: 1px solid @input-color-border; transition: all 0.3s ease; &::placeholder { - color: light-dark(@dark-40, @beige-50); + color: @color-text-subtle; } &:hover, @@ -30,7 +28,7 @@ &:focus[type='number'] { background: light-dark(@soft-shadow, @semi-transparent-dark-blue); box-shadow: none; - outline: 2px solid light-dark(@dark, @beige); + outline: 2px solid @input-color-border; } &:disabled[type='text'], @@ -47,7 +45,7 @@ .input[contenteditable] { cursor: var(--cursor-text); &:empty:before { - color: light-dark(@dark-40, @beige-50); + color: @color-text-subtle; content: attr(placeholder); } } @@ -265,7 +263,7 @@ align-items: center; margin-top: 5px; border-radius: 6px; - border-color: light-dark(@dark-blue, @golden); + border-color: @color-fieldset-border; &.glassy { background-color: light-dark(@dark-blue-10, @golden-10); @@ -274,7 +272,7 @@ legend { padding: 2px 12px; border-radius: 3px; - background-color: light-dark(@dark-blue, @golden); + background-color: @color-fieldset-border; color: light-dark(@beige, @dark-blue); margin-bottom: var(--spacer-4); } @@ -365,18 +363,6 @@ } } - input[type='text'], - input[type='number'] { - color: light-dark(@dark, @beige); - transition: all 0.3s ease; - outline: 2px solid transparent; - - &:focus, - &:hover { - outline: 2px solid light-dark(@dark, @beige); - } - } - &[disabled], &.child-disabled .form-group, select[disabled], @@ -514,7 +500,7 @@ display: block; height: 1px; width: 100%; - border-bottom: 1px solid light-dark(@dark-blue, @golden); + border-bottom: 1px solid @color-border; mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); } @@ -522,7 +508,7 @@ display: block; height: 1px; width: 100%; - border-bottom: 1px solid light-dark(@dark-blue, @golden); + border-bottom: 1px solid @color-border; mask-image: linear-gradient(270deg, transparent 0%, black 100%); &.invert { diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index a2b9ebd8..3a5a9321 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -287,7 +287,7 @@ position: relative; height: 120px; width: 98px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; cursor: pointer; diff --git a/styles/less/global/resource-bar.less b/styles/less/global/resource-bar.less index ef411eee..d06b43a8 100644 --- a/styles/less/global/resource-bar.less +++ b/styles/less/global/resource-bar.less @@ -50,7 +50,7 @@ flex-wrap: wrap; gap: 5px; padding: 5px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; color: @color-text-emphatic; @@ -59,7 +59,7 @@ .slot { width: 15px; height: 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; background: light-dark(@dark-blue-10, @golden-10); border-radius: 3px; transition: all 0.3s ease; @@ -148,7 +148,7 @@ appearance: none; width: 100px; height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index e04c7573..e3072da1 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -4,7 +4,7 @@ // Theme handling .appTheme({ - background: @dark-blue-d0; + background: @dark-blue-c0; backdrop-filter: blur(7px); }, { background: url('../assets/parchments/dh-parchment-light.png') no-repeat center; diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 5d669d4d..470067ca 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -182,7 +182,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index 8bb9834c..b1bb51db 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -67,7 +67,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; @@ -191,7 +191,7 @@ appearance: none; width: 100px; height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; @@ -237,7 +237,7 @@ display: flex; width: 50px; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; padding: 0 6px; diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index 9bd3d1ff..91b3545a 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -172,7 +172,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; @@ -226,7 +226,6 @@ padding-left: 0.5rem; .trait { - --color-border: light-dark(@semi-transparent-dark-blue, @golden-60); cursor: pointer; position: relative; @@ -238,10 +237,10 @@ .trait-name { position: relative; - background-color: light-dark(@semi-transparent-dark-blue, @golden-40); - border: 1px solid var(--color-border); + background-color: @trait-color-bg; + border: 1px solid @trait-color-border; border-radius: 3px; - color: light-dark(var(--color-light-1), @golden); + color: @color-text-emphatic; font-size: var(--font-size-12); font-weight: 600; height: 1rem; @@ -250,7 +249,7 @@ width: 100%; padding: 0 0.1876px 0 0.375rem; margin-right: 0.125rem; /* makes it center SLIGHTLY */ - text-shadow: 1px 1px 2px @light-black; + text-shadow: 1px 1px 3px @color-text-shadow-contrast; display: flex; align-items: center; @@ -259,7 +258,7 @@ .tier-mark { position: absolute; background-color: @dark-blue; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 50%; width: 1rem; height: 1rem; @@ -279,6 +278,7 @@ } .trait-value-area { + --color-border: @trait-color-border; --background: light-dark(#e8e6e3, @dark-blue); display: flex; position: relative; @@ -298,7 +298,7 @@ .spellcasting-mark { position: absolute; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; color: @golden; left: 0; right: 0; diff --git a/styles/less/sheets/actors/character/loadout.less b/styles/less/sheets/actors/character/loadout.less index 5c0abef3..a896b92e 100644 --- a/styles/less/sheets/actors/character/loadout.less +++ b/styles/less/sheets/actors/character/loadout.less @@ -45,7 +45,7 @@ .btn-toggle-view { background: light-dark(@dark-blue-10, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 15px; padding: 0; gap: 0; diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index a4c7c0db..3c358d8f 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -40,7 +40,7 @@ .application.sheet.dh-style .character-sidebar-sheet { width: 275px; min-width: 275px; - border-right: 1px solid light-dark(@dark-blue, @golden); + border-right: 1px solid @color-border; .portrait { position: relative; @@ -168,7 +168,7 @@ appearance: none; width: 100px; height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; @@ -282,7 +282,7 @@ &:hover { background: light-dark(@light-black, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; h4, i { @@ -309,7 +309,7 @@ flex-wrap: wrap; gap: 4px; padding: 5px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; @@ -343,7 +343,7 @@ &:hover { background: light-dark(@light-black, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; .label, .value, @@ -375,7 +375,7 @@ text-align: center; z-index: 2; color: light-dark(@dark-blue, @beige); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; @@ -411,7 +411,7 @@ appearance: none; width: 80px; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: light-dark(transparent, @dark-blue); @@ -450,7 +450,7 @@ display: flex; width: 50px; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; padding: 0 6px; @@ -515,7 +515,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index 6cf886ab..b4df96bf 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -30,14 +30,14 @@ font-size: var(--font-size-24); outline: 2px solid transparent; box-shadow: unset; - text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); - + text-shadow: 0 0 4px @color-text-shadow-contrast, 0 0 8px @color-text-shadow-contrast, 0 0 14px @color-text-shadow-contrast; + height: 2rem; text-align: center; transition: all 0.3s ease; &:hover { - outline: 2px solid light-dark(@dark, @golden); + outline: 2px solid @color-border; } } } @@ -68,7 +68,7 @@ display: flex; width: 50px; height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; padding: 0 6px; @@ -100,104 +100,6 @@ } } - // .status-bar { - // display: flex; - // justify-content: center; - // position: relative; - // width: 100px; - // height: 40px; - - // .status-label { - // position: relative; - // top: 40px; - // height: 22px; - // width: 79px; - // clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); - // background: light-dark(@dark-blue, @golden); - - // h4 { - // font-weight: bold; - // text-align: center; - // line-height: 18px; - // color: light-dark(@beige, @dark-blue); - // } - // } - // .status-value { - // position: absolute; - // display: flex; - // padding: 0 6px; - // font-size: 1.5rem; - // align-items: center; - // width: 100px; - // height: 40px; - // justify-content: center; - // text-align: center; - // z-index: 2; - // color: @beige; - - // input[type='number'] { - // background: transparent; - // font-size: 1.5rem; - // width: 40px; - // height: 30px; - // text-align: center; - // border: none; - // outline: 2px solid transparent; - // color: @beige; - - // &.bar-input { - // padding: 0; - // color: @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: 40px; - // } - // } - // .progress-bar { - // position: absolute; - // appearance: none; - // width: 100px; - // height: 40px; - // border: 1px solid light-dark(@dark-blue, @golden); - // border-radius: 6px; - // z-index: 1; - // background: @dark-blue; - - // &::-webkit-progress-bar { - // border: none; - // background: @dark-blue; - // border-radius: 6px; - // } - // &::-webkit-progress-value { - // background: @gradient-hp; - // border-radius: 6px; - // } - // &.stress-color::-webkit-progress-value { - // background: @gradient-stress; - // border-radius: 6px; - // } - // &::-moz-progress-bar { - // background: @gradient-hp; - // border-radius: 6px; - // } - // &.stress-color::-moz-progress-bar { - // background: @gradient-stress; - // border-radius: 6px; - // } - // } - // } - .level-div { white-space: nowrap; display: flex; diff --git a/styles/less/sheets/actors/environment/header.less b/styles/less/sheets/actors/environment/header.less index ce7e6163..85471af4 100644 --- a/styles/less/sheets/actors/environment/header.less +++ b/styles/less/sheets/actors/environment/header.less @@ -36,15 +36,14 @@ transition: all 0.3s ease; outline: 2px solid transparent; box-shadow: none; - text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); - + text-shadow: 0 0 4px @color-text-shadow-contrast, 0 0 8px @color-text-shadow-contrast, 0 0 14px @color-text-shadow-contrast; padding-left: 0; height: 2.625rem; &:hover[type='text'], &:focus[type='text'] { box-shadow: none; - outline: 2px solid light-dark(@dark-blue, @golden); + outline: 2px solid @color-border; } } @@ -98,7 +97,7 @@ display: flex; width: 50px; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-bottom: none; border-radius: 6px 6px 0 0; padding: 0 6px; diff --git a/styles/less/sheets/actors/party/header.less b/styles/less/sheets/actors/party/header.less index 18d69834..58e3bc31 100644 --- a/styles/less/sheets/actors/party/header.less +++ b/styles/less/sheets/actors/party/header.less @@ -20,11 +20,12 @@ input.item-name[type='text'] { backdrop-filter: none; border: none; + color: @color-text-emphatic; font-family: @font-title; font-size: var(--font-size-32); outline: 2px solid transparent; box-shadow: unset; - text-shadow: 0 0 4px light-dark(white, @dark-80), 0 0 8px light-dark(white, @dark-80), 0 0 14px light-dark(white, @dark-80); + text-shadow: 0 0 4px @color-text-shadow-contrast, 0 0 8px @color-text-shadow-contrast, 0 0 14px @color-text-shadow-contrast; text-align: center; transition: all 0.3s ease; @@ -33,7 +34,7 @@ &:hover[type='text'], &:focus[type='text'] { - outline: 2px solid light-dark(@dark-blue, @golden); + outline: 2px solid @color-border; } } diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index 380f98b1..dc464291 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -48,7 +48,7 @@ border-radius: 50%; width: 24px; height: 24px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; object-fit: cover; } } @@ -81,7 +81,7 @@ background-color: light-dark(var(--color-light-1), @dark-blue); color: @color-text-emphatic; padding: 4px 6px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 3px; align-items: baseline; width: fit-content; @@ -134,7 +134,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 3px 6px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 3px; align-items: center; width: fit-content; @@ -214,7 +214,7 @@ background-color: light-dark(@dark-blue-10, @dark-blue); color: @color-text-emphatic; padding: 2px 5px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 0 6px 6px 0; width: fit-content; min-height: 22px; @@ -232,7 +232,7 @@ .slot { width: 16px; height: 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; background: light-dark(@dark-blue-10, @golden-10); border-radius: 3px; transition: all 0.3s ease; @@ -248,7 +248,7 @@ .traits { background-color: light-dark(@dark-blue-10, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; display: grid; grid-template-columns: 1fr 1fr; diff --git a/styles/less/ui/effects-display/sheet.less b/styles/less/ui/effects-display/sheet.less index 17d9889f..80ad0d65 100644 --- a/styles/less/ui/effects-display/sheet.less +++ b/styles/less/ui/effects-display/sheet.less @@ -20,7 +20,7 @@ .effect-container { position: relative; pointer-events: all; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 3px; img { diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index 90da7ed3..1387f444 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -266,7 +266,7 @@ align-items: center; background-color: light-dark(@dark-15, @dark-golden-80); color: @color-text-emphatic; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 3px; min-height: 30px; font-weight: bold; diff --git a/styles/less/ui/settings/homebrew-settings/domains.less b/styles/less/ui/settings/homebrew-settings/domains.less index 406294ac..6314cc66 100644 --- a/styles/less/ui/settings/homebrew-settings/domains.less +++ b/styles/less/ui/settings/homebrew-settings/domains.less @@ -60,7 +60,7 @@ position: relative; display: flex; justify-content: center; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; &.selectable { @@ -76,7 +76,7 @@ .domain-label { position: absolute; top: 4px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; padding: 0 2px; color: light-dark(@dark, @beige); diff --git a/styles/less/ui/settings/homebrew-settings/types.less b/styles/less/ui/settings/homebrew-settings/types.less index d09431f7..1d568853 100644 --- a/styles/less/ui/settings/homebrew-settings/types.less +++ b/styles/less/ui/settings/homebrew-settings/types.less @@ -21,7 +21,7 @@ border: 1px solid; border-radius: 6px; padding: 0 8px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; color: light-dark(@dark, @beige); background-image: url('../assets/parchments/dh-parchment-dark.png'); cursor: pointer; diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index dd626358..bb219ebb 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -51,7 +51,7 @@ --dark-blue-50: #18162e50; --dark-blue-60: #18162e60; --dark-blue-90: #18162e90; - --dark-blue-d0: #18162ed0; + --dark-blue-c0: #18162ec0; --semi-transparent-dark-blue: rgba(24, 22, 46, 0.33); --dark: #222222; @@ -71,6 +71,7 @@ --beige-filter: brightness(0) saturate(100%) invert(87%) sepia(25%) saturate(164%) hue-rotate(339deg) brightness(106%) contrast(87%); --bright-beige-filter: brightness(0) saturate(100%) invert(87%) sepia(15%) saturate(343%) hue-rotate(333deg) brightness(110%) contrast(87%); + --light-white: rgba(255, 255, 255, 0.3); --soft-white-shadow: rgba(255, 255, 255, 0.05); --light-black: rgba(0, 0, 0, 0.3); @@ -85,13 +86,27 @@ --primary-color-fear: rgba(9, 71, 179, 0.75); } +/** + * Override core foundry color variables in theme scopes, and add some new theme specific variants. + * These are intended to be more representative of its use case, so avoid naming these after the color. + * Some foundry variables are heavily scoped and we need to redefine them (https://github.com/foundryvtt/foundryvtt/issues/12893) + * Convention for the redefined ones is --dh-{element}-color-{thing} + */ @scope (.theme-light) to (.themed) { .dh-style :scope, :scope.dh-style, .dh-style, .duality { + --color-border: @dark-blue; + --color-fieldset-border: @dark-blue; --color-text-emphatic: @dark-blue; --color-text-subtle: #555; + + --dh-color-text-shadow-contrast: #ffffffa0; + --dh-input-color-border: @dark; + --dh-input-color-text: @dark; + --dh-trait-color-bg: #b1afb6; + --dh-trait-color-border: #8e8d96; } } @scope (.theme-dark) to (.themed) { @@ -99,8 +114,16 @@ :scope.dh-style, .dh-style, .duality { + --color-border: @golden; + --color-fieldset-border: @golden; --color-text-emphatic: @golden; --color-text-subtle: #a29086; + + --dh-color-text-shadow-contrast: @dark-80; + --dh-input-color-border: @beige; + --dh-input-color-text: @beige; + --dh-trait-color-bg: #50433F; + --dh-trait-color-border: #927952; } } @@ -156,7 +179,7 @@ @dark-blue-50: var(--dark-blue-50, #18162e50); @dark-blue-60: var(--dark-blue-60, #18162e60); @dark-blue-90: var(--dark-blue-90, #18162e90); -@dark-blue-d0: var(--dark-blue-d0, #18162ed0); +@dark-blue-c0: var(--dark-blue-c0, #18162ec0); @semi-transparent-dark-blue: var(--semi-transparent-dark-blue, rgba(24, 22, 46, 0.33)); @dark: var(--dark, #222222); @@ -178,6 +201,7 @@ @soft-white-shadow: var(--soft-white-shadow, rgba(255, 255, 255, 0.05)); +@light-white: var(--light-white, rgba(255, 255, 255, 0.3)); @light-black: var(--light-black, rgba(0, 0, 0, 0.3)); @soft-shadow: var(--soft-shadow, rgba(0, 0, 0, 0.05)); @@ -211,6 +235,13 @@ } // LESS variable versions of core foundry color variables +@color-border: var(--color-border); +@color-fieldset-border: var(--color-fieldset-border); @color-text-emphatic: var(--color-text-emphatic); @color-text-primary: var(--color-text-primary); @color-text-subtle: var(--color-text-subtle); +@color-text-shadow-contrast: var(--dh-color-text-shadow-contrast); +@input-color-border: var(--dh-input-color-border); +@input-color-text: var(--dh-input-color-text); +@trait-color-bg: var(--dh-trait-color-bg); +@trait-color-border: var(--dh-trait-color-border); \ No newline at end of file diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index b37bfc06..f2d31232 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -50,14 +50,15 @@ */ .dh-typography() { h1 { + --dh-input-color-text: @color-text-emphatic; font-family: @font-title; margin: 0; border: none; font-weight: normal; - } - - h1 input[type='text'] { - font-family: @font-title; + input[type='text'], + .input[contenteditable] { + font-family: @font-title; + } } h2, diff --git a/styles/less/ux/tooltip/armorManagement.less b/styles/less/ux/tooltip/armorManagement.less index ca26e2e8..497df4f5 100644 --- a/styles/less/ux/tooltip/armorManagement.less +++ b/styles/less/ux/tooltip/armorManagement.less @@ -83,7 +83,7 @@ appearance: none; width: 100%; height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; @@ -118,7 +118,7 @@ flex-wrap: wrap; gap: 4px; padding: 5px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; z-index: 1; background: @dark-blue; diff --git a/styles/less/ux/tooltip/resource-management.less b/styles/less/ux/tooltip/resource-management.less index 5daccd32..6e7e7851 100644 --- a/styles/less/ux/tooltip/resource-management.less +++ b/styles/less/ux/tooltip/resource-management.less @@ -11,7 +11,7 @@ background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; align-items: center; width: fit-content; diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less index 541b3160..f02499e2 100644 --- a/styles/less/ux/tooltip/tooltip.less +++ b/styles/less/ux/tooltip/tooltip.less @@ -238,7 +238,7 @@ aside[role='tooltip'].locked-tooltip:has(div.daggerheart.dh-style.tooltip.card-s .tooltip-chip { font-size: var(--font-size-18); padding: 2px 4px; - border: 1px solid light-dark(@dark-blue, @golden); + border: 1px solid @color-border; border-radius: 6px; color: light-dark(@dark, @beige); background-image: url(../assets/parchments/dh-parchment-dark.png); From ac72012387e02cf8878b9ce7e88a015be46cbefe Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Wed, 27 May 2026 16:20:53 -0400 Subject: [PATCH 266/304] Fix setting dialogs created from overriden light sheet actors (#1939) --- .../character-settings/sheet.less | 18 ++++++++++-------- styles/less/utils/mixin.less | 8 ++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/styles/less/sheets-settings/character-settings/sheet.less b/styles/less/sheets-settings/character-settings/sheet.less index eab29436..37906712 100644 --- a/styles/less/sheets-settings/character-settings/sheet.less +++ b/styles/less/sheets-settings/character-settings/sheet.less @@ -1,17 +1,19 @@ @import '../../utils/colors.less'; -.theme-light .application.daggerheart.dh-style.dialog { - .tab.details { - .traits-inner-container .trait-container { - background: url('../assets/svg/trait-shield-light.svg') no-repeat; +.appTheme({}, { + &.dialog.character-settings { + .tab.details { + .traits-inner-container .trait-container { + background: url('../assets/svg/trait-shield-light.svg') no-repeat; - div { - filter: drop-shadow(0 0 3px @beige); - text-shadow: 0 0 3px @beige; + div { + filter: drop-shadow(0 0 3px @beige); + text-shadow: 0 0 3px @beige; + } } } } -} +}); .application.daggerheart.dh-style.dialog { .tab.details { diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index f2d31232..237a5acb 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -5,16 +5,16 @@ */ .appTheme(@darkRules, @lightRules) { // Dark theme selectors - .themed.theme-dark .application.daggerheart.sheet.dh-style, - .themed.theme-dark.application.daggerheart.sheet.dh-style, + .themed.theme-dark .application.daggerheart.dh-style, + .themed.theme-dark.application.daggerheart.dh-style, body.theme-dark .application.daggerheart, body.theme-dark.application.daggerheart { @darkRules(); } // Light theme selectors - .themed.theme-light .application.daggerheart.sheet.dh-style, - .themed.theme-light.application.daggerheart.sheet.dh-style, + .themed.theme-light .application.daggerheart.dh-style, + .themed.theme-light.application.daggerheart.dh-style, body.theme-light .application.daggerheart, body.theme-light.application.daggerheart { @lightRules(); From ddf4747310cdb39de9ca95738aa6084e0f1a7f89 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 27 May 2026 22:25:00 +0200 Subject: [PATCH 267/304] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index bbee2c09..2acd7570 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.2.6", + "version": "2.2.7", "compatibility": { "minimum": "14.361", "verified": "14.363", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.6/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.7/system.zip", "authors": [ { "name": "WBHarry" From f1a530f57f71f18c40b34e3b345cad40b1cda5c0 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 29 May 2026 12:19:08 +0200 Subject: [PATCH 268/304] [Feature] Full Rerolls (#1928) * Initial * Removed damage dialogs * Fixed DamageReroll * Fixed d20 modifiers * Fixed * Fixed DiceSoNice multiple damageType reroll * Added triggerChatRollFx * Fixed dice.denomination being lost on damage reroll --- lang/en.json | 7 +- module/applications/dialogs/_module.mjs | 1 - .../applications/dialogs/groupRollDialog.mjs | 2 - .../dialogs/rerollDamageDialog.mjs | 280 ------------------ module/applications/dialogs/rerollDialog.mjs | 279 ----------------- .../dialogs/resourceDiceDialog.mjs | 4 +- module/applications/dialogs/tagTeamDialog.mjs | 2 - module/applications/ui/chatLog.mjs | 18 +- module/data/chat-message/actorRoll.mjs | 31 ++ module/data/fields/action/damageField.mjs | 3 - module/data/fields/action/summonField.mjs | 6 +- module/dice/d20Roll.mjs | 12 + module/dice/damageRoll.mjs | 42 +-- module/dice/dhRoll.mjs | 6 +- module/dice/die/dualityDie.mjs | 22 +- module/dice/dualityRoll.mjs | 46 ++- module/dice/helpers.mjs | 17 ++ module/helpers/utils.mjs | 15 + styles/less/dialog/index.less | 2 - styles/less/dialog/reroll-dialog/sheet.less | 125 -------- .../dialogs/rerollDialog/damage/main.hbs | 35 --- templates/dialogs/rerollDialog/footer.hbs | 4 - templates/dialogs/rerollDialog/main.hbs | 35 --- 23 files changed, 164 insertions(+), 830 deletions(-) delete mode 100644 module/applications/dialogs/rerollDamageDialog.mjs delete mode 100644 module/applications/dialogs/rerollDialog.mjs create mode 100644 module/dice/helpers.mjs delete mode 100644 styles/less/dialog/reroll-dialog/sheet.less delete mode 100644 templates/dialogs/rerollDialog/damage/main.hbs delete mode 100644 templates/dialogs/rerollDialog/footer.hbs delete mode 100644 templates/dialogs/rerollDialog/main.hbs diff --git a/lang/en.json b/lang/en.json index a06c46c2..f1841e09 100755 --- a/lang/en.json +++ b/lang/en.json @@ -712,12 +712,6 @@ "ReactionRoll": { "title": "Reaction Roll: {trait}" }, - "RerollDialog": { - "title": "Reroll", - "damageTitle": "Reroll Damage", - "deselectDiceNotification": "Deselect one of the selected dice first", - "acceptCurrentRolls": "Accept Current Rolls" - }, "ResourceDice": { "title": "{name} Resource", "rerollDice": "Reroll Dice" @@ -3097,6 +3091,7 @@ } }, "ChatLog": { + "rerollActionRoll": "Reroll Action", "rerollDamage": "Reroll Damage", "assignTagRoll": "Assign as Tag Roll" }, diff --git a/module/applications/dialogs/_module.mjs b/module/applications/dialogs/_module.mjs index c866f1cd..fc5169b2 100644 --- a/module/applications/dialogs/_module.mjs +++ b/module/applications/dialogs/_module.mjs @@ -10,7 +10,6 @@ export { default as ImageSelectDialog } from './imageSelectDialog.mjs'; export { default as ItemTransferDialog } from './itemTransfer.mjs'; export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs'; export { default as OwnershipSelection } from './ownershipSelection.mjs'; -export { default as RerollDamageDialog } from './rerollDamageDialog.mjs'; export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs'; export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs'; export { default as TagTeamDialog } from './tagTeamDialog.mjs'; diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index bd45fe91..52baf537 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -358,8 +358,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat }); if (!result) return; - // todo: move logic to actor.rollTrait() or actor.diceRoll() - if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); const rollData = result.messageRoll.toJSON(); delete rollData.options.messageRoll; diff --git a/module/applications/dialogs/rerollDamageDialog.mjs b/module/applications/dialogs/rerollDamageDialog.mjs deleted file mode 100644 index b821bd24..00000000 --- a/module/applications/dialogs/rerollDamageDialog.mjs +++ /dev/null @@ -1,280 +0,0 @@ -const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; - -export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(message, options = {}) { - super(options); - - this.message = message; - this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => { - const type = message.system.damage[typeKey]; - acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => { - const part = type.parts[partKey]; - acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => { - const dice = part.dice[diceKey]; - const activeResults = dice.results.filter(x => x.active); - acc[diceKey] = { - dice: dice.dice, - selectedResults: activeResults.length, - maxSelected: activeResults.length, - results: activeResults.map(x => ({ ...x, selected: true })) - }; - - return acc; - }, {}); - - return acc; - }, {}); - - return acc; - }, {}); - } - - static DEFAULT_OPTIONS = { - id: 'reroll-dialog', - classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'], - window: { - icon: 'fa-solid fa-dice' - }, - actions: { - toggleResult: RerollDamageDialog.#toggleResult, - selectRoll: RerollDamageDialog.#selectRoll, - doReroll: RerollDamageDialog.#doReroll, - save: RerollDamageDialog.#save - } - }; - - /** @override */ - static PARTS = { - main: { - id: 'main', - template: 'systems/daggerheart/templates/dialogs/rerollDialog/damage/main.hbs' - }, - footer: { - id: 'footer', - template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs' - } - }; - - get title() { - return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.damageTitle'); - } - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - htmlElement.querySelectorAll('.to-reroll-input').forEach(element => { - element.addEventListener('change', this.toggleDice.bind(this)); - }); - } - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.damage = this.damage; - context.disabledReroll = !this.getRerollDice().length; - context.saveDisabled = !this.isSelectionDone(); - - return context; - } - - static async #save() { - const update = { - 'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => { - const type = this.damage[typeKey]; - let typeTotal = 0; - const messageType = this.message.system.damage[typeKey]; - const parts = Object.keys(type).map(partKey => { - const part = type[partKey]; - const messagePart = messageType.parts[partKey]; - let partTotal = messagePart.modifierTotal; - const dice = Object.keys(part).map(diceKey => { - const dice = part[diceKey]; - const total = dice.results.reduce((acc, result) => { - if (result.active) acc += result.result; - return acc; - }, 0); - partTotal += total; - const messageDice = messagePart.dice[diceKey]; - return { - ...messageDice, - total: total, - results: dice.results.map(x => ({ - ...x, - hasRerolls: dice.results.length > 1 - })) - }; - }); - - typeTotal += partTotal; - return { - ...messagePart, - total: partTotal, - dice: dice - }; - }); - - acc[typeKey] = { - ...messageType, - total: typeTotal, - parts: parts - }; - - return acc; - }, {}) - }; - - await this.message.update(update); - await this.close(); - } - - getRerollDice() { - const rerollDice = []; - Object.keys(this.damage).forEach(typeKey => { - const type = this.damage[typeKey]; - Object.keys(type).forEach(partKey => { - const part = type[partKey]; - Object.keys(part).forEach(diceKey => { - const dice = part[diceKey]; - Object.keys(dice.results).forEach(resultKey => { - const result = dice.results[resultKey]; - if (result.toReroll) { - rerollDice.push({ - ...result, - dice: dice.dice, - type: typeKey, - part: partKey, - dice: diceKey, - result: resultKey - }); - } - }); - }); - }); - }); - - return rerollDice; - } - - isSelectionDone() { - const diceFinishedData = []; - Object.keys(this.damage).forEach(typeKey => { - const type = this.damage[typeKey]; - Object.keys(type).forEach(partKey => { - const part = type[partKey]; - Object.keys(part).forEach(diceKey => { - const dice = part[diceKey]; - const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0); - diceFinishedData.push(selected === dice.maxSelected); - }); - }); - }); - - return diceFinishedData.every(x => x); - } - - toggleDice(event) { - const target = event.target; - const { type, part, dice } = target.dataset; - const toggleDice = this.damage[type][part][dice]; - - const existingDiceRerolls = this.getRerollDice().filter( - x => x.type === type && x.part === part && x.dice === dice - ); - - const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length; - - toggleDice.toReroll = !allRerolled; - toggleDice.results.forEach(result => { - if (result.active) { - result.toReroll = !allRerolled; - } - }); - - this.render(); - } - - static #toggleResult(event) { - event.stopPropagation(); - - const target = event.target.closest('.to-reroll-result'); - const { type, part, dice, result } = target.dataset; - const toggleDice = this.damage[type][part][dice]; - const toggleResult = toggleDice.results[result]; - toggleResult.toReroll = !toggleResult.toReroll; - - const existingDiceRerolls = this.getRerollDice().filter( - x => x.type === type && x.part === part && x.dice === dice - ); - - const allToReroll = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length; - toggleDice.toReroll = allToReroll; - - this.render(); - } - - static async #selectRoll(_, button) { - const { type, part, dice, result } = button.dataset; - - const diceVal = this.damage[type][part][dice]; - const diceResult = diceVal.results[result]; - if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) { - return ui.notifications.warn( - game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification') - ); - } - - if (diceResult.active) { - diceVal.toReroll = false; - diceResult.toReroll = false; - } - - diceVal.selectedResults += diceResult.active ? -1 : 1; - diceResult.active = !diceResult.active; - - this.render(); - } - - static async #doReroll() { - const toReroll = this.getRerollDice().map(x => { - const { type, part, dice, result } = x; - const diceData = this.damage[type][part][dice].results[result]; - return { - ...diceData, - dice: this.damage[type][part][dice].dice, - typeKey: type, - partKey: part, - diceKey: dice, - resultsIndex: result - }; - }); - - const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate(); - - if (game.modules.get('dice-so-nice')?.active) { - const diceSoNiceRoll = { - _evaluated: true, - dice: roll.dice, - options: { appearance: {} } - }; - - await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); - } - - toReroll.forEach((data, index) => { - const { typeKey, partKey, diceKey, resultsIndex } = data; - const rerolledDice = roll.dice[index]; - - const dice = this.damage[typeKey][partKey][diceKey]; - dice.toReroll = false; - dice.results[resultsIndex].active = false; - dice.results[resultsIndex].discarded = true; - dice.results[resultsIndex].toReroll = false; - dice.results.splice(dice.results.length, 0, { - ...rerolledDice.results[0], - toReroll: false, - selected: true - }); - }); - - this.render(); - } -} diff --git a/module/applications/dialogs/rerollDialog.mjs b/module/applications/dialogs/rerollDialog.mjs deleted file mode 100644 index cae4e53a..00000000 --- a/module/applications/dialogs/rerollDialog.mjs +++ /dev/null @@ -1,279 +0,0 @@ -const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; - -export default class RerollDialog extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(message, options = {}) { - super(options); - - this.message = message; - this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => { - const type = message.system.damage[typeKey]; - acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => { - const part = type.parts[partKey]; - acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => { - const dice = part.dice[diceKey]; - const activeResults = dice.results.filter(x => x.active); - acc[diceKey] = { - dice: dice.dice, - selectedResults: activeResults.length, - maxSelected: activeResults.length, - results: activeResults.map(x => ({ ...x, selected: true })) - }; - - return acc; - }, {}); - - return acc; - }, {}); - - return acc; - }, {}); - } - - static DEFAULT_OPTIONS = { - id: 'reroll-dialog', - classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'], - window: { - icon: 'fa-solid fa-dice' - }, - actions: { - toggleResult: RerollDialog.#toggleResult, - selectRoll: RerollDialog.#selectRoll, - doReroll: RerollDialog.#doReroll, - save: RerollDialog.#save - } - }; - - /** @override */ - static PARTS = { - main: { - id: 'main', - template: 'systems/daggerheart/templates/dialogs/rerollDialog/main.hbs' - }, - footer: { - id: 'footer', - template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs' - } - }; - - get title() { - return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.title'); - } - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - htmlElement.querySelectorAll('.to-reroll-input').forEach(element => { - element.addEventListener('change', this.toggleDice.bind(this)); - }); - } - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.damage = this.damage; - context.disabledReroll = !this.getRerollDice().length; - context.saveDisabled = !this.isSelectionDone(); - - return context; - } - - static async #save() { - const update = { - 'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => { - const type = this.damage[typeKey]; - let typeTotal = 0; - const messageType = this.message.system.damage[typeKey]; - const parts = Object.keys(type).map(partKey => { - const part = type[partKey]; - const messagePart = messageType.parts[partKey]; - let partTotal = messagePart.modifierTotal; - const dice = Object.keys(part).map(diceKey => { - const dice = part[diceKey]; - const total = dice.results.reduce((acc, result) => { - if (result.active) acc += result.result; - return acc; - }, 0); - partTotal += total; - const messageDice = messagePart.dice[diceKey]; - return { - ...messageDice, - total: total, - results: dice.results.map(x => ({ - ...x, - hasRerolls: dice.results.length > 1 - })) - }; - }); - - typeTotal += partTotal; - return { - ...messagePart, - total: partTotal, - dice: dice - }; - }); - - acc[typeKey] = { - ...messageType, - total: typeTotal, - parts: parts - }; - - return acc; - }, {}) - }; - await this.message.update(update); - await this.close(); - } - - getRerollDice() { - const rerollDice = []; - Object.keys(this.damage).forEach(typeKey => { - const type = this.damage[typeKey]; - Object.keys(type).forEach(partKey => { - const part = type[partKey]; - Object.keys(part).forEach(diceKey => { - const dice = part[diceKey]; - Object.keys(dice.results).forEach(resultKey => { - const result = dice.results[resultKey]; - if (result.toReroll) { - rerollDice.push({ - ...result, - dice: dice.dice, - type: typeKey, - part: partKey, - dice: diceKey, - result: resultKey - }); - } - }); - }); - }); - }); - - return rerollDice; - } - - isSelectionDone() { - const diceFinishedData = []; - Object.keys(this.damage).forEach(typeKey => { - const type = this.damage[typeKey]; - Object.keys(type).forEach(partKey => { - const part = type[partKey]; - Object.keys(part).forEach(diceKey => { - const dice = part[diceKey]; - const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0); - diceFinishedData.push(selected === dice.maxSelected); - }); - }); - }); - - return diceFinishedData.every(x => x); - } - - toggleDice(event) { - const target = event.target; - const { type, part, dice } = target.dataset; - const toggleDice = this.damage[type][part][dice]; - - const existingDiceRerolls = this.getRerollDice().filter( - x => x.type === type && x.part === part && x.dice === dice - ); - - const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length; - - toggleDice.toReroll = !allRerolled; - toggleDice.results.forEach(result => { - if (result.active) { - result.toReroll = !allRerolled; - } - }); - - this.render(); - } - - static #toggleResult(event) { - event.stopPropagation(); - - const target = event.target.closest('.to-reroll-result'); - const { type, part, dice, result } = target.dataset; - const toggleDice = this.damage[type][part][dice]; - const toggleResult = toggleDice.results[result]; - toggleResult.toReroll = !toggleResult.toReroll; - - const existingDiceRerolls = this.getRerollDice().filter( - x => x.type === type && x.part === part && x.dice === dice - ); - - const allToReroll = existingDiceRerolls.length === toggleDice.results.length; - toggleDice.toReroll = allToReroll; - - this.render(); - } - - static async #selectRoll(_, button) { - const { type, part, dice, result } = button.dataset; - - const diceVal = this.damage[type][part][dice]; - const diceResult = diceVal.results[result]; - if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) { - return ui.notifications.warn( - game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification') - ); - } - - if (diceResult.active) { - diceVal.toReroll = false; - diceResult.toReroll = false; - } - - diceVal.selectedResults += diceResult.active ? -1 : 1; - diceResult.active = !diceResult.active; - - this.render(); - } - - static async #doReroll() { - const toReroll = this.getRerollDice().map(x => { - const { type, part, dice, result } = x; - const diceData = this.damage[type][part][dice].results[result]; - return { - ...diceData, - dice: this.damage[type][part][dice].dice, - typeKey: type, - partKey: part, - diceKey: dice, - resultsIndex: result - }; - }); - - const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate(); - - if (game.modules.get('dice-so-nice')?.active) { - const diceSoNiceRoll = { - _evaluated: true, - dice: roll.dice, - options: { appearance: {} } - }; - - await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); - } - - toReroll.forEach((data, index) => { - const { typeKey, partKey, diceKey, resultsIndex } = data; - const rerolledDice = roll.dice[index]; - - const dice = this.damage[typeKey][partKey][diceKey]; - dice.toReroll = false; - dice.results[resultsIndex].active = false; - dice.results[resultsIndex].discarded = true; - dice.results[resultsIndex].toReroll = false; - dice.results.splice(dice.results.length, 0, { - ...rerolledDice.results[0], - toReroll: false, - selected: true - }); - }); - - this.render(); - } -} diff --git a/module/applications/dialogs/resourceDiceDialog.mjs b/module/applications/dialogs/resourceDiceDialog.mjs index 32e1e5d8..8394538c 100644 --- a/module/applications/dialogs/resourceDiceDialog.mjs +++ b/module/applications/dialogs/resourceDiceDialog.mjs @@ -1,4 +1,4 @@ -import { itemAbleRollParse } from '../../helpers/utils.mjs'; +import { itemAbleRollParse, triggerChatRollFx } from '../../helpers/utils.mjs'; const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; @@ -69,7 +69,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item); 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); + await triggerChatRollFx([roll]); this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false })); this.resetUsed = true; diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index ba76831f..4e63d93b 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -434,8 +434,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio if (!result) return; - if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); - const rollData = result.messageRoll.toJSON(); delete rollData.options.messageRoll; this.updatePartyData( diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 34b25591..7036a5df 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -103,6 +103,19 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo _getEntryContextOptions() { return [ ...super._getEntryContextOptions(), + { + label: 'DAGGERHEART.UI.ChatLog.rerollActionRoll', + icon: '', + visible: li => { + const message = game.messages.get(li.dataset.messageId); + return message.system.hasRoll && (game.user.isGM || message.isAuthor); + }, + callback: async li => { + const message = game.messages.get(li.dataset.messageId); + const reroll = await message.rolls[0].reroll({ liveRoll: true }); + message.update({ rolls: [reroll] }); + } + }, { label: 'DAGGERHEART.UI.ChatLog.rerollDamage', icon: '', @@ -113,9 +126,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo : false; return (game.user.isGM || message.isAuthor) && hasRolledDamage; }, - callback: li => { + callback: async li => { const message = game.messages.get(li.dataset.messageId); - new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true }); + const update = await message.system.getRerolledDamage(); + message.update(update); } } ]; diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index eaa1cdc2..ccfe25ea 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -1,3 +1,5 @@ +import { triggerChatRollFx } from '../../helpers/utils.mjs'; + const fields = foundry.data.fields; const targetsField = () => @@ -130,6 +132,35 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { }); } + /* TODO: Change how damage data is stored somehow to enable better rerolling */ + async getRerolledDamage() { + if (!this.damage) return; + + const rerolls = []; + const update = { system: { damage: {} } }; + for (const partKey in this.damage) { + const part = this.damage[partKey]; + const testRoll = Roll.fromData(part.parts[0].roll); + const rerolled = await testRoll.reroll(); + rerolls.push(rerolled); + + if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] }; + const partData = update.system.damage[partKey].parts[0]; + update.system.damage[partKey].total = rerolled.total; + partData.modifierTotal = rerolled.terms.reduce((acc, x) => { + if (x.isDeterministic && !x.operator) acc += x.total; + return acc; + }, 0); + partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination })); + partData.total = rerolled.total; + partData.roll = rerolled.toJSON(); + } + + await triggerChatRollFx(rerolls); + + return update; + } + registerTargetHook() { if (!this.parent.isAuthor || !this.hasTarget) return; if (this.targetMode && this.parent.targetHook !== null) { diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 30a5ad7c..9b21d3ba 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -72,9 +72,6 @@ export default class DamageField extends fields.SchemaField { damageConfig.source.message = messageId; damageConfig.directDamage = !!damageConfig.source?.message; - // if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active) - // await game.dice3d.waitFor3DAnimationByMessageID(damageConfig.source.message); - const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig); if (!damageResult) return false; if (damageResult.actionChatMessageHandled) config.actionChatMessageHandled = true; diff --git a/module/data/fields/action/summonField.mjs b/module/data/fields/action/summonField.mjs index ec7881f7..a2275fa5 100644 --- a/module/data/fields/action/summonField.mjs +++ b/module/data/fields/action/summonField.mjs @@ -1,4 +1,4 @@ -import { itemAbleRollParse } from '../../../helpers/utils.mjs'; +import { itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs'; import FormulaField from '../formulaField.mjs'; const fields = foundry.data.fields; @@ -40,7 +40,7 @@ export default class DHSummonField extends fields.ArrayField { const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item)); await roll.evaluate(); const count = roll.total; - if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) rolls.push(roll); + if (!roll.isDeterministic) rolls.push(roll); const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); /* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */ @@ -56,7 +56,7 @@ export default class DHSummonField extends fields.ArrayField { } } - if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true))); + if (rolls.length) await triggerChatRollFx(rolls); this.actor.sheet?.minimize(); DHSummonField.handleSummon(summonData, this.actor); diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index 509f5d69..b1d3bd0b 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -1,4 +1,5 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; +import { triggerChatRollFx } from '../helpers/utils.mjs'; import DHRoll from './dhRoll.mjs'; export default class D20Roll extends DHRoll { @@ -224,4 +225,15 @@ export default class D20Roll extends DHRoll { resetFormula() { return (this._formula = this.constructor.getFormula(this.terms)); } + + async reroll(options) { + const result = await super.reroll(options); + if (this instanceof game.system.api.dice.DualityRoll) return result; + + if (options?.liveRoll) { + await triggerChatRollFx([result]); + } + + return result; + } } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 98fd8401..ef810ed7 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -1,5 +1,5 @@ import DamageDialog from '../applications/dialogs/damageDialog.mjs'; -import { parseRallyDice } from '../helpers/utils.mjs'; +import { parseRallyDice, triggerChatRollFx } from '../helpers/utils.mjs'; import DHRoll from './dhRoll.mjs'; export default class DamageRoll extends DHRoll { @@ -18,7 +18,12 @@ export default class DamageRoll extends DHRoll { if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate(); roll._evaluated = true; - const parts = config.roll.map(r => this.postEvaluate(r)); + + const parts = []; + for (const roll of config.roll) { + parts.push(this.postEvaluate(roll)); + roll.roll = JSON.stringify(roll.roll.toJSON()); + } config.damage = this.unifyDamageRoll(parts); } @@ -38,25 +43,24 @@ export default class DamageRoll extends DHRoll { const chatMessage = config.source?.message ? ui.chat.collection.get(config.source.message) : getDocumentClass('ChatMessage').applyMode({}, config.rollMode ?? 'public'); + + const diceRolls = []; if (game.modules.get('dice-so-nice')?.active) { - const pool = foundry.dice.terms.PoolTerm.fromRolls( - Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll)) - ), - diceRoll = Roll.fromTerms([pool]); - await game.dice3d.showForRoll( - diceRoll, - game.user, - true, - chatMessage.whisper?.length > 0 ? chatMessage.whisper : null, - chatMessage.blind - ); config.mute = true; + const pool = foundry.dice.terms.PoolTerm.fromRolls( + Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll)) + ); + diceRolls.push(Roll.fromTerms([pool])); } + + await triggerChatRollFx(diceRolls, { + whisper: chatMessage.whisper?.length > 0 ? chatMessage.whisper : null, + blind: chatMessage.blind + }); await super.buildPost(roll, config, message); + if (config.source?.message) { chatMessage.update({ 'system.damage': config.damage }); - - if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); } } @@ -319,9 +323,10 @@ export default class DamageRoll extends DHRoll { const newIndex = parsedDiceTerms[dice].results.length; await term.reroll(`/r1=${termResult.result}`); + const diceRolls = []; if (game.modules.get('dice-so-nice')?.active) { const newResult = parsedDiceTerms[dice].results[newIndex]; - const diceSoNiceRoll = { + diceRolls.push({ _evaluated: true, dice: [ new foundry.dice.terms.Die({ @@ -332,11 +337,10 @@ export default class DamageRoll extends DHRoll { }) ], options: { appearance: {} } - }; - - await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); + }); } + await triggerChatRollFx(diceRolls); await parsedRoll.evaluate(); const results = parsedRoll.dice[dice].results.map(result => ({ diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index d6975f71..02c4ab24 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -1,4 +1,5 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; +import { triggerChatRollFx } from '../helpers/utils.mjs'; export default class DHRoll extends Roll { baseTerms = []; @@ -75,9 +76,7 @@ export default class DHRoll extends Roll { } if (config.skips?.createMessage) { - if (game.modules.get('dice-so-nice')?.active) { - await game.dice3d.showForRoll(roll, game.user, true); - } + await triggerChatRollFx([roll]); } else if (!config.source?.message) { config.message = await this.toMessage(roll, config); } @@ -85,6 +84,7 @@ export default class DHRoll extends Roll { static postEvaluate(roll, config = {}) { return { + ...roll.options.roll, total: roll.total, formula: roll.formula, dice: roll.dice.map(d => ({ diff --git a/module/dice/die/dualityDie.mjs b/module/dice/die/dualityDie.mjs index 83229425..cc7ee75e 100644 --- a/module/dice/die/dualityDie.mjs +++ b/module/dice/die/dualityDie.mjs @@ -1,4 +1,4 @@ -import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; +import { updateResourcesForDualityReroll } from '../helpers.mjs'; export default class DualityDie extends foundry.dice.terms.Die { constructor(options) { @@ -12,24 +12,6 @@ export default class DualityDie extends foundry.dice.terms.Die { return roll.withHope ? 1 : roll.withFear ? -1 : 0; } - #updateResources(oldDuality, newDuality, actor) { - const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); - if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return; - - const updates = []; - const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0); - const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0); - const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0); - - if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true }); - if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true }); - if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true }); - - const resourceUpdates = new ResourceUpdateMap(actor); - resourceUpdates.addResources(updates); - resourceUpdates.updateResources(); - } - async reroll(modifier, options) { const oldDuality = this.#getDualityState(options.liveRoll.roll); await super.reroll(modifier, options); @@ -57,7 +39,7 @@ export default class DualityDie extends foundry.dice.terms.Die { if (options.liveRoll.isReaction) return; const newDuality = this.#getDualityState(options.liveRoll.roll); - this.#updateResources(oldDuality, newDuality, options.liveRoll.actor); + updateResourcesForDualityReroll(oldDuality, newDuality, options.liveRoll.actor); } } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index d58811fe..f40e9781 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -1,6 +1,8 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20Roll from './d20Roll.mjs'; import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; +import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; +import { updateResourcesForDualityReroll } from './helpers.mjs'; export default class DualityRoll extends D20Roll { _advantageNumber = 1; @@ -130,13 +132,7 @@ export default class DualityRoll extends D20Roll { } createBaseDice() { - if ( - this.dice[0] instanceof game.system.api.dice.diceTypes.HopeDie && - this.dice[1] instanceof game.system.api.dice.diceTypes.FearDie - ) { - this.terms = [this.terms[0], this.terms[1], this.terms[2]]; - return; - } + this.terms = [this.terms[0], this.terms[1], this.terms[2]]; this.terms[0] = new game.system.api.dice.diceTypes.HopeDie({ faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12 @@ -388,4 +384,40 @@ export default class DualityRoll extends D20Roll { if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id); } } + + async reroll(options) { + const oldDuality = this.withHope ? 1 : this.withFear ? -1 : 0; + const rerolled = DualityRoll.fromData((await super.reroll(options)).toJSON()); + + if (options?.liveRoll) { + if (game.modules.get('dice-so-nice')?.active) { + const diceAppearance = await getDiceSoNicePresets( + rerolled, + rerolled.dHope.denomination, + rerolled.dFear.denomination + ); + rerolled.dHope.options.appearance = diceAppearance.hope.appearance; + rerolled.dFear.options.appearance = diceAppearance.fear.appearance; + if (rerolled.dAdvantage) rerolled.dAdvantage.options.appearance = diceAppearance.advantage.appearance; + if (rerolled.dDisadvantage) + rerolled.dDisadvantage.options.appearance = diceAppearance.disadvantage.appearance; + + await game.dice3d.showForRoll(rerolled, game.user, true); + } else { + foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); + } + + if (this.options.actionType === 'reaction') return; + + const newDuality = rerolled.withHope ? 1 : rerolled.withFear ? -1 : 0; + const actor = await foundry.utils.fromUuid(this.options.source.actor); + updateResourcesForDualityReroll(oldDuality, newDuality, actor); + } + + return rerolled; + } + + fromJSON(json) { + return super.fromJSON(json); + } } diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs new file mode 100644 index 00000000..33519949 --- /dev/null +++ b/module/dice/helpers.mjs @@ -0,0 +1,17 @@ +export function updateResourcesForDualityReroll(oldDuality, newDuality, actor) { + const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return; + + const updates = []; + const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0); + const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0); + const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0); + + if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true }); + if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true }); + if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true }); + + const resourceUpdates = new ResourceUpdateMap(actor); + resourceUpdates.addResources(updates); + resourceUpdates.updateResources(); +} diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 7bc5fa25..8bc95aa0 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -864,3 +864,18 @@ export function camelize(str) { }) .replace(/\s+/g, ''); } + +/** + * Triggers DiceSoNice rolls or dice roll audio for rolls. Not used for duality rolls. + * @param { Roll[] } rolls + * @return { void } + */ +export async function triggerChatRollFx(rolls, options = { whisper: false, blind: false }) { + const { whisper, blind } = options; + if (game.modules.get('dice-so-nice')?.active) { + const rerollPromises = rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true, whisper, blind)); + await Promise.allSettled(rerollPromises); + } else { + foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); + } +} diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index 11d9635e..e8f61318 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -24,8 +24,6 @@ @import './multiclass-choice/sheet.less'; -@import './reroll-dialog/sheet.less'; - @import './tag-team-dialog/initialization.less'; @import './tag-team-dialog/sheet.less'; diff --git a/styles/less/dialog/reroll-dialog/sheet.less b/styles/less/dialog/reroll-dialog/sheet.less deleted file mode 100644 index 71c94d80..00000000 --- a/styles/less/dialog/reroll-dialog/sheet.less +++ /dev/null @@ -1,125 +0,0 @@ -.daggerheart.dialog.dh-style.views.reroll-dialog { - .window-content { - max-width: 648px; - } - - .reroll-outer-container { - h2 { - margin: 0; - } - - .dices-container { - display: flex; - flex-wrap: wrap; - gap: 8px; - } - - .dice-outer-container { - width: 300px; - - legend { - display: flex; - align-items: center; - gap: 4px; - - i { - margin-right: 4px; - } - } - - .dice-container { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; - - .result-container { - position: relative; - aspect-ratio: 1; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.375rem; - opacity: 0.8; - - &.selected { - opacity: 1; - border: 1px solid; - border-radius: 6px; - border-color: light-dark(@dark-blue, @golden); - filter: drop-shadow(0 0 3px @golden); - } - - &:before { - content: ' '; - position: absolute; - width: 100%; - height: 100%; - z-index: -1; - mask: var(--svg-die) no-repeat center; - mask-size: contain; - background: linear-gradient(139.01deg, #efe6d8 3.51%, #372e1f 96.49%); - } - - &.d4:before { - --svg-die: url(../assets/icons/dice/default/d4.svg); - } - &.d6:before { - --svg-die: url(../assets/icons/dice/default/d6.svg); - } - &.d8:before { - --svg-die: url(../assets/icons/dice/default/d8.svg); - } - &.d10:before { - --svg-die: url(../assets/icons/dice/default/d10.svg); - } - &.d12:before { - --svg-die: url('../assets/icons/dice/default/d12.svg'); - } - &.d20:before { - --svg-die: url(../assets/icons/dice/default/d20.svg); - } - - .to-reroll-result { - position: absolute; - bottom: -7px; - gap: 2px; - border: 1px solid; - border-radius: 6px; - background-image: url(../assets/parchments/dh-parchment-dark.png); - display: flex; - align-items: center; - padding: 2px 6px; - - input { - margin: 0; - height: 12px; - line-height: 0px; - position: relative; - top: 1px; - - &:before, - &:after { - line-height: 12px; - font-size: var(--font-size-12); - } - } - - i { - font-size: var(--font-size-10); - } - } - } - } - } - } - - footer { - margin-top: 8px; - display: flex; - justify-content: space-between; - - .controls { - display: flex; - gap: 8px; - } - } -} diff --git a/templates/dialogs/rerollDialog/damage/main.hbs b/templates/dialogs/rerollDialog/damage/main.hbs deleted file mode 100644 index 5b994bf6..00000000 --- a/templates/dialogs/rerollDialog/damage/main.hbs +++ /dev/null @@ -1,35 +0,0 @@ -
      - {{#each damage}} -

      {{localize (concat 'DAGGERHEART.CONFIG.HealingType.' @key '.name')}}

      - {{#each this}} -
      - {{#each this}} -
      - - - - {{this.selectedResults}}/{{this.maxSelected}} Selected - - -
      - {{#each this.results}} -
      - {{this.result}} - {{#if this.active}} - - - - - {{/if}} -
      - {{/each}} -
      -
      - {{/each}} -
      - {{/each}} - {{/each}} -
      \ No newline at end of file diff --git a/templates/dialogs/rerollDialog/footer.hbs b/templates/dialogs/rerollDialog/footer.hbs deleted file mode 100644 index 5d4ae2b2..00000000 --- a/templates/dialogs/rerollDialog/footer.hbs +++ /dev/null @@ -1,4 +0,0 @@ -
      - - -
      \ No newline at end of file diff --git a/templates/dialogs/rerollDialog/main.hbs b/templates/dialogs/rerollDialog/main.hbs deleted file mode 100644 index 6f10ce33..00000000 --- a/templates/dialogs/rerollDialog/main.hbs +++ /dev/null @@ -1,35 +0,0 @@ -
      - {{#each damage}} -

      {{localize (concat 'DAGGERHEART.CONFIG.HealingType.' @key '.name')}}

      - {{#each this}} -
      - {{#each this}} -
      - - - - {{this.selectedResults}}/{{this.results.length}} Selected - - -
      - {{#each this.results}} -
      - {{this.result}} - {{#if this.active}} - - - - - {{/if}} -
      - {{/each}} -
      -
      - {{/each}} -
      - {{/each}} - {{/each}} -
      \ No newline at end of file From 9487b07e434ba1449b7ebab9998fc32313b4985a Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 06:47:06 -0400 Subject: [PATCH 269/304] Fix tier adjustment on actions that use standard attack damage (#1942) --- .gitattributes | 2 + module/data/actor/adversary.mjs | 204 +------------------------ module/data/actor/tierAdjustment.mjs | 218 +++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 202 deletions(-) create mode 100644 .gitattributes create mode 100644 module/data/actor/tierAdjustment.mjs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..56ce8818 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=lf +*.json text eol=lf diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index d69e17ad..d6d0dcdf 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -3,8 +3,7 @@ import { ActionField } from '../fields/actionField.mjs'; import { commonActorRules } from './base.mjs'; import DhCreature from './creature.mjs'; import { bonusField } from '../fields/actorField.mjs'; -import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs'; -import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; +import { getTierAdjustedAdversary } from './tierAdjustment.mjs'; export default class DhpAdversary extends DhCreature { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary']; @@ -206,205 +205,6 @@ export default class DhpAdversary extends DhCreature { /** Returns source data for this actor adjusted to a new tier, which can be used to create a new actor. */ adjustForTier(tier) { const source = this.parent.toObject(true); - - /** @type {(2 | 3 | 4)[]} */ - const tiers = new Array(Math.abs(tier - this.tier)) - .fill(0) - .map((_, idx) => idx + Math.min(tier, this.tier) + 1); - if (tier < this.tier) tiers.reverse(); - const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard]; - const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] })); - - // Apply simple tier changes - const scale = tier > this.tier ? 1 : -1; - for (const entry of tierEntries) { - source.system.difficulty += scale * entry.difficulty; - source.system.damageThresholds.major += scale * entry.majorThreshold; - source.system.damageThresholds.severe += scale * entry.severeThreshold; - source.system.resources.hitPoints.max += scale * entry.hp; - source.system.resources.stress.max += scale * entry.stress; - source.system.attack.roll.bonus += scale * entry.attack; - } - - // Get the mean and standard deviation of expected damage in the previous and new tier - // The data we have is for attack scaling, but we reuse this for action scaling later - const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic; - const damageMeta = { - currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] }, - newDamageRange: { tier, ...expectedDamageData[tier] }, - type: 'attack' - }; - - // Update damage of base attack - try { - this.#adjustActionDamage(source.system.attack, damageMeta); - } catch (err) { - ui.notifications.warn('Failed to convert attack damage of adversary'); - console.error(err); - } - - // Update damage of each item action, making sure to also update the description if possible - const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g; - for (const item of source.items) { - // Replace damage inlines with new formulas - for (const withDescription of [item.system, ...Object.values(item.system.actions)]) { - withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => { - const { value: formula } = parseInlineParams(inner); - if (!formula || !type) return match; - - try { - const adjusted = this.#calculateAdjustedDamage(formula, { ...damageMeta, type: 'action' }); - const newFormula = [ - adjusted.diceQuantity ? `${adjusted.diceQuantity}d${adjusted.faces}` : null, - adjusted.bonus - ] - .filter(p => !!p) - .join('+'); - return match.replace(formula, newFormula); - } catch { - return match; - } - }); - } - - // Update damage in item actions - // Parse damage, and convert all formula matches in the descriptions to the new damage - for (const action of Object.values(item.system.actions)) { - try { - const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' }); - if (!result) continue; - - for (const { previousFormula, formula } of Object.values(result)) { - const oldFormulaRegexp = new RegExp( - previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?') - ); - item.system.description = item.system.description.replace(oldFormulaRegexp, formula); - action.description = action.description.replace(oldFormulaRegexp, formula); - } - } catch (err) { - ui.notifications.warn(`Failed to convert action damage for item ${item.name}`); - console.error(err); - } - } - } - - // Finally set the tier of the source data, now that everything is complete - source.system.tier = tier; - return source; - } - - /** - * Converts a damage object to a new damage range - * @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term - * @throws error if the formula is the wrong type - */ - #calculateAdjustedDamage(formula, { currentDamageRange, newDamageRange, type }) { - const terms = parseTermsFromSimpleFormula(formula); - const flatTerms = terms.filter(t => t.diceQuantity === 0); - const diceTerms = terms.filter(t => t.diceQuantity > 0); - if (flatTerms.length > 1 || diceTerms.length > 1) { - throw new Error('invalid formula for conversion'); - } - const value = { - ...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }), - bonus: flatTerms[0]?.bonus ?? 0 - }; - const previousExpected = calculateExpectedValue(value); - if (previousExpected === 0) return value; // nothing to do - - const dieSizes = [4, 6, 8, 10, 12, 20]; - const steps = newDamageRange.tier - currentDamageRange.tier; - const increasing = steps > 0; - const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation; - const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation); - - // If this was just a flat number, convert to the expected damage and exit - if (value.diceQuantity === 0) { - value.bonus = Math.round(expected); - return value; - } - - const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1; - const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 }); - - // Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die - const baseOverages = Math.floor(value.bonus / getExpectedDie()); - - // Prestep. Change number of dice for attacks, bump up/down for actions - // We never bump up to d20, though we might bump down from it - if (type === 'attack') { - const minimum = increasing ? value.diceQuantity : 0; - value.diceQuantity = Math.max(minimum, newDamageRange.tier); - } else { - const currentIdx = dieSizes.indexOf(value.faces); - value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)]; - } - - value.bonus = Math.round(expected - getBaseAverage()); - - // Attempt to handle negative values. - // If we can do it with only step downs, do so. Otherwise remove tier dice, and try again - if (value.bonus < 0) { - let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity); - const currentIdx = dieSizes.indexOf(value.faces); - - // If step downs alone don't suffice, change the flat modifier, then calculate steps required again - // If this isn't sufficient, the result will be slightly off. This is unlikely to happen - if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) { - value.diceQuantity -= increasing ? 1 : Math.abs(steps); - value.bonus = Math.round(expected - getBaseAverage()); - if (value.bonus >= 0) return value; // complete - } - - stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity); - value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)]; - value.bonus = Math.max(0, Math.round(expected - getBaseAverage())); - } - - // If value is really high, we add a number of dice based on the number of overages - // This attempts to preserve a similar amount of variance when increasing an action - const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages; - if (type !== 'attack' && increasing && overagesToRemove > 0) { - value.diceQuantity += overagesToRemove; - value.bonus = Math.round(expected - getBaseAverage()); - } - - return value; - } - - /** - * Updates damage to reflect a specific value. - * @throws if damage structure is invalid for conversion - * @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage - */ - #adjustActionDamage(action, damageMeta) { - if (!action.damage?.parts.hitPoints) return null; - - const result = {}; - for (const property of ['value', 'valueAlt']) { - const data = action.damage.parts.hitPoints[property]; - const previousFormula = data.custom.enabled - ? data.custom.formula - : [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0] - .filter(p => !!p) - .join('+'); - const value = this.#calculateAdjustedDamage(previousFormula, damageMeta); - const formula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus] - .filter(p => !!p) - .join('+'); - if (value.diceQuantity) { - data.custom.enabled = false; - data.bonus = value.bonus; - data.dice = `d${value.faces}`; - data.flatMultiplier = value.diceQuantity; - } else if (!value.diceQuantity) { - data.custom.enabled = true; - data.custom.formula = formula; - } - - result[property] = { previousFormula, formula, value }; - } - - return result; + return getTierAdjustedAdversary(source, tier); } } diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs new file mode 100644 index 00000000..785eec2b --- /dev/null +++ b/module/data/actor/tierAdjustment.mjs @@ -0,0 +1,218 @@ +import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs'; +import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; + +export function getTierAdjustedAdversary(source, tier) { + const currentTier = source.tier ?? 1; + + /** @type {(2 | 3 | 4)[]} */ + const tiers = new Array(Math.abs(tier - currentTier)) + .fill(0) + .map((_, idx) => idx + Math.min(tier, currentTier) + 1); + if (tier < currentTier) tiers.reverse(); + const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard]; + const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] })); + + // Apply simple tier changes + const scale = tier > currentTier ? 1 : -1; + for (const entry of tierEntries) { + source.system.difficulty += scale * entry.difficulty; + source.system.damageThresholds.major += scale * entry.majorThreshold; + source.system.damageThresholds.severe += scale * entry.severeThreshold; + source.system.resources.hitPoints.max += scale * entry.hp; + source.system.resources.stress.max += scale * entry.stress; + source.system.attack.roll.bonus += scale * entry.attack; + } + + // Get the mean and standard deviation of expected damage in the previous and new tier + // The data we have is for attack scaling, but we reuse this for action scaling later + const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic; + const damageMeta = { + currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] }, + newDamageRange: { tier, ...expectedDamageData[tier] } + }; + + // Store initial attack damage for abilities that have you deal a "standard attack" + const initialAttack = { + type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(), + value: getDamagePartsFormula(source.system.attack.damage?.parts.hitPoints?.value) + }; + + // Update damage of base attack. + try { + const damage = source.system.attack.damage; + if (!damage?.parts.hitPoints) throw new Error('Unexpected missing attack in adversary'); + + for (const property of ['value', 'valueAlt']) { + const data = damage.parts.hitPoints[property]; + const previousFormula = getDamagePartsFormula(data); + const { value, formula } = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); + applyAdjustedDamage(data, value, formula); + } + } catch (err) { + ui.notifications.warn('Failed to convert attack damage of adversary'); + console.error(err); + } + + // Update damage of each item action, making sure to also update the description if possible + const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g; + for (const item of source.items) { + // Replace damage inlines with new formulas. Keep a record for a specific check later + const descriptionFormulas = []; + for (const withDescription of [item.system, ...Object.values(item.system.actions)]) { + withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => { + const { value: formula } = parseInlineParams(inner); + if (!formula || !type) return match; + + try { + const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula; + descriptionFormulas.push(formula); + return match.replace(formula, newFormula); + } catch { + return match; + } + }); + } + + // Update damage in item actions and convert all formula matches in the descriptions to the new damage + for (const action of Object.values(item.system.actions)) { + if (!action.damage?.parts.hitPoints) continue; + try { + // Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead + const result = []; + for (const property of ['value', 'valueAlt']) { + const { [property]: data, type: damageType } = action.damage.parts.hitPoints; + const previousFormula = getDamagePartsFormula(data); + const isActuallyAttack = + previousFormula === initialAttack.value && + foundry.utils.equals(damageType.toSorted(), initialAttack.type) && + !descriptionFormulas.includes(previousFormula); + const type = isActuallyAttack ? 'attack' : 'action'; + const { value, formula } = calculateAdjustedDamage(previousFormula, type, damageMeta); + applyAdjustedDamage(data, value, formula); + result.push({ previousFormula, formula }); + } + + // Override text in the description with those values + for (const { previousFormula, formula } of Object.values(result)) { + const oldFormulaRegexp = new RegExp( + previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?') + ); + item.system.description = item.system.description.replace(oldFormulaRegexp, formula); + action.description = action.description.replace(oldFormulaRegexp, formula); + } + } catch (err) { + ui.notifications.warn(`Failed to convert action damage for item ${item.name}`); + console.error(err); + } + } + } + + // Finally set the tier of the source data, now that everything is complete + source.system.tier = tier; + return source; +} + +/** + * Converts a damage object to a new damage range + * @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term + * @throws error if the formula is the wrong type + */ +function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageRange }) { + const terms = parseTermsFromSimpleFormula(formula); + const flatTerms = terms.filter(t => t.diceQuantity === 0); + const diceTerms = terms.filter(t => t.diceQuantity > 0); + if (flatTerms.length > 1 || diceTerms.length > 1) { + throw new Error('invalid formula for conversion'); + } + const value = { + ...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }), + bonus: flatTerms[0]?.bonus ?? 0 + }; + const previousExpected = calculateExpectedValue(value); + if (previousExpected === 0) return value; // nothing to do + + const dieSizes = [4, 6, 8, 10, 12, 20]; + const steps = newDamageRange.tier - currentDamageRange.tier; + const increasing = steps > 0; + const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation; + const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation); + + // If this was just a flat number, convert to the expected damage and exit + if (value.diceQuantity === 0) { + value.bonus = Math.round(expected); + return value; + } + + const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1; + const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 }); + + // Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die + const baseOverages = Math.floor(value.bonus / getExpectedDie()); + + // Prestep. Change number of dice for attacks, bump up/down for actions + // We never bump up to d20, though we might bump down from it + if (type === 'attack') { + const minimum = increasing ? value.diceQuantity : 0; + value.diceQuantity = Math.max(minimum, newDamageRange.tier); + } else { + const currentIdx = dieSizes.indexOf(value.faces); + value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)]; + } + + value.bonus = Math.round(expected - getBaseAverage()); + + // Attempt to handle negative values. + // If we can do it with only step downs, do so. Otherwise remove tier dice, and try again + if (value.bonus < 0) { + let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity); + const currentIdx = dieSizes.indexOf(value.faces); + + // If step downs alone don't suffice, change the flat modifier, then calculate steps required again + // If this isn't sufficient, the result will be slightly off. This is unlikely to happen + if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) { + value.diceQuantity -= increasing ? 1 : Math.abs(steps); + value.bonus = Math.round(expected - getBaseAverage()); + if (value.bonus >= 0) return value; // complete + } + + stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity); + value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)]; + value.bonus = Math.max(0, Math.round(expected - getBaseAverage())); + } + + // If value is really high, we add a number of dice based on the number of overages + // This attempts to preserve a similar amount of variance when increasing an action + const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages; + if (type !== 'attack' && increasing && overagesToRemove > 0) { + value.diceQuantity += overagesToRemove; + value.bonus = Math.round(expected - getBaseAverage()); + } + + const newFormula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus] + .filter(p => !!p) + .join('+'); + return { value, formula: newFormula }; +} + +function getDamagePartsFormula(data) { + return data.custom.enabled + ? data.custom.formula + : [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0].filter(p => !!p).join('+'); +} + +/** + * Updates damage to reflect a specific value. + * @throws if damage structure is invalid for conversion + * @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage + */ +function applyAdjustedDamage(diceData, value, formula) { + if (value.diceQuantity) { + diceData.custom.enabled = false; + diceData.bonus = value.bonus; + diceData.dice = `d${value.faces}`; + diceData.flatMultiplier = value.diceQuantity; + } else if (!value.diceQuantity) { + diceData.custom.enabled = true; + diceData.custom.formula = formula; + } +} From a209b035c8d863ed8788cb82278e096248d12824 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 06:48:20 -0400 Subject: [PATCH 270/304] Make prosemirror button nicer (#1946) --- styles/less/global/prose-mirror.less | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index 8412235d..e4b1249f 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -40,6 +40,11 @@ ul { list-style: disc; } + } + // Fixes centering and makes it not render over scrollbar + &:hover button.toggle:enabled { + display: flex; + right: 12px; } } } From 251d7e4e13cd172245f577b0adac9de633dd8013 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 06:49:06 -0400 Subject: [PATCH 271/304] Swap order of thresholds and resources in actor editor (#1943) --- .../sheets-settings/adversary-settings/sheet.less | 2 +- .../sheets-settings/adversary-settings/details.hbs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/styles/less/sheets-settings/adversary-settings/sheet.less b/styles/less/sheets-settings/adversary-settings/sheet.less index b4b0683b..e6eb8d0b 100644 --- a/styles/less/sheets-settings/adversary-settings/sheet.less +++ b/styles/less/sheets-settings/adversary-settings/sheet.less @@ -7,7 +7,7 @@ &.attack.active { display: flex; flex-direction: column; - gap: 16px; + gap: 12px; } .fieldsets-section { diff --git a/templates/sheets-settings/adversary-settings/details.hbs b/templates/sheets-settings/adversary-settings/details.hbs index dc2fd386..3160fbb9 100644 --- a/templates/sheets-settings/adversary-settings/details.hbs +++ b/templates/sheets-settings/adversary-settings/details.hbs @@ -18,6 +18,12 @@ {{formField systemFields.motivesAndTactics value=document._source.system.motivesAndTactics label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.motivesAndTactics.label")}}
      +
      + {{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}} + {{formGroup systemFields.damageThresholds.fields.major value=document._source.system.damageThresholds.major label=(localize "DAGGERHEART.GENERAL.DamageThresholds.majorThreshold")}} + {{formGroup systemFields.damageThresholds.fields.severe value=document._source.system.damageThresholds.severe label=(localize "DAGGERHEART.GENERAL.DamageThresholds.severeThreshold")}} +
      +
      {{localize "DAGGERHEART.GENERAL.Resource.plural"}} @@ -26,10 +32,4 @@ {{/each}}
      - -
      - {{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}} - {{formGroup systemFields.damageThresholds.fields.major value=document._source.system.damageThresholds.major label=(localize "DAGGERHEART.GENERAL.DamageThresholds.majorThreshold")}} - {{formGroup systemFields.damageThresholds.fields.severe value=document._source.system.damageThresholds.severe label=(localize "DAGGERHEART.GENERAL.DamageThresholds.severeThreshold")}} -
      From 493998cc957a1c5f0a5153c28138aa8ff4db1a22 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 06:51:39 -0400 Subject: [PATCH 272/304] Preload class and subclass features for description (#1940) Co-authored-by: WBHarry --- module/data/item/class.mjs | 13 ++++++++---- module/data/item/subclass.mjs | 8 +++++-- module/helpers/utils.mjs | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 7014e011..470a1e3c 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import ItemLinkFields from '../fields/itemLinkFields.mjs'; -import { addLinkedItemsDiff, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs'; +import { addLinkedItemsDiff, fromUuids, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs'; export default class DHClass extends BaseDataItem { /** @inheritDoc */ @@ -73,15 +73,16 @@ export default class DHClass extends BaseDataItem { const uuids = [this.parent.uuid, this.parent._stats?.compendiumSource].filter(u => !!u); const subclasses = game.items.filter(x => x.type === 'subclass' && uuids.includes(x.system.linkedClass)); for (const pack of game.packs) { + const packIds = []; const indexes = await pack.getIndex({ fields: ['system.linkedClass'] }); for (const index of indexes) { if (index.type !== 'subclass') continue; if (!uuids.includes(index.system?.linkedClass)) continue; if (subclasses.find(x => x.uuid === index.uuid)) continue; - - const subclass = await foundry.utils.fromUuid(index.uuid); - subclasses.push(subclass); + packIds.push(index._id); } + + if (packIds.length > 0) subclasses.push(...(await pack.getDocuments({ _id__in: packIds }))); } return subclasses; @@ -216,6 +217,10 @@ export default class DHClass extends BaseDataItem { classItems.push(contentLink.outerHTML); } + // Preload all class features for acquisition from the cache + // todo: make feature acquisition async and replace feature helpers for methods + await fromUuids(this._source.features.map(f => f.item)); + const hopeFeatures = await getFeaturesHTMLData(this.hopeFeatures); const classFeatures = await getFeaturesHTMLData(this.classFeatures); diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index ecf72de3..55b078c2 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -1,5 +1,4 @@ -import { getFeaturesHTMLData } from '../../helpers/utils.mjs'; -import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs'; import ItemLinkFields from '../fields/itemLinkFields.mjs'; import BaseDataItem from './base.mjs'; @@ -91,6 +90,11 @@ export default class DHSubclass extends BaseDataItem { const spellcastTrait = this.spellcastingTrait ? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label) : null; + + // Preload all class features for acquisition from the cache + // todo: make feature acquisition async and replace feature helpers for methods + await fromUuids(this._source.features.map(f => f.item)); + const foundationFeatures = await getFeaturesHTMLData(this.foundationFeatures); const specializationFeatures = await getFeaturesHTMLData(this.specializationFeatures); const masteryFeatures = await getFeaturesHTMLData(this.masteryFeatures); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 8bc95aa0..2f20175b 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -865,6 +865,45 @@ export function camelize(str) { .replace(/\s+/g, ''); } +/** Bulk load a list of documents using uuids. Returns the documents in the same order */ +export async function fromUuids(uuids) { + // Set up base entries. Each step works on a sublist of these objects + const entries = uuids.map(uuid => ({ + uuid, + parsed: foundry.utils.parseUuid(uuid), + value: foundry.utils.fromUuidSync(uuid) + })); + + // Handle missing uuids for embedded documents first + // A value may be index data, so we check if its a document + const packEmbeddedEntries = entries.filter( + e => + !(e.value instanceof Document) && + e.parsed.collection instanceof foundry.documents.collections.CompendiumCollection && + e.parsed.embedded.length > 0 + ); + await Promise.all( + packEmbeddedEntries.map(async e => { + e.value = await fromUuid(e.uuid); + return true; + }) + ); + + // Handle missing top level pack stuff, by batching per pack + const missingTopLevel = entries.filter(e => !(e.value instanceof Document) && e.value?.pack); + for (const packGroup of Object.values(Object.groupBy(missingTopLevel, e => e.value.pack))) { + const pack = game.packs.get(packGroup[0].value.pack); + if (!pack) continue; + + const ids = packGroup.map(p => p.parsed.id); + const documents = await pack.getDocuments({ _id__in: ids }); + for (const p of packGroup) { + p.value = documents.find(d => d.id === p.parsed.id) ?? p.value; + } + } + + return entries.map(e => e.value); +} /** * Triggers DiceSoNice rolls or dice roll audio for rolls. Not used for duality rolls. * @param { Roll[] } rolls From 2bc1c04c932d91f9d66bc65321813a3756270c23 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 30 May 2026 12:56:42 +0200 Subject: [PATCH 273/304] Fixed an issue where hope/fear dice size could no longer be changed in the roll dialog --- module/dice/dualityRoll.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index f40e9781..1d2d556a 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -135,11 +135,11 @@ export default class DualityRoll extends D20Roll { this.terms = [this.terms[0], this.terms[1], this.terms[2]]; this.terms[0] = new game.system.api.dice.diceTypes.HopeDie({ - faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12 + faces: this.terms[0]?.faces ?? this.data.rules.dualityRoll?.defaultHopeDice ?? 12 }); this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' }); this.terms[2] = new game.system.api.dice.diceTypes.FearDie({ - faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12 + faces: this.terms[2]?.faces ?? this.data.rules.dualityRoll?.defaultFearDice ?? 12 }); } From 61db7ca37151fb95feec4df406c19a2b8f2aedf2 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 19:00:12 -0400 Subject: [PATCH 274/304] Fix tag team roll results where one of them has stress (#1948) --- .../applications/dialogs/groupRollDialog.mjs | 14 +- module/applications/dialogs/tagTeamDialog.mjs | 173 +++++++++--------- 2 files changed, 95 insertions(+), 92 deletions(-) diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index 52baf537..dd504b4b 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -106,7 +106,12 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat const context = await super._prepareContext(_options); context.isGM = game.user.isGM; - context.isEditable = this.getIsEditable(); + context.isEditable = + game.user.isGM || + this.party.system.partyMembers.some(actor => { + const selected = Boolean(this.party.system.groupRoll.participants[actor.id]); + return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); + }); context.fields = this.party.system.schema.fields.groupRoll.fields; context.data = this.party.system.groupRoll; context.traitOptions = CONFIG.DH.ACTOR.abilities; @@ -265,13 +270,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat ]; } - getIsEditable() { - return this.party.system.partyMembers.some(actor => { - const selected = Boolean(this.party.system.groupRoll.participants[actor.id]); - return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); - }); - } - groupRollRefresh = ({ refreshType, action, parts }) => { if (refreshType !== RefreshType.GroupRoll) return; diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 4e63d93b..3dc6b0fc 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -116,7 +116,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.isEditable = this.getIsEditable(); + context.isEditable = + game.user.isGM || + this.party.system.partyMembers.some(actor => { + const selected = Boolean(this.party.system.tagTeam.members[actor.id]); + return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); + }); context.fields = this.party.system.schema.fields.tagTeam.fields; context.data = this.party.system.tagTeam; context.rollTypes = CONFIG.DH.GENERAL.tagTeamRollTypes; @@ -179,57 +184,56 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } if (Object.keys(this.party.system.tagTeam.members).includes(partId)) { - const data = this.party.system.tagTeam.members[partId]; - const actor = game.actors.get(partId); - - const rollOptions = []; - const damageRollOptions = []; - for (const item of actor.items) { - if (item.system.metadata.hasActions) { - const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])]; - for (const action of actions) { - if (action.hasRoll) { - const actionItem = { - value: action.uuid, - label: action.name, - group: item.name, - baseAction: action.baseAction - }; - - if (action.hasDamage) damageRollOptions.push(actionItem); - else rollOptions.push(actionItem); - } - } - } - } - - const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); - const critSelected = !selectedRoll - ? undefined - : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false); - - const damage = data.rollData?.options?.damage; - partContext.hasDamage |= Boolean(damage); - const critHitPointsDamage = await this.getCriticalDamage(damage); - - partContext.members[partId] = { - ...data, - roll: data.roll, - isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER), - key: partId, - readyToRoll: Boolean(data.rollChoice), - hasRolled: Boolean(data.rollData), - rollOptions, - damageRollOptions, - damage: damage, - critDamage: critHitPointsDamage, - useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical) - }; + const data = await this.#prepareMemberContext(partId); + partContext.hasDamage |= Boolean(data?.damage); + partContext.members[partId] = data; } return partContext; } + async #prepareMemberContext(partId) { + const data = this.party.system.tagTeam.members[partId] ?? {}; + const actor = game.actors.get(partId); + if (!actor) console.error(`Failed to get actor ${partId}`); + + const rollOptions = []; + const damageRollOptions = []; + for (const item of actor?.items ?? []) { + if (!item.system.metadata.hasActions) continue; + const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])]; + for (const action of actions) { + if (action.hasRoll) { + const collection = action.hasDamage ? damageRollOptions : rollOptions; + collection.push({ + value: action.uuid, + label: action.name, + group: item.name, + baseAction: action.baseAction + }); + } + } + } + + const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); + const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false); + const damage = data.rollData?.options?.damage; + + return { + ...data, + roll: data.roll, + isEditable: actor?.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER), + key: partId, + readyToRoll: Boolean(data.rollChoice), + hasRolled: Boolean(data.rollData), + rollOptions, + damageRollOptions, + damage: damage, + critDamage: await this.getCriticalDamage(damage), + useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical) + }; + } + getUpdatingParts(target) { const { initialization, rollSelection, result } = this.constructor.PARTS; const isInitialization = this.tabGroups.application === initialization.id; @@ -273,13 +277,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ); } - getIsEditable() { - return this.party.system.partyMembers.some(actor => { - const selected = Boolean(this.party.system.tagTeam.members[actor.id]); - return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER); - }); - } - tagTeamRefresh = ({ refreshType, action, parts }) => { if (refreshType !== RefreshType.TagTeamRoll) return; @@ -649,42 +646,50 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } async getJoinedRoll({ overrideIsCritical, displayVersion } = {}) { - const memberValues = Object.values(this.party.system.tagTeam.members); - const selectedRoll = memberValues.find(x => x.selected); - let baseMainRoll = selectedRoll ?? memberValues[0]; - let baseSecondaryRoll = selectedRoll - ? memberValues.find(x => !x.selected) - : memberValues.length > 1 - ? memberValues[1] - : null; + try { + const memberValues = Object.values(this.party.system.tagTeam.members); + const selectedRoll = memberValues.find(x => x.selected); + const baseMainRoll = selectedRoll ?? memberValues[0]; + const baseSecondaryRoll = selectedRoll + ? memberValues.find(x => !x.selected) + : memberValues.length > 1 + ? memberValues[1] + : null; - if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null; + if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null; - const mainRoll = new MemberData(baseMainRoll.toObject()); - const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData; - const systemData = mainRoll.rollData.options; - const isCritical = overrideIsCritical ?? systemData.roll.isCritical; - if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage); + const mainRoll = new MemberData(baseMainRoll.toObject()); + const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData; + const systemData = mainRoll.rollData.options; + const isCritical = overrideIsCritical ?? systemData.roll.isCritical; + if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage); - if (secondaryRollData?.options.hasDamage) { - const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical) - ? await this.getCriticalDamage(secondaryRollData.options.damage) - : secondaryRollData.options.damage; - if (systemData.damage) { - for (const key in secondaryDamage) { - const damage = secondaryDamage[key]; - systemData.damage[key].formula = [systemData.damage[key].formula, damage.formula] - .filter(x => x) - .join(' + '); - systemData.damage[key].total += damage.total; - systemData.damage[key].parts.push(...damage.parts); + if (secondaryRollData?.options.hasDamage) { + const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical) + ? await this.getCriticalDamage(secondaryRollData.options.damage) + : secondaryRollData.options.damage; + if (systemData.damage) { + for (const [key, damage] of Object.entries(secondaryDamage ?? {})) { + if (key in systemData.damage) { + systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula] + .filter(x => x) + .join(' + '); + systemData.damage[key].total += damage.total; + systemData.damage[key].parts.push(...damage.parts); + } else { + systemData.damage[key] = damage; + } + } + } else { + systemData.damage = secondaryDamage; } - } else { - systemData.damage = secondaryDamage; } - } - return mainRoll; + return mainRoll; + } catch (err) { + console.error(err); + return null; + } } static async #onCancelRoll(_event, _button, options = { confirm: true }) { From d3141059acfef63db29b7f22060977a5f362a551 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 30 May 2026 19:02:51 -0400 Subject: [PATCH 275/304] Create index files for actor sheet styles (#1945) --- .../adversary/{actions.less => features.less} | 0 .../less/sheets/actors/adversary/index.less | 5 +++ .../less/sheets/actors/character/index.less | 8 +++++ .../less/sheets/actors/companion/index.less | 4 +++ .../{actions.less => features.less} | 0 .../less/sheets/actors/environment/index.less | 4 +++ styles/less/sheets/actors/party/index.less | 4 +++ styles/less/sheets/index.less | 34 +++---------------- 8 files changed, 30 insertions(+), 29 deletions(-) rename styles/less/sheets/actors/adversary/{actions.less => features.less} (100%) create mode 100644 styles/less/sheets/actors/adversary/index.less create mode 100644 styles/less/sheets/actors/character/index.less create mode 100644 styles/less/sheets/actors/companion/index.less rename styles/less/sheets/actors/environment/{actions.less => features.less} (100%) create mode 100644 styles/less/sheets/actors/environment/index.less create mode 100644 styles/less/sheets/actors/party/index.less diff --git a/styles/less/sheets/actors/adversary/actions.less b/styles/less/sheets/actors/adversary/features.less similarity index 100% rename from styles/less/sheets/actors/adversary/actions.less rename to styles/less/sheets/actors/adversary/features.less diff --git a/styles/less/sheets/actors/adversary/index.less b/styles/less/sheets/actors/adversary/index.less new file mode 100644 index 00000000..a1ab41c7 --- /dev/null +++ b/styles/less/sheets/actors/adversary/index.less @@ -0,0 +1,5 @@ +@import './features.less'; +@import './header.less'; +@import './sheet.less'; +@import './sidebar.less'; +@import './effects.less'; diff --git a/styles/less/sheets/actors/character/index.less b/styles/less/sheets/actors/character/index.less new file mode 100644 index 00000000..edefe0a1 --- /dev/null +++ b/styles/less/sheets/actors/character/index.less @@ -0,0 +1,8 @@ +@import './biography.less'; +@import './effects.less'; +@import './features.less'; +@import './header.less'; +@import './inventory.less'; +@import './loadout.less'; +@import './sheet.less'; +@import './sidebar.less'; diff --git a/styles/less/sheets/actors/companion/index.less b/styles/less/sheets/actors/companion/index.less new file mode 100644 index 00000000..c4931814 --- /dev/null +++ b/styles/less/sheets/actors/companion/index.less @@ -0,0 +1,4 @@ +@import './details.less'; +@import './header.less'; +@import './sheet.less'; +@import './effects.less'; diff --git a/styles/less/sheets/actors/environment/actions.less b/styles/less/sheets/actors/environment/features.less similarity index 100% rename from styles/less/sheets/actors/environment/actions.less rename to styles/less/sheets/actors/environment/features.less diff --git a/styles/less/sheets/actors/environment/index.less b/styles/less/sheets/actors/environment/index.less new file mode 100644 index 00000000..211c8e60 --- /dev/null +++ b/styles/less/sheets/actors/environment/index.less @@ -0,0 +1,4 @@ +@import './features.less'; +@import './header.less'; +@import './potentialAdversaries.less'; +@import './sheet.less'; diff --git a/styles/less/sheets/actors/party/index.less b/styles/less/sheets/actors/party/index.less new file mode 100644 index 00000000..56f7a457 --- /dev/null +++ b/styles/less/sheets/actors/party/index.less @@ -0,0 +1,4 @@ +@import './header.less'; +@import './party-members.less'; +@import './sheet.less'; +@import './inventory.less'; diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index 7d595614..ca1bc840 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -2,35 +2,11 @@ @import './actors/actor-sheet-shared.less'; -@import './actors/adversary/actions.less'; -@import './actors/adversary/header.less'; -@import './actors/adversary/sheet.less'; -@import './actors/adversary/sidebar.less'; -@import './actors/adversary/effects.less'; - -@import './actors/character/biography.less'; -@import './actors/character/effects.less'; -@import './actors/character/features.less'; -@import './actors/character/header.less'; -@import './actors/character/inventory.less'; -@import './actors/character/loadout.less'; -@import './actors/character/sheet.less'; -@import './actors/character/sidebar.less'; - -@import './actors/companion/details.less'; -@import './actors/companion/header.less'; -@import './actors/companion/sheet.less'; -@import './actors/companion/effects.less'; - -@import './actors/environment/actions.less'; -@import './actors/environment/header.less'; -@import './actors/environment/potentialAdversaries.less'; -@import './actors/environment/sheet.less'; - -@import './actors/party/header.less'; -@import './actors/party/party-members.less'; -@import './actors/party/sheet.less'; -@import './actors/party/inventory.less'; +@import './actors/adversary/index.less'; +@import './actors/character/index.less'; +@import './actors/companion/index.less'; +@import './actors/environment/index.less'; +@import './actors/party/index.less'; @import './items/beastform.less'; @import './items/class.less'; From c23ac61ee53994b268a48db1c9172bf821684e75 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 31 May 2026 03:05:13 +0200 Subject: [PATCH 276/304] Corrected the data path for showing the difficulty marker in roll chat messages (#1950) --- templates/ui/chat/parts/roll-part.hbs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index 14e3eaa6..cfee735f 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -12,13 +12,9 @@ {{/if}}
      - {{#if roll.difficulty}} - - {{!-- {{#if canViewSecret}} --}} - difficulty {{roll.difficulty}} - {{!-- {{else}} - {{localize (ifThen roll.success "DAGGERHEART.GENERAL.success" "DAGGERHEART.GENERAL.failure")}} - {{/if}} --}} + {{#if roll.options.roll.difficulty}} + + {{localize "DAGGERHEART.GENERAL.difficulty"}} {{roll.options.roll.difficulty}} {{/if}}
      From 53f15a7fdec1edb6de4a54c29029c33e0890e100 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 31 May 2026 03:11:43 +0200 Subject: [PATCH 277/304] [Feature] NPC Actors (#1949) --- assets/icons/documents/actors/drama-masks.svg | 1 + daggerheart.mjs | 5 + lang/en.json | 6 + .../applications/sheets-configs/_module.mjs | 1 + .../sheets-configs/npc-settings.mjs | 85 +++++++++++ module/applications/sheets/actors/_module.mjs | 1 + module/applications/sheets/actors/npc.mjs | 136 ++++++++++++++++++ module/data/actor/_module.mjs | 4 +- module/data/actor/npc.mjs | 43 ++++++ module/documents/actor.mjs | 8 ++ styles/less/global/tab-navigation.less | 7 +- .../less/sheets/actors/adversary/header.less | 8 +- .../less/sheets/actors/companion/header.less | 4 +- .../sheets/actors/environment/header.less | 4 +- styles/less/sheets/actors/npc/features.less | 18 +++ styles/less/sheets/actors/npc/header.less | 83 +++++++++++ styles/less/sheets/actors/npc/index.less | 3 + styles/less/sheets/actors/npc/sheet.less | 10 ++ styles/less/sheets/index.less | 1 + system.json | 5 +- .../sheets-settings/npc-settings/details.hbs | 13 ++ .../sheets-settings/npc-settings/features.hbs | 29 ++++ .../sheets-settings/npc-settings/header.hbs | 3 + templates/sheets/actors/adversary/header.hbs | 5 +- templates/sheets/actors/companion/header.hbs | 9 +- .../sheets/actors/environment/header.hbs | 9 +- templates/sheets/actors/npc/features.hbs | 14 ++ templates/sheets/actors/npc/header.hbs | 40 ++++++ templates/sheets/actors/npc/navigation.hbs | 7 + templates/sheets/actors/npc/notes.hbs | 11 ++ .../sheets/global/tabs/tab-navigation.hbs | 2 +- .../ui/sidebar/actor-document-partial.hbs | 2 + 32 files changed, 548 insertions(+), 29 deletions(-) create mode 100644 assets/icons/documents/actors/drama-masks.svg create mode 100644 module/applications/sheets-configs/npc-settings.mjs create mode 100644 module/applications/sheets/actors/npc.mjs create mode 100644 module/data/actor/npc.mjs create mode 100644 styles/less/sheets/actors/npc/features.less create mode 100644 styles/less/sheets/actors/npc/header.less create mode 100644 styles/less/sheets/actors/npc/index.less create mode 100644 styles/less/sheets/actors/npc/sheet.less create mode 100644 templates/sheets-settings/npc-settings/details.hbs create mode 100644 templates/sheets-settings/npc-settings/features.hbs create mode 100644 templates/sheets-settings/npc-settings/header.hbs create mode 100644 templates/sheets/actors/npc/features.hbs create mode 100644 templates/sheets/actors/npc/header.hbs create mode 100644 templates/sheets/actors/npc/navigation.hbs create mode 100644 templates/sheets/actors/npc/notes.hbs diff --git a/assets/icons/documents/actors/drama-masks.svg b/assets/icons/documents/actors/drama-masks.svg new file mode 100644 index 00000000..84307da0 --- /dev/null +++ b/assets/icons/documents/actors/drama-masks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/daggerheart.mjs b/daggerheart.mjs index 363430be..23977628 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -196,6 +196,11 @@ Hooks.once('init', () => { makeDefault: true, label: sheetLabel('TYPES.Actor.environment') }); + Actors.registerSheet(SYSTEM.id, applications.sheets.actors.NPC, { + types: ['npc'], + makeDefault: true, + label: sheetLabel('TYPES.Actor.npc') + }); Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, { types: ['party'], makeDefault: true, diff --git a/lang/en.json b/lang/en.json index f1841e09..9ce515d9 100755 --- a/lang/en.json +++ b/lang/en.json @@ -23,6 +23,7 @@ "companion": "Companion", "adversary": "Adversary", "environment": "Environment", + "npc": "NPC", "party": "Party" } }, @@ -333,6 +334,11 @@ }, "newAdversary": "New Adversary" }, + "NPC": { + "FIELDS": { + "motives": { "label": "Motives" } + } + }, "Party": { "Subtitle": { "character": "{community} {ancestry} | {subclass} {class}", diff --git a/module/applications/sheets-configs/_module.mjs b/module/applications/sheets-configs/_module.mjs index 4b83a042..9528a424 100644 --- a/module/applications/sheets-configs/_module.mjs +++ b/module/applications/sheets-configs/_module.mjs @@ -2,6 +2,7 @@ export { default as ActionConfig } from './action-config.mjs'; export { default as ActionSettingsConfig } from './action-settings-config.mjs'; export { default as CharacterSettings } from './character-settings.mjs'; export { default as AdversarySettings } from './adversary-settings.mjs'; +export { default as NPCSettings } from './npc-settings.mjs'; export { default as CompanionSettings } from './companion-settings.mjs'; export { default as SettingFeatureConfig } from './setting-feature-config.mjs'; export { default as EnvironmentSettings } from './environment-settings.mjs'; diff --git a/module/applications/sheets-configs/npc-settings.mjs b/module/applications/sheets-configs/npc-settings.mjs new file mode 100644 index 00000000..c187877c --- /dev/null +++ b/module/applications/sheets-configs/npc-settings.mjs @@ -0,0 +1,85 @@ +import DHBaseActorSettings from '../sheets/api/actor-setting.mjs'; + +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + +export default class DHNPCSettings extends DHBaseActorSettings { + /**@inheritdoc */ + static DEFAULT_OPTIONS = { + classes: ['npc-settings'], + position: { width: 455, height: 'auto' }, + actions: {}, + dragDrop: [ + { dragSelector: null, dropSelector: '.tab.features' }, + { dragSelector: '.feature-item', dropSelector: null } + ] + }; + + /**@override */ + static PARTS = { + header: { + id: 'header', + template: 'systems/daggerheart/templates/sheets-settings/npc-settings/header.hbs' + }, + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + details: { + id: 'details', + template: 'systems/daggerheart/templates/sheets-settings/npc-settings/details.hbs' + }, + features: { + id: 'features', + template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs' + } + }; + + /** @override */ + static TABS = { + primary: { + tabs: [{ id: 'details' }, { id: 'features' }], + initial: 'details', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' + } + }; + + async _prepareContext(options) { + const context = await super._prepareContext(options); + + const featureForms = ['passive', 'action', 'reaction']; + context.features = context.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); + + return context; + } + + /* -------------------------------------------- */ + + async _onDragStart(event) { + const featureItem = event.currentTarget.closest('.feature-item'); + + if (featureItem) { + const feature = this.actor.items.get(featureItem.id); + const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true }; + event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); + event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); + } + } + + async _onDrop(event) { + event.stopPropagation(); + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); + + const item = await fromUuid(data.uuid); + if (item?.type === 'feature') { + if (data.fromInternal && item.parent?.uuid === this.actor.uuid) { + return; + } + + const itemData = item.toObject(); + delete itemData._id; + + await this.actor.createEmbeddedDocuments('Item', [itemData]); + } + } +} diff --git a/module/applications/sheets/actors/_module.mjs b/module/applications/sheets/actors/_module.mjs index c4ea2d94..1a2bebfb 100644 --- a/module/applications/sheets/actors/_module.mjs +++ b/module/applications/sheets/actors/_module.mjs @@ -2,4 +2,5 @@ export { default as Adversary } from './adversary.mjs'; export { default as Character } from './character.mjs'; export { default as Companion } from './companion.mjs'; export { default as Environment } from './environment.mjs'; +export { default as NPC } from './npc.mjs'; export { default as Party } from './party.mjs'; diff --git a/module/applications/sheets/actors/npc.mjs b/module/applications/sheets/actors/npc.mjs new file mode 100644 index 00000000..8c9048c2 --- /dev/null +++ b/module/applications/sheets/actors/npc.mjs @@ -0,0 +1,136 @@ +import DHBaseActorSheet from '../api/base-actor.mjs'; + +export default class NPCSheet extends DHBaseActorSheet { + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + classes: ['npc'], + position: { width: 660, height: 600 }, + window: { resizable: true }, + actions: {}, + window: { + resizable: true, + controls: [ + { + icon: 'fa-solid fa-signature', + label: 'DAGGERHEART.UI.Tooltip.configureAttribution', + action: 'editAttribution' + } + ] + }, + dragDrop: [ + { + dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', + dropSelector: null + } + ] + }; + + static PARTS = { + header: { template: 'systems/daggerheart/templates/sheets/actors/npc/header.hbs' }, + tabs: { template: 'systems/daggerheart/templates/sheets/actors/npc/navigation.hbs' }, + features: { + template: 'systems/daggerheart/templates/sheets/actors/npc/features.hbs', + scrollable: ['.feature-section'] + }, + notes: { + template: 'systems/daggerheart/templates/sheets/actors/npc/notes.hbs' + } + }; + + /** @inheritdoc */ + static TABS = { + primary: { + tabs: [{ id: 'notes' }, { id: 'features' }], + initial: 'notes', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' + } + }; + + /** @inheritdoc */ + _prepareTabs(group) { + const result = super._prepareTabs(group); + if (group === 'primary') { + result.features.empty = this.document.system.features.length === 0; + } + return result; + } + + /** @inheritdoc */ + async _preparePartContext(partId, context, options) { + context = await super._preparePartContext(partId, context, options); + switch (partId) { + case 'header': + await this._prepareHeaderContext(context, options); + break; + case 'features': + await this._prepareFeaturesContext(context, options); + break; + case 'notes': + await this._prepareNotesContext(context, options); + break; + } + + return context; + } + + /** + * Prepare render context for the Header part. + * @param {ApplicationRenderContext} context + * @param {ApplicationRenderOptions} options + * @returns {Promise} + * @protected + */ + async _prepareHeaderContext(context, _options) { + const { system } = this.document; + const { TextEditor } = foundry.applications.ux; + + context.description = await TextEditor.implementation.enrichHTML(system.description, { + secrets: this.document.isOwner, + relativeTo: this.document + }); + } + + /** + * Prepare render context for the Features part. + * @param {ApplicationRenderContext} context + * @param {ApplicationRenderOptions} options + * @returns {Promise} + * @protected + */ + async _prepareFeaturesContext(context, _options) { + const featureForms = ['passive', 'action', 'reaction']; + context.features = this.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); + } + + /** + * Prepare render context for the Biography part. + * @param {ApplicationRenderContext} context + * @param {ApplicationRenderOptions} options + * @returns {Promise} + * @protected + */ + async _prepareNotesContext(context, _options) { + const { system } = this.document; + const { TextEditor } = foundry.applications.ux; + + const paths = { + notes: 'notes' + }; + + for (const [key, path] of Object.entries(paths)) { + const value = foundry.utils.getProperty(system, path); + context[key] = { + field: system.schema.getField(path), + value, + enriched: await TextEditor.implementation.enrichHTML(value, { + secrets: this.document.isOwner, + relativeTo: this.document + }) + }; + } + } +} diff --git a/module/data/actor/_module.mjs b/module/data/actor/_module.mjs index 99577620..1fe1ef3f 100644 --- a/module/data/actor/_module.mjs +++ b/module/data/actor/_module.mjs @@ -1,15 +1,17 @@ import DhCharacter from './character.mjs'; import DhCompanion from './companion.mjs'; import DhAdversary from './adversary.mjs'; +import DhNPC from './npc.mjs'; import DhEnvironment from './environment.mjs'; import DhParty from './party.mjs'; -export { DhCharacter, DhCompanion, DhAdversary, DhEnvironment, DhParty }; +export { DhCharacter, DhCompanion, DhAdversary, DhNPC, DhEnvironment, DhParty }; export const config = { character: DhCharacter, companion: DhCompanion, adversary: DhAdversary, + npc: DhNPC, environment: DhEnvironment, party: DhParty }; diff --git a/module/data/actor/npc.mjs b/module/data/actor/npc.mjs new file mode 100644 index 00000000..2ccaf926 --- /dev/null +++ b/module/data/actor/npc.mjs @@ -0,0 +1,43 @@ +import DHNPCSettings from '../../applications/sheets-configs/npc-settings.mjs'; +import BaseDataActor from './base.mjs'; + +export default class DhpNPC extends BaseDataActor { + static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.NPC']; + + static get metadata() { + return foundry.utils.mergeObject(super.metadata, { + label: 'TYPES.Actor.npc', + type: 'npc', + settingSheet: DHNPCSettings, + hasResistances: false, + hasAttribution: true + }); + } + + static defineSchema() { + const fields = foundry.data.fields; + return { + ...super.defineSchema(), + difficulty: new fields.NumberField({ + nullable: true, + initial: null, + integer: true, + label: 'DAGGERHEART.GENERAL.difficulty' + }), + description: new fields.HTMLField({ label: 'DAGGERHEART.GENERAL.description' }), + motives: new fields.StringField(), + notes: new fields.HTMLField() + }; + } + + /**@inheritdoc */ + static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/actors/drama-masks.svg'; + + get features() { + return this.parent.items.filter(x => x.type === 'feature'); + } + + isItemValid(source) { + return super.isItemValid(source) || source.type === 'feature'; + } +} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index e4c11a5c..6c462d98 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -109,6 +109,14 @@ export default class DhpActor extends Actor { }); } + if (this.type === 'npc') { + Object.assign(update, { + prototypeToken: { + disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY + } + }); + } + this.updateSource(update); } diff --git a/styles/less/global/tab-navigation.less b/styles/less/global/tab-navigation.less index 038a9749..3d143b4c 100755 --- a/styles/less/global/tab-navigation.less +++ b/styles/less/global/tab-navigation.less @@ -3,8 +3,7 @@ .daggerheart.dh-style { .tab-navigation { - margin: 5px 0; - height: 40px; + margin: 5px 0 10px 0; width: 100%; .navigation-container { @@ -21,6 +20,10 @@ a { color: @color-text-emphatic; + + &.empty:not(.active) { + opacity: 0.4; + } } } } diff --git a/styles/less/sheets/actors/adversary/header.less b/styles/less/sheets/actors/adversary/header.less index 8bd3fcee..1e5e4fa5 100644 --- a/styles/less/sheets/actors/adversary/header.less +++ b/styles/less/sheets/actors/adversary/header.less @@ -35,7 +35,7 @@ .tags { display: flex; gap: 10px; - padding-bottom: 16px; + padding-bottom: 8px; .tag { display: flex; @@ -67,11 +67,5 @@ gap: 12px; padding: 16px 0; } - - .adversary-navigation { - display: flex; - gap: 8px; - align-items: center; - } } } diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index b4df96bf..aca789a6 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -148,10 +148,8 @@ } .companion-navigation { - display: flex; - gap: 8px; - align-items: baseline; width: 100%; + padding: 0 10px; } } } diff --git a/styles/less/sheets/actors/environment/header.less b/styles/less/sheets/actors/environment/header.less index 85471af4..da6954e0 100644 --- a/styles/less/sheets/actors/environment/header.less +++ b/styles/less/sheets/actors/environment/header.less @@ -138,10 +138,8 @@ } .environment-navigation { - display: flex; - gap: 20px; - align-items: baseline; padding: 0 20px; + .tab-navigation { margin-top: 0; } diff --git a/styles/less/sheets/actors/npc/features.less b/styles/less/sheets/actors/npc/features.less new file mode 100644 index 00000000..107b5a06 --- /dev/null +++ b/styles/less/sheets/actors/npc/features.less @@ -0,0 +1,18 @@ +.application.sheet.daggerheart.actor.dh-style.npc { + .tab.features { + &.active { + overflow: hidden; + display: flex; + flex-direction: column; + } + + .feature-section { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + mask-image: linear-gradient(0deg, transparent 0%, black 5%); + padding-bottom: 20px; + } + } +} diff --git a/styles/less/sheets/actors/npc/header.less b/styles/less/sheets/actors/npc/header.less new file mode 100644 index 00000000..d49d763c --- /dev/null +++ b/styles/less/sheets/actors/npc/header.less @@ -0,0 +1,83 @@ +.application.sheet.daggerheart.actor.dh-style.npc { + .npc-header-sheet { + width: 100%; + display: flex; + + .portrait { + cursor: pointer; + width: 275px; + + img { + height: 275px; + } + } + + .tags { + display: flex; + gap: 10px; + padding-bottom: 8px; + + .tag { + display: flex; + flex-direction: row; + gap: 4px; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: var(--font-size-12); + font: @font-body; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: var(--font-size-12); + } + } + + .info-section { + flex: 1; + padding: 0 15px; + padding-top: var(--header-height); + display: flex; + flex-direction: column; + + .name-row { + display: flex; + gap: 5px; + align-items: center; + justify-content: space-between; + padding: 8px 0; + + h1 { + display: flex; + flex: 1; + padding: 6px 0 0 0; + font-size: var(--font-size-32); + text-align: start; + border: 1px solid transparent; + outline: 2px solid transparent; + transition: all 0.3s ease; + word-break: break-word; + + &:hover { + outline: 2px solid light-dark(@dark, @golden); + } + } + } + + .npc-info { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px 0; + } + } + } +} \ No newline at end of file diff --git a/styles/less/sheets/actors/npc/index.less b/styles/less/sheets/actors/npc/index.less new file mode 100644 index 00000000..2d7d54e3 --- /dev/null +++ b/styles/less/sheets/actors/npc/index.less @@ -0,0 +1,3 @@ +@import './sheet.less'; +@import './header.less'; +@import './features.less'; \ No newline at end of file diff --git a/styles/less/sheets/actors/npc/sheet.less b/styles/less/sheets/actors/npc/sheet.less new file mode 100644 index 00000000..8ba3b7a9 --- /dev/null +++ b/styles/less/sheets/actors/npc/sheet.less @@ -0,0 +1,10 @@ +.application.sheet.daggerheart.actor.dh-style.npc { + .window-content { + display: grid; + grid-template-rows: auto auto 1fr; + } + + .npc-navigation { + padding: 0 15px; + } +} \ No newline at end of file diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index ca1bc840..4312f755 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -6,6 +6,7 @@ @import './actors/character/index.less'; @import './actors/companion/index.less'; @import './actors/environment/index.less'; +@import './actors/npc/index.less'; @import './actors/party/index.less'; @import './items/beastform.less'; diff --git a/system.json b/system.json index 2acd7570..89320768 100644 --- a/system.json +++ b/system.json @@ -244,11 +244,14 @@ "adversary": { "htmlFields": ["notes", "description"] }, + "npc": { + "htmlFields": ["notes"] + }, "environment": { "htmlFields": ["notes", "description"] }, "party": { - "htmlFields": ["notes"] + "htmlFields": ["notes", "description"] } }, "Item": { diff --git a/templates/sheets-settings/npc-settings/details.hbs b/templates/sheets-settings/npc-settings/details.hbs new file mode 100644 index 00000000..0e18b488 --- /dev/null +++ b/templates/sheets-settings/npc-settings/details.hbs @@ -0,0 +1,13 @@ +
      +
      + {{localize "DAGGERHEART.GENERAL.description"}} + {{formInput systemFields.description value=document._source.system.description}} +
      + + {{formGroup systemFields.motives value=document._source.system.motives}} + {{formGroup systemFields.difficulty value=document._source.system.difficulty localize=true}} +
      diff --git a/templates/sheets-settings/npc-settings/features.hbs b/templates/sheets-settings/npc-settings/features.hbs new file mode 100644 index 00000000..2f2f5f47 --- /dev/null +++ b/templates/sheets-settings/npc-settings/features.hbs @@ -0,0 +1,29 @@ +
      + +
      + {{localize tabs.features.label}} +
        + {{#each @root.features as |feature|}} +
      • + +
        + {{feature.name}} +
        +
        + + +
        +
      • + {{/each}} +
      +
      + {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
      +
      +
      \ No newline at end of file diff --git a/templates/sheets-settings/npc-settings/header.hbs b/templates/sheets-settings/npc-settings/header.hbs new file mode 100644 index 00000000..c9cb60fe --- /dev/null +++ b/templates/sheets-settings/npc-settings/header.hbs @@ -0,0 +1,3 @@ +
      +

      {{document.name}}

      +
      \ No newline at end of file diff --git a/templates/sheets/actors/adversary/header.hbs b/templates/sheets/actors/adversary/header.hbs index fba96980..5adc235a 100644 --- a/templates/sheets/actors/adversary/header.hbs +++ b/templates/sheets/actors/adversary/header.hbs @@ -44,10 +44,9 @@
      -
      - {{> '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/actors/companion/header.hbs b/templates/sheets/actors/companion/header.hbs index d10c0640..9c324709 100644 --- a/templates/sheets/actors/companion/header.hbs +++ b/templates/sheets/actors/companion/header.hbs @@ -50,9 +50,10 @@
      - {{> '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/actors/environment/header.hbs b/templates/sheets/actors/environment/header.hbs index 2c6bbb5a..1b4073c7 100644 --- a/templates/sheets/actors/environment/header.hbs +++ b/templates/sheets/actors/environment/header.hbs @@ -44,9 +44,10 @@
      - {{> '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/actors/npc/features.hbs b/templates/sheets/actors/npc/features.hbs new file mode 100644 index 00000000..3b495e74 --- /dev/null +++ b/templates/sheets/actors/npc/features.hbs @@ -0,0 +1,14 @@ +
      +
      + {{> 'daggerheart.inventory-items' + title=tabs.features.label + type='feature' + collection=@root.features + hideContextMenu=true + hideModifyControls=true + canCreate=@root.editable + showActions=@root.editable + }} +
      +
      \ No newline at end of file diff --git a/templates/sheets/actors/npc/header.hbs b/templates/sheets/actors/npc/header.hbs new file mode 100644 index 00000000..8dc345dc --- /dev/null +++ b/templates/sheets/actors/npc/header.hbs @@ -0,0 +1,40 @@ +
      +
      + {{source.name}} +
      +
      + + +
      +

      {{source.name}}

      +
      + + {{#if source.system.difficulty}} +
      +
      + {{localize "DAGGERHEART.GENERAL.difficulty"}} + {{source.system.difficulty}} +
      +
      + {{/if}} + + + +
      + + {{{description}}} + +
      + {{localize 'DAGGERHEART.ACTORS.NPC.FIELDS.motives.label'}}: + {{source.system.motives}} +
      +
      +
      +
      \ No newline at end of file diff --git a/templates/sheets/actors/npc/navigation.hbs b/templates/sheets/actors/npc/navigation.hbs new file mode 100644 index 00000000..ae684f0d --- /dev/null +++ b/templates/sheets/actors/npc/navigation.hbs @@ -0,0 +1,7 @@ +
      + {{#> '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/actors/npc/notes.hbs b/templates/sheets/actors/npc/notes.hbs new file mode 100644 index 00000000..bc9ac3cf --- /dev/null +++ b/templates/sheets/actors/npc/notes.hbs @@ -0,0 +1,11 @@ +
      + {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} + + {{#if (and showAttribution document.system.attribution.artist)}} + + {{/if}} +
      \ 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 f9a31d3e..8af1f140 100755 --- a/templates/sheets/global/tabs/tab-navigation.hbs +++ b/templates/sheets/global/tabs/tab-navigation.hbs @@ -4,7 +4,7 @@

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

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

      {{#times member.resources.hope.max}} @@ -79,7 +79,7 @@
      - {{#unless (eq member.type 'companion') }} + {{#unless (or (eq member.type 'companion') (eq member.type 'npc')) }}
      @@ -101,25 +101,27 @@
      {{/unless}} -
      -
      - - - - - {{member.resources.stress.value}} - / - {{member.resources.stress.max}} - -
      -
      - {{#times member.resources.stress.max}} - + {{#unless (eq member.type 'npc')}} +
      +
      + + - {{/times}} + + {{member.resources.stress.value}} + / + {{member.resources.stress.max}} + +
      +
      + {{#times member.resources.stress.max}} + + + {{/times}} +
      -
      + {{/unless}} {{#if member.armorScore.max}}
      From 98ce49b928196c931f315bf2be03e98150ddbb50 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 1 Jun 2026 05:06:24 -0400 Subject: [PATCH 285/304] Avoid default type on name and item create dialogs (#1958) --- daggerheart.mjs | 30 ++++++++++++++++++++++++++++++ module/documents/actor.mjs | 5 +++++ module/documents/item.mjs | 1 + 3 files changed, 36 insertions(+) diff --git a/daggerheart.mjs b/daggerheart.mjs index 23977628..25c41ced 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -446,3 +446,33 @@ Hooks.on('canvasTearDown', canvas => { Hooks.on('canvasReady', canas => { game.system.registeredTriggers.registerSceneTriggers(canvas.scene); }); + +/** Make the user to select a document type, instead of having a default doc type for them to accidentally keep */ +Hooks.on('renderDialogV2', (_dialog, html) => { + if (!html.classList.contains('dialog')) return; + const cls = html.classList.contains('item-create') + ? documents.DHItem.implementation + : html.classList.contains('actor-create') + ? documents.DhpActor.implementation + : null; + if (!cls) return; + + const form = html.querySelector('form'); + const submit = html.querySelector('button[type=submit]'); + const select = html.querySelector('select[name=type]'); + const nameInput = html.querySelector('input[name=name]'); + if (!form || !select || !submit || !nameInput) return; + + nameInput.placeholder = cls.defaultName({}); + const emptyOption = document.createElement('option'); + emptyOption.value = ''; + emptyOption.selected = true; + select.required = true; + select.prepend(emptyOption); + submit.addEventListener('click', event => { + if (!form.reportValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + }); +}); diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 6c462d98..fb10435f 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -65,6 +65,11 @@ export default class DhpActor extends Actor { }; } + static createDialog(data, createOptions, options, renderOptions) { + options.classes = [options.classes ?? [], 'actor-create'].flat(); // handled in hook + return super.createDialog(data, createOptions, options, renderOptions); + } + /* -------------------------------------------- */ /** @inheritDoc */ diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 603ca594..f46e24e6 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -82,6 +82,7 @@ export default class DHItem extends foundry.documents.Item { /** @inheritdoc */ static async createDialog(data = {}, createOptions = {}, options = {}) { const { folders, types, template, context = {}, ...dialogOptions } = options; + dialogOptions.classes = [options.classes ?? [], 'item-create'].flat(); // handled in hook if (types?.length === 0) { throw new Error('The array of sub-types to restrict to must not be empty.'); From d0c29ede56b9834c4a71ac58656524b1996b724f Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:24:00 +0200 Subject: [PATCH 286/304] Fixed so the combat tracker doesn't error out if a combatant has no attached token (#1960) --- module/applications/ui/combatTracker.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index 0989bcb8..8dd76850 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -133,7 +133,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'), type: combatant.actor?.system?.type, img: await this._getCombatantThumbnail(combatant), - disposition: combatant.token.disposition + disposition: combatant.token?.disposition }; turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin( From 6448666579f4cca9a3f72c15db9cbe2e049d3208 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 1 Jun 2026 19:47:06 +0200 Subject: [PATCH 287/304] Updated combat contextmenu --- module/applications/ui/combatTracker.mjs | 44 ++++++++++++++++--- .../ui/combatTracker/combatTrackerHeader.hbs | 5 +++ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index 8dd76850..25f3e06b 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -84,19 +84,49 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C }); } + /** + * Open the dialog used to edit the name of the currently viewed Combat encounter. + * @this {CombatTracker} + * @returns {Promise} + */ + static async #onEditName() { + const combat = this.viewed; + if (!combat || !game.user.isGM) return null; + const field = combat.schema.fields.name; + const inputHTML = field.toFormGroup({}, { name: 'name', value: combat.name, autofocus: true }).outerHTML; + const formData = await foundry.applications.api.DialogV2.input({ + window: { icon: 'fa-solid fa-tag', title: 'COMBAT.ACTIONS.EditNameTitle' }, + position: { width: 480 }, + content: inputHTML + }); + await combat.update({ name: formData.name || '' }); + } + _getCombatContextOptions() { return [ { - label: 'COMBAT.ClearMovementHistories', - icon: '', - visible: () => game.user.isGM && this.viewed?.combatants.size > 0, - callback: () => this.viewed.clearMovementHistories() + label: 'COMBAT.ACTIONS.EditName', + icon: 'fa-solid fa-tag', + visible: () => game.user.isGM && !!this.viewed, + onClick: () => DhCombatTracker.#onEditName.call(this) }, { - label: 'COMBAT.Delete', - icon: '', + label: 'COMBAT.ACTIONS.LinkToScene', + icon: '', + visible: () => game.user.isGM && !this.scene, + onClick: () => this.viewed.toggleSceneLink() + }, + { + label: 'COMBAT.ACTIONS.UnlinkFromScene', + icon: '', + visible: () => game.user.isGM && !!this.scene, + onClick: () => this.viewed.toggleSceneLink() + }, + { + label: 'COMBAT.End', + icon: 'fa-solid fa-xmark', visible: () => game.user.isGM && !!this.viewed, - callback: () => this.viewed.endCombat() + onClick: () => this.viewed.endCombat() } ]; } diff --git a/templates/ui/combatTracker/combatTrackerHeader.hbs b/templates/ui/combatTracker/combatTrackerHeader.hbs index 803286ab..9f5a7561 100644 --- a/templates/ui/combatTracker/combatTrackerHeader.hbs +++ b/templates/ui/combatTracker/combatTrackerHeader.hbs @@ -50,6 +50,11 @@ {{/if}} + {{!-- Encounter Name --}} + {{#if combat.name}} +

      {{ combat.name }}

      + {{/if}} +
      {{!-- Combat Status --}} From 646ebc8bdf0fa6bcb18a2997c76802bff44a59a1 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:52:27 +0200 Subject: [PATCH 288/304] Added a temp fix for status effect rows (#1965) --- styles/less/hud/token-hud/token-hud.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/styles/less/hud/token-hud/token-hud.less b/styles/less/hud/token-hud/token-hud.less index 3cb94e1e..3b998f4e 100644 --- a/styles/less/hud/token-hud/token-hud.less +++ b/styles/less/hud/token-hud/token-hud.less @@ -38,6 +38,9 @@ } .status-effects { + // TODO: Remove when the issue https://github.com/foundryvtt/foundryvtt/issues/14410 is resolved and Foundry handles it cleanly themselves. + grid-template-rows: min-content; + .effect-control-container { position: relative; From df4a2c5d57c3592d3c15550c1f2fadc36bdba655 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:53:28 +0200 Subject: [PATCH 289/304] Fixed Countdown.migrationData assuming an array when it's actually an object (#1962) --- module/data/action/countdownAction.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/data/action/countdownAction.mjs b/module/data/action/countdownAction.mjs index abcc6b40..cb141637 100644 --- a/module/data/action/countdownAction.mjs +++ b/module/data/action/countdownAction.mjs @@ -36,7 +36,7 @@ export default class DhCountdownAction extends DHBaseAction { /** @inheritDoc */ static migrateData(source) { - for (const countdown of source.countdown) { + for (const countdown of Object.values(source.countdown)) { if (countdown.progress.max) { countdown.progress.startFormula = countdown.progress.max; countdown.progress.start = 1; From bcf274f1d05819cf9148ab597e558962e270a2e2 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:59:44 +0200 Subject: [PATCH 290/304] [Fix] ChatMessage Saves Pending Confirmation (#1963) --- lang/en.json | 6 +++--- module/documents/chatMessage.mjs | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lang/en.json b/lang/en.json index 9ce515d9..3a1340e0 100755 --- a/lang/en.json +++ b/lang/en.json @@ -711,9 +711,9 @@ }, "PendingReactionsDialog": { "title": "Pending Reaction Rolls Found", - "unfinishedRolls": "Some Tokens still need to roll their Reaction Roll.", - "confirmation": "Are you sure you want to continue ?", - "warning": "Undone reaction rolls will be considered as failed" + "unfinishedRolls": "Some Tokens have not finished their Reaction Rolls.", + "warning": "Unfinished reaction rolls will be considered as failed.", + "confirmation": "Are you sure you want to continue?" }, "ReactionRoll": { "title": "Reaction Roll: {trait}" diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 78bab016..893e6e5c 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -183,7 +183,11 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (pendingingSaves.length) { const confirm = await foundry.applications.api.DialogV2.confirm({ window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') }, - content: `

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}

      ` + content: ` +

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}

      +

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}

      +

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}

      + ` }); if (!confirm) return; } @@ -247,8 +251,24 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { const targets = this.filterPermTargets(this.system.hitTargets), config = foundry.utils.deepClone(this.system); config.event = event; + if (targets.length === 0) - ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); + return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); + else if (config.hasSave) { + const pendingingSaves = targets.filter(t => t.saved.success === null); + if (pendingingSaves.length) { + const confirm = await foundry.applications.api.DialogV2.confirm({ + window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') }, + content: ` +

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}

      +

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}

      +

      ${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}

      + ` + }); + if (!confirm) return; + } + } + this.consumeOnSuccess(); this.system.action?.workflow.get('effects')?.execute(config, targets, true); } From 3c36c5747d477a831d6b0d5a653a414073f6f39c Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:02:42 +0200 Subject: [PATCH 291/304] [Fix] Base Attack Context Menu (#1961) * Fixed Adversary standard attack context menu * Fixed Character base attack context menu * Fixed Companion base attack context menu --- .../applications/sheets/actors/adversary.mjs | 10 +++++ .../applications/sheets/actors/character.mjs | 8 ++++ .../applications/sheets/actors/companion.mjs | 12 +++++- module/applications/sheets/api/base-actor.mjs | 37 +++++++++++++++++++ module/data/action/attackAction.mjs | 7 +++- 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 04be3efb..06dd4a0f 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -31,6 +31,16 @@ export default class AdversarySheet extends DHBaseActorSheet { dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', dropSelector: null } + ], + contextMenus: [ + { + handler: DHBaseActorSheet.getBaseAttackContextOptions, + selector: '[data-item-uuid][data-type="attack"]', + options: { + parentClassHooks: false, + fixed: true + } + } ] }; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 19b82712..e4d0e6d9 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -65,6 +65,14 @@ export default class CharacterSheet extends DHBaseActorSheet { fixed: true } }, + { + handler: DHBaseActorSheet.getBaseAttackContextOptions, + selector: '[data-item-uuid][data-type="attack"]', + options: { + parentClassHooks: false, + fixed: true + } + }, { handler: CharacterSheet.#getDomainCardContextOptions, selector: '[data-item-uuid][data-type="domainCard"]', diff --git a/module/applications/sheets/actors/companion.mjs b/module/applications/sheets/actors/companion.mjs index b30b9c07..a01b4a64 100644 --- a/module/applications/sheets/actors/companion.mjs +++ b/module/applications/sheets/actors/companion.mjs @@ -11,7 +11,17 @@ export default class DhCompanionSheet extends DHBaseActorSheet { toggleStress: DhCompanionSheet.#toggleStress, actionRoll: DhCompanionSheet.#actionRoll, levelManagement: DhCompanionSheet.#levelManagement - } + }, + contextMenus: [ + { + handler: DHBaseActorSheet.getBaseAttackContextOptions, + selector: '[data-item-uuid][data-type="attack"]', + options: { + parentClassHooks: false, + fixed: true + } + } + ] }; static PARTS = { diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 5cd0f6a5..7b820822 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -189,6 +189,43 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true }); } + /** + * Get the set of ContextMenu options for the base attack. + * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance + * @this {CharacterSheet} + * @protected + */ + static getBaseAttackContextOptions() { + /**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */ + return [ + { + label: 'DAGGERHEART.CONFIG.RollTypes.attack.name', + icon: 'fa-solid fa-burst', + onClick: async (event, target) => (await getDocFromElement(target)).use(event) + }, + { + label: 'DAGGERHEART.GENERAL.damage', + icon: 'fa-solid fa-explosion', + onClick: async (event, target) => { + const doc = await getDocFromElement(target), + action = doc?.system?.attack ?? doc; + const config = action.prepareConfig(event); + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects( + this.document, + doc + ); + config.hasRoll = false; + return action && action.workflow.get('damage').execute(config, null, true); + } + }, + { + label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat', + icon: 'fa-solid fa-message', + onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid) + } + ]; + } + /* -------------------------------------------- */ /* Application Listener Actions */ /* -------------------------------------------- */ diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index c4d07c25..1f7e1c92 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -75,7 +75,12 @@ export default class DHAttackAction extends DHDamageAction { const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active; for (const { value, valueAlt, type } of damage.parts) { const usedValue = useAltDamage ? valueAlt : value; - const str = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {}); + const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {}); + const str = damageString + ? damageString + : game.i18n.format('DAGGERHEART.GENERAL.missingX', { + x: game.i18n.localize('DAGGERHEART.GENERAL.damage') + }); const icons = Array.from(type) .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon) From d98a7c951e5363ce1980b1540767bad923385e64 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:20:06 +0200 Subject: [PATCH 292/304] [Fix] Tooltip Color Scope (#1964) * Added DH style to tooltips * Setting dh-style for ResourceManagementTooltip and ArmorManagementTooltip --- module/applications/sheets/actors/character.mjs | 4 ++-- module/documents/tooltipManager.mjs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index e4d0e6d9..5d0e7144 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -1053,7 +1053,7 @@ export default class CharacterSheet extends DHBaseActorSheet { game.tooltip.activate(target, { html, locked: true, - cssClass: 'bordered-tooltip', + cssClass: 'bordered-tooltip dh-style', direction: 'DOWN' }); @@ -1149,7 +1149,7 @@ export default class CharacterSheet extends DHBaseActorSheet { game.tooltip.activate(target, { html, locked: true, - cssClass: 'bordered-tooltip', + cssClass: 'bordered-tooltip dh-style', direction: 'DOWN', noOffset: true }); diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index 18c03169..3e3f4a16 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -3,7 +3,6 @@ import { AdversaryBPPerEncounter, BaseBPPerEncounter } from '../config/encounter export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager { #wide = false; #bordered = false; - #active = false; async activate(element, options = {}) { const { TextEditor } = foundry.applications.ux; From 5dbcd9448064f97d202abb541cd5b4c5072fe544 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 1 Jun 2026 22:22:50 +0200 Subject: [PATCH 293/304] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 5994c576..588ceafe 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.3.0", + "version": "2.3.1", "compatibility": { "minimum": "14.361", "verified": "14.363", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.0/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.1/system.zip", "authors": [ { "name": "WBHarry" From 77c5cfcbb79e7131d58650fdc73016aece3e04f5 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 3 Jun 2026 21:46:23 +0200 Subject: [PATCH 294/304] Fixed so that actions on homebrew downtime/items don't crash due to note having metadata --- module/applications/sheets-configs/action-base-config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index e83dfae4..b65e1cdf 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -204,7 +204,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) }; } - if (this.action.parent.metadata.isInventoryItem) { + if (this.action.parent.metadata?.isInventoryItem) { options.quantity = { label: 'DAGGERHEART.GENERAL.itemQuantity', group: 'Global' From 6747be49b2089b4cc49200875c9cf82c553fa74f Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 4 Jun 2026 05:15:41 -0400 Subject: [PATCH 295/304] Allow removing empty string domains (#1968) --- module/applications/settings/homebrewSettings.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 40ea0301..09bb00f2 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -111,7 +111,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli switch (partId) { case 'domains': - const selectedDomain = this.selected.domain ? this.settings.domains[this.selected.domain] : null; + const selectedDomain = this.settings.domains[this.selected.domain] ?? null; const enrichedDescription = selectedDomain ? await foundry.applications.ux.TextEditor.implementation.enrichHTML(selectedDomain.description) : null; From 5ac4fc3b9ceec0f83e829295e6b197a47cdd4e01 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 4 Jun 2026 05:42:17 -0400 Subject: [PATCH 296/304] [Fix] visual quirk with blur in unfocused countdown (#1970) * Fix visual quirk with blur in unfocused countdown * Snuck in fixes and refactors --- module/applications/ui/countdowns.mjs | 22 ++++++++++++---------- styles/less/ui/countdown/countdown.less | 13 ++++++------- templates/ui/countdowns.hbs | 12 +++++++----- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 76e2b399..6fa05e29 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -31,9 +31,9 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application minimizable: false }, actions: { - toggleViewMode: DhCountdowns.#toggleViewMode, - editCountdowns: DhCountdowns.#editCountdowns, - loopCountdown: DhCountdowns.#loopCountdown, + toggleViewMode: DhCountdowns.#onToggleViewMode, + editCountdowns: DhCountdowns.#onEditCountdowns, + loopCountdown: DhCountdowns.#onLoopCountdown, decreaseCountdown: (_, target) => this.editCountdown(false, target), increaseCountdown: (_, target) => this.editCountdown(true, target) }, @@ -147,7 +147,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return true; } - static async #toggleViewMode() { + static async #onToggleViewMode() { const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode); const appMode = CONFIG.DH.GENERAL.countdownAppMode; const newMode = currentMode === appMode.textIcon ? appMode.iconOnly : appMode.textIcon; @@ -158,15 +158,16 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application this.render(); } - static async #editCountdowns() { + static async #onEditCountdowns() { new game.system.api.applications.ui.CountdownEdit().render(true); } - static async #loopCountdown(_, target) { + static async #onLoopCountdown(_, target) { if (!DhCountdowns.canPerformEdit()) return; const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const countdown = settings.countdowns[target.id]; + const countdownId = target.closest('[data-countdown]').dataset.countdown; + const countdown = settings.countdowns[countdownId]; let progressMax = countdown.progress.start; let message = null; @@ -185,7 +186,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application await waitForDiceSoNice(message); await settings.updateSource({ - [`countdowns.${target.id}.progress`]: { + [`countdowns.${countdownId}.progress`]: { current: newMax, start: newMax } @@ -199,11 +200,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application if (!DhCountdowns.canPerformEdit()) return; const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const countdown = settings.countdowns[target.id]; + const countdownId = target.closest('[data-countdown]').dataset.countdown; + const countdown = settings.countdowns[countdownId]; const newCurrent = increase ? Math.min(countdown.progress.current + 1, countdown.progress.start) : Math.max(countdown.progress.current - 1, 0); - await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent }); + await settings.updateSource({ [`countdowns.${countdownId}.progress.current`]: newCurrent }); await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, { refreshType: RefreshType.Countdown }); diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 66a6c88a..63e539ba 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -18,7 +18,7 @@ border: 0; box-shadow: none; color: @color-text-primary; - width: 300px; + width: 18.75rem; pointer-events: all; align-self: flex-end; transition: 0.3s right ease-in-out; @@ -36,7 +36,7 @@ transition: opacity var(--ui-fade-duration); } - :not(.performance-low, .noblur) { + &:not(.performance-low, .noblur) { backdrop-filter: blur(5px); } @@ -49,8 +49,7 @@ } &.icon-only { - width: 180px; - min-width: 180px; + width: 12rem; } .countdowns-header, @@ -108,8 +107,8 @@ gap: 16px; img { - width: 44px; - height: 44px; + width: 2.75rem; + height: 2.75rem; border-radius: 6px; } @@ -127,7 +126,7 @@ .countdown-tool-controls { display: flex; align-items: center; - gap: 16px; + gap: var(--spacer-12); } .progress-tag { diff --git a/templates/ui/countdowns.hbs b/templates/ui/countdowns.hbs index 95067826..faaffdc5 100644 --- a/templates/ui/countdowns.hbs +++ b/templates/ui/countdowns.hbs @@ -11,18 +11,20 @@
      {{#each countdowns as | countdown id |}} -
      +
      - {{#unless ../iconOnly}}{{/unless}} + {{#unless ../iconOnly}} +
      {{countdown.name}}
      + {{/unless}}
      - {{#if countdown.editable}}{{/if}} + {{#if countdown.editable}}{{/if}}
      {{countdown.progress.current}}/{{countdown.progress.start}}
      - {{#if countdown.editable}}{{/if}} + {{#if countdown.editable}}{{/if}}
      {{#if (not ../iconOnly)}} @@ -31,7 +33,7 @@ {{/if}} {{#unless (eq countdown.progress.looping "noLooping")}} - + {{#if (eq countdown.progress.looping "increasing")}} From c0c909584792661089370877e0eb521a836f7ca2 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 4 Jun 2026 14:08:40 -0400 Subject: [PATCH 297/304] [Fix] Preload ancestry and community features in description (#1967) * Preload ancestry and community features in description * Corrected comments --------- Co-authored-by: WBHarry --- module/data/item/ancestry.mjs | 6 +++++- module/data/item/community.mjs | 6 +++++- module/data/item/subclass.mjs | 2 +- module/helpers/utils.mjs | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/module/data/item/ancestry.mjs b/module/data/item/ancestry.mjs index b9253a3c..eae1136c 100644 --- a/module/data/item/ancestry.mjs +++ b/module/data/item/ancestry.mjs @@ -1,6 +1,6 @@ import BaseDataItem from './base.mjs'; import ItemLinkFields from '../../data/fields/itemLinkFields.mjs'; -import { getFeaturesHTMLData } from '../../helpers/utils.mjs'; +import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs'; export default class DHAncestry extends BaseDataItem { /** @inheritDoc */ @@ -45,6 +45,10 @@ export default class DHAncestry extends BaseDataItem { /**@inheritdoc */ async getDescriptionData() { + // Preload all ancestry features for acquisition from the cache + // todo: make feature acquisition async and replace feature helpers for methods + await fromUuids(this._source.features.map(f => f.item)); + const baseDescription = this.description; const features = await getFeaturesHTMLData(this.features); diff --git a/module/data/item/community.mjs b/module/data/item/community.mjs index 6d054976..6f4470b8 100644 --- a/module/data/item/community.mjs +++ b/module/data/item/community.mjs @@ -1,4 +1,4 @@ -import { getFeaturesHTMLData } from '../../helpers/utils.mjs'; +import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import BaseDataItem from './base.mjs'; @@ -27,6 +27,10 @@ export default class DHCommunity extends BaseDataItem { /**@inheritdoc */ async getDescriptionData() { + // Preload all community features for acquisition from the cache + // todo: make feature acquisition async and replace feature helpers for methods + await fromUuids(this._source.features); + const baseDescription = this.description; const features = await getFeaturesHTMLData(this.features); diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 55b078c2..934b55d3 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -91,7 +91,7 @@ export default class DHSubclass extends BaseDataItem { ? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label) : null; - // Preload all class features for acquisition from the cache + // Preload all subclass features for acquisition from the cache // todo: make feature acquisition async and replace feature helpers for methods await fromUuids(this._source.features.map(f => f.item)); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 2f20175b..ddc353b1 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -879,6 +879,7 @@ export async function fromUuids(uuids) { const packEmbeddedEntries = entries.filter( e => !(e.value instanceof Document) && + e.parsed && e.parsed.collection instanceof foundry.documents.collections.CompendiumCollection && e.parsed.embedded.length > 0 ); @@ -895,7 +896,7 @@ export async function fromUuids(uuids) { const pack = game.packs.get(packGroup[0].value.pack); if (!pack) continue; - const ids = packGroup.map(p => p.parsed.id); + const ids = packGroup.map(p => p.parsed?.id).filter(id => !!id); const documents = await pack.getDocuments({ _id__in: ids }); for (const p of packGroup) { p.value = documents.find(d => d.id === p.parsed.id) ?? p.value; From 52b81de11f7224c15f2e76243f4228abb0ea859f Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 5 Jun 2026 00:30:41 +0200 Subject: [PATCH 298/304] Fixed so that the saved data for an experience that is in the character data is used over that in the levelup data if available (#1971) --- module/applications/levelup/levelup.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index c4616d9a..03638548 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -358,14 +358,14 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases'); if (experienceIncreaseTagify) { const allExperiences = { - ...this.actor.system.experiences, ...Object.values(this.levelup.levels).reduce((acc, level) => { for (const key of Object.keys(level.achievements.experiences)) { acc[key] = level.achievements.experiences[key]; } return acc; - }, {}) + }, {}), + ...this.actor.system.experiences }; tagifyElement( experienceIncreaseTagify, From 2fc5b01f091edca3138aa83fdfbf1d055c1a6f19 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 05:31:01 -0400 Subject: [PATCH 299/304] Fix rerolling when hope/fear automation is enabled (#1972) --- module/dice/helpers.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs index 33519949..35adb8b7 100644 --- a/module/dice/helpers.mjs +++ b/module/dice/helpers.mjs @@ -1,3 +1,5 @@ +import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; + export function updateResourcesForDualityReroll(oldDuality, newDuality, actor) { const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return; From 5be79f4ab83b2b4483a0f4deb7f7e7f60bc60e34 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 05:33:20 -0400 Subject: [PATCH 300/304] Fix several issues with inline damage (#1973) --- module/data/actor/tierAdjustment.mjs | 5 +++-- module/dice/dhRoll.mjs | 1 + module/enrichers/DamageEnricher.mjs | 4 ++-- module/enrichers/parser.mjs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs index 785eec2b..bc6ad176 100644 --- a/module/data/actor/tierAdjustment.mjs +++ b/module/data/actor/tierAdjustment.mjs @@ -1,5 +1,6 @@ import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs'; import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; +import { parseInlineParams } from '../../enrichers/parser.mjs'; export function getTierAdjustedAdversary(source, tier) { const currentTier = source.tier ?? 1; @@ -60,8 +61,8 @@ export function getTierAdjustedAdversary(source, tier) { const descriptionFormulas = []; for (const withDescription of [item.system, ...Object.values(item.system.actions)]) { withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => { - const { value: formula } = parseInlineParams(inner); - if (!formula || !type) return match; + const { value: formula } = parseInlineParams(inner, { first: 'value' }); + if (!formula) return match; try { const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula; diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 02c4ab24..fb20870f 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -37,6 +37,7 @@ export default class DHRoll extends Roll { static async buildConfigure(config = {}, message = {}) { config.hooks = [...this.getHooks(), '']; config.dialog ??= {}; + config.damageOptions ??= {}; for (const hook of config.hooks) { if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null; diff --git a/module/enrichers/DamageEnricher.mjs b/module/enrichers/DamageEnricher.mjs index e3f9c42a..db0e8729 100644 --- a/module/enrichers/DamageEnricher.mjs +++ b/module/enrichers/DamageEnricher.mjs @@ -1,7 +1,7 @@ import { parseInlineParams } from './parser.mjs'; export default function DhDamageEnricher(match, _options) { - const { value, type, inline } = parseInlineParams(match[1]); + const { value, type, inline } = parseInlineParams(match[1], { first: 'value' }); if (!value || !type) return match[0]; return getDamageMessage(value, type, inline, match[0]); } @@ -59,7 +59,7 @@ export const renderDamageButton = async event => { { formula: value, applyTo: CONFIG.DH.GENERAL.healingTypes.hitPoints.id, - type: type + damageTypes: type } ] }; diff --git a/module/enrichers/parser.mjs b/module/enrichers/parser.mjs index 365caec9..76ea0b73 100644 --- a/module/enrichers/parser.mjs +++ b/module/enrichers/parser.mjs @@ -8,7 +8,7 @@ export function parseInlineParams(paramString, { first } = {}) { const parts = paramString.split('|').map(x => x.trim()); const params = {}; for (const [idx, param] of parts.entries()) { - if (first && idx === 0) { + if (first && idx === 0 && !param.includes(':')) { params[first] = param; } else { const parts = param.split(':'); From f0a7539018dd9f6c6c1320b1036adb084a483d33 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 06:25:44 -0400 Subject: [PATCH 301/304] Update README.md (#1976) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f59143fd..ac3666b3 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,10 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart Looking to contribute to the project? Look no further, check out our [contributing guide](CONTRIBUTING.md), and keep the [Code of Conduct](coc.md) in mind when working on things. +## AI Policy + +The Foundryborne Daggerheart system does not make use of AI (generative or otherwise) for any area of its implementation. We expect all contributors to follow this same policy when contributing with a pull request; contributions made using AI will be rejected outright. + ## Disclaimer: **Daggerheart System** From 3527fd7959a54bbee9e4c0419d1a973d2c42c770 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 5 Jun 2026 12:31:30 +0200 Subject: [PATCH 302/304] Raised version --- system.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system.json b/system.json index 588ceafe..ed14a17b 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.3.1", + "version": "2.3.2", "compatibility": { "minimum": "14.361", "verified": "14.363", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.1/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.2/system.zip", "authors": [ { "name": "WBHarry" From 6312a171e23f9b2769b3e81da3da1aff760c0ec7 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:36:07 +0200 Subject: [PATCH 303/304] [Housekeeping] Styles Index:ification (#1977) --- styles/daggerheart.less | 12 +--- styles/less/dialog/actions/index.less | 1 + styles/less/dialog/attribution/index.less | 1 + styles/less/dialog/beastform/index.less | 1 + .../less/dialog/character-creation/index.less | 4 ++ styles/less/dialog/character-reset/index.less | 1 + .../compendiumBrowserPackDialog/index.less | 1 + .../less/dialog/damage-reduction/index.less | 2 + .../less/dialog/damage-selection/index.less | 1 + styles/less/dialog/death-move/index.less | 1 + styles/less/dialog/dice-roll/index.less | 1 + styles/less/dialog/downtime/index.less | 1 + .../dialog/group-roll-dialog/_common.less | 44 -------------- .../less/dialog/group-roll-dialog/index.less | 11 +--- .../less/dialog/group-roll-dialog/sheet.less | 48 +++++++++++++++ styles/less/dialog/image-select/index.less | 1 + styles/less/dialog/index.less | 60 ++++++------------- styles/less/dialog/item-transfer/index.less | 1 + .../less/dialog/multiclass-choice/index.less | 1 + styles/less/dialog/resource-dice/index.less | 1 + styles/less/dialog/risk-it-all/index.less | 1 + styles/less/dialog/settings/index.less | 1 + styles/less/dialog/tag-team-dialog/index.less | 2 + styles/less/hud/index.less | 2 +- styles/less/hud/token-hud/index.less | 1 + .../adversary-settings/index.less | 3 + .../character-settings/index.less | 1 + .../environment-settings/index.less | 2 + styles/less/sheets-settings/index.less | 10 +--- styles/less/sheets/actions/index.less | 1 + styles/less/sheets/activeEffects/index.less | 1 + styles/less/sheets/actors/index.less | 7 +++ styles/less/sheets/index.less | 27 ++------- styles/less/sheets/items/index.less | 6 ++ styles/less/sheets/rollTables/index.less | 1 + styles/less/ui/chat/index.less | 10 ++++ styles/less/ui/combat-sidebar/index.less | 5 ++ styles/less/ui/countdown/index.less | 3 + styles/less/ui/effects-display/index.less | 1 + styles/less/ui/index.less | 51 ++++------------ styles/less/ui/item-browser/index.less | 1 + styles/less/ui/ownership-selection/index.less | 1 + styles/less/ui/resources/index.less | 1 + styles/less/ui/scene-config/index.less | 1 + styles/less/ui/scene-navigation/index.less | 1 + .../settings/appearance-settings/index.less | 1 + .../ui/settings/homebrew-settings/index.less | 3 + styles/less/ui/settings/index.less | 3 + styles/less/ui/sidebar/index.less | 2 + styles/less/utils/index.less | 4 ++ styles/less/ux/autocomplete/index.less | 1 + styles/less/ux/index.less | 12 +--- styles/less/ux/tooltip/index.less | 7 +++ 53 files changed, 185 insertions(+), 183 deletions(-) create mode 100644 styles/less/dialog/actions/index.less create mode 100644 styles/less/dialog/attribution/index.less create mode 100644 styles/less/dialog/beastform/index.less create mode 100644 styles/less/dialog/character-creation/index.less create mode 100644 styles/less/dialog/character-reset/index.less create mode 100644 styles/less/dialog/compendiumBrowserPackDialog/index.less create mode 100644 styles/less/dialog/damage-reduction/index.less create mode 100644 styles/less/dialog/damage-selection/index.less create mode 100644 styles/less/dialog/death-move/index.less create mode 100644 styles/less/dialog/dice-roll/index.less create mode 100644 styles/less/dialog/downtime/index.less delete mode 100644 styles/less/dialog/group-roll-dialog/_common.less create mode 100644 styles/less/dialog/group-roll-dialog/sheet.less create mode 100644 styles/less/dialog/image-select/index.less create mode 100644 styles/less/dialog/item-transfer/index.less create mode 100644 styles/less/dialog/multiclass-choice/index.less create mode 100644 styles/less/dialog/resource-dice/index.less create mode 100644 styles/less/dialog/risk-it-all/index.less create mode 100644 styles/less/dialog/settings/index.less create mode 100644 styles/less/dialog/tag-team-dialog/index.less create mode 100644 styles/less/hud/token-hud/index.less create mode 100644 styles/less/sheets-settings/adversary-settings/index.less create mode 100644 styles/less/sheets-settings/character-settings/index.less create mode 100644 styles/less/sheets-settings/environment-settings/index.less create mode 100644 styles/less/sheets/actions/index.less create mode 100644 styles/less/sheets/activeEffects/index.less create mode 100644 styles/less/sheets/actors/index.less create mode 100644 styles/less/sheets/items/index.less create mode 100644 styles/less/sheets/rollTables/index.less create mode 100644 styles/less/ui/chat/index.less create mode 100644 styles/less/ui/combat-sidebar/index.less create mode 100644 styles/less/ui/countdown/index.less create mode 100644 styles/less/ui/effects-display/index.less create mode 100644 styles/less/ui/item-browser/index.less create mode 100644 styles/less/ui/ownership-selection/index.less create mode 100644 styles/less/ui/resources/index.less create mode 100644 styles/less/ui/scene-config/index.less create mode 100644 styles/less/ui/scene-navigation/index.less create mode 100644 styles/less/ui/settings/appearance-settings/index.less create mode 100644 styles/less/ui/settings/homebrew-settings/index.less create mode 100644 styles/less/ui/settings/index.less create mode 100644 styles/less/ui/sidebar/index.less create mode 100644 styles/less/utils/index.less create mode 100644 styles/less/ux/autocomplete/index.less create mode 100644 styles/less/ux/tooltip/index.less diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 187402fb..4da2e043 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -1,19 +1,11 @@ @import './less/sheets/index.less'; @import './less/sheets-settings/index.less'; - @import './less/dialog/index.less'; - -@import './less//hud/index.less'; - -@import './less/utils/colors.less'; -@import './less/utils/fonts.less'; - +@import './less/hud/index.less'; +@import './less/utils/index.less'; @import './less/global/index.less'; - @import './less/ui/index.less'; - @import './less/ux/index.less'; @import '../build/tagify.css'; -@import './less/utils/mixin.less'; diff --git a/styles/less/dialog/actions/index.less b/styles/less/dialog/actions/index.less new file mode 100644 index 00000000..e9cc0401 --- /dev/null +++ b/styles/less/dialog/actions/index.less @@ -0,0 +1 @@ +@import "./action-list.less"; \ No newline at end of file diff --git a/styles/less/dialog/attribution/index.less b/styles/less/dialog/attribution/index.less new file mode 100644 index 00000000..2f8eaf45 --- /dev/null +++ b/styles/less/dialog/attribution/index.less @@ -0,0 +1 @@ +@import "./sheet.less"; \ No newline at end of file diff --git a/styles/less/dialog/beastform/index.less b/styles/less/dialog/beastform/index.less new file mode 100644 index 00000000..2f8eaf45 --- /dev/null +++ b/styles/less/dialog/beastform/index.less @@ -0,0 +1 @@ +@import "./sheet.less"; \ No newline at end of file diff --git a/styles/less/dialog/character-creation/index.less b/styles/less/dialog/character-creation/index.less new file mode 100644 index 00000000..adf8d57a --- /dev/null +++ b/styles/less/dialog/character-creation/index.less @@ -0,0 +1,4 @@ +@import "./sheet.less"; +@import "./creation-action-footer.less"; +@import "./selections-container.less"; +@import "./tab-navigation.less"; \ No newline at end of file diff --git a/styles/less/dialog/character-reset/index.less b/styles/less/dialog/character-reset/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/character-reset/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/compendiumBrowserPackDialog/index.less b/styles/less/dialog/compendiumBrowserPackDialog/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/compendiumBrowserPackDialog/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/damage-reduction/index.less b/styles/less/dialog/damage-reduction/index.less new file mode 100644 index 00000000..0b8e94a8 --- /dev/null +++ b/styles/less/dialog/damage-reduction/index.less @@ -0,0 +1,2 @@ +@import './sheets.less'; +@import './damage-reduction-container.less'; \ No newline at end of file diff --git a/styles/less/dialog/damage-selection/index.less b/styles/less/dialog/damage-selection/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/damage-selection/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/death-move/index.less b/styles/less/dialog/death-move/index.less new file mode 100644 index 00000000..8a8a16c4 --- /dev/null +++ b/styles/less/dialog/death-move/index.less @@ -0,0 +1 @@ +@import './death-move-container.less'; \ No newline at end of file diff --git a/styles/less/dialog/dice-roll/index.less b/styles/less/dialog/dice-roll/index.less new file mode 100644 index 00000000..8e0af6e0 --- /dev/null +++ b/styles/less/dialog/dice-roll/index.less @@ -0,0 +1 @@ +@import './roll-selection.less'; \ No newline at end of file diff --git a/styles/less/dialog/downtime/index.less b/styles/less/dialog/downtime/index.less new file mode 100644 index 00000000..09cc2dfe --- /dev/null +++ b/styles/less/dialog/downtime/index.less @@ -0,0 +1 @@ +@import './downtime-container.less'; \ No newline at end of file diff --git a/styles/less/dialog/group-roll-dialog/_common.less b/styles/less/dialog/group-roll-dialog/_common.less deleted file mode 100644 index f74ab8a0..00000000 --- a/styles/less/dialog/group-roll-dialog/_common.less +++ /dev/null @@ -1,44 +0,0 @@ -h1 { - color: @color-text-emphatic; - font: 700 var(--font-size-24) var(--dh-font-subtitle); - text-align: center; -} - -header { - --bar-color: light-dark(@dark-blue, @golden); - color: light-dark(@dark, @beige); - display: flex; - justify-content: center; - align-items: center; - - &:not(:first-child) { - margin-top: var(--spacer-8); - } - - span { - padding: 0 10px; - } - - &:before { - content: ' '; - flex: 1; - height: 1px; - background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, var(--bar-color) 100%); - } - - &:after { - content: ' '; - flex: 1; - height: 1px; - background: linear-gradient(90deg, var(--bar-color) 0%, rgba(0, 0, 0, 0) 100%); - } -} - -img.portrait { - border-radius: 50%; - border: none; - object-fit: cover; - object-position: center top; - width: 2.5rem; - height: 2.5rem; -} diff --git a/styles/less/dialog/group-roll-dialog/index.less b/styles/less/dialog/group-roll-dialog/index.less index 27925fa2..f90b57dc 100644 --- a/styles/less/dialog/group-roll-dialog/index.less +++ b/styles/less/dialog/group-roll-dialog/index.less @@ -1,8 +1,3 @@ -.daggerheart.dialog.dh-style.views.group-roll-dialog { - .window-content { - @import "./_common.less"; - } -} - -@import "./initialization.less"; -@import "./main.less"; +@import './sheet.less'; +@import './initialization.less'; +@import './main.less'; diff --git a/styles/less/dialog/group-roll-dialog/sheet.less b/styles/less/dialog/group-roll-dialog/sheet.less new file mode 100644 index 00000000..938710c9 --- /dev/null +++ b/styles/less/dialog/group-roll-dialog/sheet.less @@ -0,0 +1,48 @@ +.daggerheart.dialog.dh-style.views.group-roll-dialog { + .window-content { + h1 { + color: @color-text-emphatic; + font: 700 var(--font-size-24) var(--dh-font-subtitle); + text-align: center; + } + + header { + --bar-color: light-dark(@dark-blue, @golden); + color: light-dark(@dark, @beige); + display: flex; + justify-content: center; + align-items: center; + + &:not(:first-child) { + margin-top: var(--spacer-8); + } + + span { + padding: 0 10px; + } + + &:before { + content: ' '; + flex: 1; + height: 1px; + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, var(--bar-color) 100%); + } + + &:after { + content: ' '; + flex: 1; + height: 1px; + background: linear-gradient(90deg, var(--bar-color) 0%, rgba(0, 0, 0, 0) 100%); + } + } + + img.portrait { + border-radius: 50%; + border: none; + object-fit: cover; + object-position: center top; + width: 2.5rem; + height: 2.5rem; + } + } +} \ No newline at end of file diff --git a/styles/less/dialog/image-select/index.less b/styles/less/dialog/image-select/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/image-select/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index e8f61318..4ce4834e 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -1,42 +1,20 @@ -@import './attribution/sheet.less'; -@import './level-up/index.less'; - -@import './resource-dice/sheet.less'; - -@import './actions/action-list.less'; - -@import './damage-selection/sheet.less'; - -@import './downtime/downtime-container.less'; - -@import './death-move/death-move-container.less'; - -@import './beastform/sheet.less'; - -@import './character-creation/creation-action-footer.less'; -@import './character-creation/selections-container.less'; -@import './character-creation/sheet.less'; -@import './character-creation/tab-navigation.less'; - -@import './dice-roll/roll-selection.less'; -@import './damage-reduction/damage-reduction-container.less'; -@import './damage-reduction/sheets.less'; - -@import './multiclass-choice/sheet.less'; - -@import './tag-team-dialog/initialization.less'; -@import './tag-team-dialog/sheet.less'; - +@import './actions/index.less'; +@import './attribution/index.less'; +@import './beastform/index.less'; +@import './character-creation/index.less'; +@import './character-reset/index.less'; +@import './compendiumBrowserPackDialog/index.less'; +@import './damage-reduction/index.less'; +@import './damage-selection/index.less'; +@import './death-move/index.less'; +@import './dice-roll/index.less'; +@import './downtime/index.less'; @import './group-roll-dialog/index.less'; - -@import './image-select/sheet.less'; - -@import './item-transfer/sheet.less'; - -@import './settings/change-currency-icon.less'; - -@import './risk-it-all/sheet.less'; - -@import './character-reset/sheet.less'; - -@import './compendiumBrowserPackDialog/sheet.less'; +@import './level-up/index.less'; +@import './resource-dice/index.less'; +@import './multiclass-choice/index.less'; +@import './tag-team-dialog/index.less'; +@import './image-select/index.less'; +@import './item-transfer/index.less'; +@import './settings/index.less'; +@import './risk-it-all/index.less'; \ No newline at end of file diff --git a/styles/less/dialog/item-transfer/index.less b/styles/less/dialog/item-transfer/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/item-transfer/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/multiclass-choice/index.less b/styles/less/dialog/multiclass-choice/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/multiclass-choice/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/resource-dice/index.less b/styles/less/dialog/resource-dice/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/resource-dice/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/risk-it-all/index.less b/styles/less/dialog/risk-it-all/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/dialog/risk-it-all/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/dialog/settings/index.less b/styles/less/dialog/settings/index.less new file mode 100644 index 00000000..235f3b9c --- /dev/null +++ b/styles/less/dialog/settings/index.less @@ -0,0 +1 @@ +@import './change-currency-icon.less'; \ No newline at end of file diff --git a/styles/less/dialog/tag-team-dialog/index.less b/styles/less/dialog/tag-team-dialog/index.less new file mode 100644 index 00000000..8bf56824 --- /dev/null +++ b/styles/less/dialog/tag-team-dialog/index.less @@ -0,0 +1,2 @@ +@import './sheet.less'; +@import './initialization.less'; \ No newline at end of file diff --git a/styles/less/hud/index.less b/styles/less/hud/index.less index 459f8fd7..f1f4602e 100644 --- a/styles/less/hud/index.less +++ b/styles/less/hud/index.less @@ -1 +1 @@ -@import './token-hud/token-hud.less'; +@import './token-hud/index.less'; diff --git a/styles/less/hud/token-hud/index.less b/styles/less/hud/token-hud/index.less new file mode 100644 index 00000000..c86d0939 --- /dev/null +++ b/styles/less/hud/token-hud/index.less @@ -0,0 +1 @@ +@import './token-hud.less'; \ No newline at end of file diff --git a/styles/less/sheets-settings/adversary-settings/index.less b/styles/less/sheets-settings/adversary-settings/index.less new file mode 100644 index 00000000..5968577d --- /dev/null +++ b/styles/less/sheets-settings/adversary-settings/index.less @@ -0,0 +1,3 @@ +@import './sheet.less'; +@import './experiences.less'; +@import './features.less'; \ No newline at end of file diff --git a/styles/less/sheets-settings/character-settings/index.less b/styles/less/sheets-settings/character-settings/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/sheets-settings/character-settings/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/sheets-settings/environment-settings/index.less b/styles/less/sheets-settings/environment-settings/index.less new file mode 100644 index 00000000..1e6ee34d --- /dev/null +++ b/styles/less/sheets-settings/environment-settings/index.less @@ -0,0 +1,2 @@ +@import './adversaries.less'; +@import './features.less'; \ No newline at end of file diff --git a/styles/less/sheets-settings/index.less b/styles/less/sheets-settings/index.less index f575f848..53a03868 100644 --- a/styles/less/sheets-settings/index.less +++ b/styles/less/sheets-settings/index.less @@ -1,8 +1,4 @@ @import './header.less'; -@import './adversary-settings/sheet.less'; -@import './adversary-settings/experiences.less'; -@import './adversary-settings/features.less'; -@import './character-settings/sheet.less'; - -@import './environment-settings/features.less'; -@import './environment-settings/adversaries.less'; +@import './adversary-settings/index.less'; +@import './character-settings/index.less'; +@import './environment-settings/index.less'; diff --git a/styles/less/sheets/actions/index.less b/styles/less/sheets/actions/index.less new file mode 100644 index 00000000..29ef8645 --- /dev/null +++ b/styles/less/sheets/actions/index.less @@ -0,0 +1 @@ +@import './actions.less'; \ No newline at end of file diff --git a/styles/less/sheets/activeEffects/index.less b/styles/less/sheets/activeEffects/index.less new file mode 100644 index 00000000..19f8a3a7 --- /dev/null +++ b/styles/less/sheets/activeEffects/index.less @@ -0,0 +1 @@ +@import './activeEffects.less'; \ No newline at end of file diff --git a/styles/less/sheets/actors/index.less b/styles/less/sheets/actors/index.less new file mode 100644 index 00000000..959bc0f5 --- /dev/null +++ b/styles/less/sheets/actors/index.less @@ -0,0 +1,7 @@ +@import './actor-sheet-shared.less'; +@import './adversary/index.less'; +@import './character/index.less'; +@import './companion/index.less'; +@import './environment/index.less'; +@import './npc/index.less'; +@import './party/index.less'; \ No newline at end of file diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index 4312f755..451ae03a 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -1,22 +1,5 @@ -@import './actions/actions.less'; - -@import './actors/actor-sheet-shared.less'; - -@import './actors/adversary/index.less'; -@import './actors/character/index.less'; -@import './actors/companion/index.less'; -@import './actors/environment/index.less'; -@import './actors/npc/index.less'; -@import './actors/party/index.less'; - -@import './items/beastform.less'; -@import './items/class.less'; -@import './items/domain-card.less'; -@import './items/feature.less'; -@import './items/heritage.less'; -@import './items/item-sheet-shared.less'; - -@import './rollTables/sheet.less'; -@import './actions/actions.less'; - -@import './activeEffects/activeEffects.less'; +@import './activeEffects/index.less'; +@import './actions/index.less'; +@import './actors/index.less'; +@import './items/index.less'; +@import './rollTables/index.less'; \ No newline at end of file diff --git a/styles/less/sheets/items/index.less b/styles/less/sheets/items/index.less new file mode 100644 index 00000000..7c40a2e3 --- /dev/null +++ b/styles/less/sheets/items/index.less @@ -0,0 +1,6 @@ +@import './beastform.less'; +@import './class.less'; +@import './domain-card.less'; +@import './feature.less'; +@import './heritage.less'; +@import './item-sheet-shared.less'; \ No newline at end of file diff --git a/styles/less/sheets/rollTables/index.less b/styles/less/sheets/rollTables/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/sheets/rollTables/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/ui/chat/index.less b/styles/less/ui/chat/index.less new file mode 100644 index 00000000..9cadbd04 --- /dev/null +++ b/styles/less/ui/chat/index.less @@ -0,0 +1,10 @@ +@import './sheet.less'; +@import './ability-use.less'; +@import './action.less'; +@import './chat.less'; +@import './damage-summary.less'; +@import './deathmoves.less'; +@import './downtime.less'; +@import './effect-summary.less'; +@import './group-roll.less'; +@import './refresh-message.less'; \ No newline at end of file diff --git a/styles/less/ui/combat-sidebar/index.less b/styles/less/ui/combat-sidebar/index.less new file mode 100644 index 00000000..786815ef --- /dev/null +++ b/styles/less/ui/combat-sidebar/index.less @@ -0,0 +1,5 @@ +@import './combat-sidebar.less'; +@import './combatant-controls.less'; +@import './encounter-controls.less'; +@import './spotlight-control.less'; +@import './token-actions.less'; \ No newline at end of file diff --git a/styles/less/ui/countdown/index.less b/styles/less/ui/countdown/index.less new file mode 100644 index 00000000..45ecda26 --- /dev/null +++ b/styles/less/ui/countdown/index.less @@ -0,0 +1,3 @@ +@import './sheet.less'; +@import './countdown-edit.less'; +@import './countdown.less'; \ No newline at end of file diff --git a/styles/less/ui/effects-display/index.less b/styles/less/ui/effects-display/index.less new file mode 100644 index 00000000..1c574e81 --- /dev/null +++ b/styles/less/ui/effects-display/index.less @@ -0,0 +1 @@ +@import './sheet.less'; \ No newline at end of file diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 31ea8955..53a71b9b 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -1,40 +1,11 @@ -@import './chat/ability-use.less'; -@import './chat/action.less'; -@import './chat/chat.less'; -@import './chat/damage-summary.less'; -@import './chat/downtime.less'; -@import './chat/effect-summary.less'; -@import './chat/group-roll.less'; -@import './chat/refresh-message.less'; -@import './chat/deathmoves.less'; -@import './chat/sheet.less'; - -@import './combat-sidebar/combat-sidebar.less'; -@import './combat-sidebar/combatant-controls.less'; -@import './combat-sidebar/encounter-controls.less'; -@import './combat-sidebar/spotlight-control.less'; -@import './combat-sidebar/token-actions.less'; -@import './item-browser/item-browser.less'; - -@import './countdown/countdown.less'; -@import './countdown/countdown-edit.less'; -@import './countdown/sheet.less'; - -@import './ownership-selection/ownership-selection.less'; - -@import './resources/resources.less'; - -@import './settings/settings.less'; -@import './settings/homebrew-settings/domains.less'; -@import './settings/homebrew-settings/types.less'; -@import './settings/homebrew-settings/resources.less'; -@import './settings/appearance-settings/diceSoNice.less'; - -@import './sidebar/tabs.less'; -@import './sidebar/daggerheartMenu.less'; - -@import './scene-config/scene-config.less'; - -@import './effects-display/sheet.less'; - -@import './scene-navigation/scene-navigation.less'; +@import './chat/index.less'; +@import './combat-sidebar/index.less'; +@import './countdown/index.less'; +@import './effects-display/index.less'; +@import './item-browser/index.less'; +@import './ownership-selection/index.less'; +@import './resources/index.less'; +@import './scene-config/index.less'; +@import './scene-navigation/index.less'; +@import './settings/index.less'; +@import './sidebar/index.less'; \ No newline at end of file diff --git a/styles/less/ui/item-browser/index.less b/styles/less/ui/item-browser/index.less new file mode 100644 index 00000000..842f716b --- /dev/null +++ b/styles/less/ui/item-browser/index.less @@ -0,0 +1 @@ +@import './item-browser.less'; \ No newline at end of file diff --git a/styles/less/ui/ownership-selection/index.less b/styles/less/ui/ownership-selection/index.less new file mode 100644 index 00000000..9646670a --- /dev/null +++ b/styles/less/ui/ownership-selection/index.less @@ -0,0 +1 @@ +@import './ownership-selection.less'; \ No newline at end of file diff --git a/styles/less/ui/resources/index.less b/styles/less/ui/resources/index.less new file mode 100644 index 00000000..a7d08785 --- /dev/null +++ b/styles/less/ui/resources/index.less @@ -0,0 +1 @@ +@import './resources.less'; \ No newline at end of file diff --git a/styles/less/ui/scene-config/index.less b/styles/less/ui/scene-config/index.less new file mode 100644 index 00000000..4e3af363 --- /dev/null +++ b/styles/less/ui/scene-config/index.less @@ -0,0 +1 @@ +@import './scene-config.less'; \ No newline at end of file diff --git a/styles/less/ui/scene-navigation/index.less b/styles/less/ui/scene-navigation/index.less new file mode 100644 index 00000000..c0765ae7 --- /dev/null +++ b/styles/less/ui/scene-navigation/index.less @@ -0,0 +1 @@ +@import './scene-navigation.less'; \ No newline at end of file diff --git a/styles/less/ui/settings/appearance-settings/index.less b/styles/less/ui/settings/appearance-settings/index.less new file mode 100644 index 00000000..8b1c109a --- /dev/null +++ b/styles/less/ui/settings/appearance-settings/index.less @@ -0,0 +1 @@ +@import './diceSoNice.less'; \ No newline at end of file diff --git a/styles/less/ui/settings/homebrew-settings/index.less b/styles/less/ui/settings/homebrew-settings/index.less new file mode 100644 index 00000000..f0a8bfc1 --- /dev/null +++ b/styles/less/ui/settings/homebrew-settings/index.less @@ -0,0 +1,3 @@ +@import './domains.less'; +@import './resources.less'; +@import './types.less'; \ No newline at end of file diff --git a/styles/less/ui/settings/index.less b/styles/less/ui/settings/index.less new file mode 100644 index 00000000..4e1aa798 --- /dev/null +++ b/styles/less/ui/settings/index.less @@ -0,0 +1,3 @@ +@import './settings.less'; +@import './appearance-settings/index.less'; +@import './homebrew-settings/index.less'; \ No newline at end of file diff --git a/styles/less/ui/sidebar/index.less b/styles/less/ui/sidebar/index.less new file mode 100644 index 00000000..b4961b41 --- /dev/null +++ b/styles/less/ui/sidebar/index.less @@ -0,0 +1,2 @@ +@import './daggerheartMenu.less'; +@import './tabs.less'; \ No newline at end of file diff --git a/styles/less/utils/index.less b/styles/less/utils/index.less new file mode 100644 index 00000000..37b096d3 --- /dev/null +++ b/styles/less/utils/index.less @@ -0,0 +1,4 @@ +@import './colors.less'; +@import './fonts.less'; +@import './mixin.less'; +@import './spacing.less'; \ No newline at end of file diff --git a/styles/less/ux/autocomplete/index.less b/styles/less/ux/autocomplete/index.less new file mode 100644 index 00000000..91007146 --- /dev/null +++ b/styles/less/ux/autocomplete/index.less @@ -0,0 +1 @@ +@import './autocomplete.less'; \ No newline at end of file diff --git a/styles/less/ux/index.less b/styles/less/ux/index.less index a73f2d1c..b6c9a2e7 100644 --- a/styles/less/ux/index.less +++ b/styles/less/ux/index.less @@ -1,10 +1,2 @@ -@import './tooltip/sheet.less'; -@import './tooltip/tooltip.less'; -@import './tooltip/armorManagement.less'; -@import './tooltip/battlepoints.less'; -@import './tooltip/bordered-tooltip.less'; -@import './tooltip/domain-cards.less'; - -@import './autocomplete/autocomplete.less'; - -@import './tooltip/resource-management.less'; +@import './autocomplete/index.less'; +@import './tooltip/index.less'; \ No newline at end of file diff --git a/styles/less/ux/tooltip/index.less b/styles/less/ux/tooltip/index.less new file mode 100644 index 00000000..eeec9354 --- /dev/null +++ b/styles/less/ux/tooltip/index.less @@ -0,0 +1,7 @@ +@import './sheet.less'; +@import './armorManagement.less'; +@import './battlepoints.less'; +@import './bordered-tooltip.less'; +@import './domain-cards.less'; +@import './resource-management.less'; +@import './tooltip.less'; \ No newline at end of file From a4428fd5be62fab57f5b32f3901b9bfe0d6807ac Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 15:53:15 -0400 Subject: [PATCH 304/304] Replace prettier with stylistic, improve types, and add no-undef rule (#1975) --- .editorconfig | 1 + .prettierrc | 13 - daggerheart.d.ts | 24 +- daggerheart.mjs | 4 +- eslint.config.mjs | 97 ++- jsconfig.json | 4 +- .../characterCreation/characterCreation.mjs | 12 +- module/applications/dialogs/d20RollDialog.mjs | 20 +- .../dialogs/damageReductionDialog.mjs | 8 +- module/applications/dialogs/downtime.mjs | 4 +- .../applications/dialogs/groupRollDialog.mjs | 4 +- module/applications/dialogs/tagTeamDialog.mjs | 4 +- module/applications/hud/tokenHUD.mjs | 32 +- .../applications/levelup/characterLevelup.mjs | 24 +- .../applications/levelup/companionLevelup.mjs | 18 +- module/applications/levelup/levelup.mjs | 248 +------- .../settings/homebrewSettings.mjs | 4 +- .../sheets-configs/setting-feature-config.mjs | 10 +- .../applications/sheets/actors/character.mjs | 10 +- module/applications/sheets/actors/party.mjs | 8 +- .../sheets/api/application-mixin.mjs | 8 +- module/applications/sheets/api/base-actor.mjs | 4 +- .../sheets/api/item-attachment-sheet.mjs | 10 - .../sidebar/tabs/actorDirectory.mjs | 4 +- module/applications/ui/chatLog.mjs | 8 +- module/applications/ui/countdownEdit.mjs | 14 +- module/applications/ui/countdowns.mjs | 18 +- module/applications/ui/effectsDisplay.mjs | 8 +- module/canvas/placeables/token.mjs | 12 +- module/config/itemBrowserConfig.mjs | 4 +- module/data/action/attackAction.mjs | 4 +- module/data/action/baseAction.mjs | 18 +- module/data/activeEffect/beastformEffect.mjs | 4 +- .../data/activeEffect/changeTypes/armor.mjs | 8 +- module/data/actor/character.mjs | 46 +- module/data/companionLevelup.mjs | 18 +- module/data/countdowns.mjs | 14 +- module/data/fields/action/beastformField.mjs | 4 +- module/data/fields/action/costField.mjs | 8 +- module/data/fields/action/countdownField.mjs | 10 +- module/data/fields/action/effectsField.mjs | 10 +- module/data/fields/action/saveField.mjs | 14 +- module/data/item/beastform.mjs | 4 +- module/data/levelup.mjs | 18 +- module/dice/dhRoll.mjs | 6 +- module/dice/dualityRoll.mjs | 12 +- module/documents/activeEffect.mjs | 4 +- module/documents/chatMessage.mjs | 22 +- module/documents/item.mjs | 4 +- module/documents/token.mjs | 12 +- module/enrichers/DualityRollEnricher.mjs | 12 +- module/enrichers/TemplateEnricher.mjs | 4 +- module/helpers/utils.mjs | 14 +- module/systemRegistration/migrations.mjs | 4 +- package-lock.json | 551 ++++++++++++++---- package.json | 8 +- tools/analyze-damage.mjs | 6 +- tools/create-symlink.mjs | 2 + tools/eslint.config.mjs | 20 + 59 files changed, 886 insertions(+), 614 deletions(-) delete mode 100644 .prettierrc create mode 100644 tools/eslint.config.mjs diff --git a/.editorconfig b/.editorconfig index 6cfef2fc..aa391e00 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,6 @@ [*] indent_size = 4 indent_style = spaces +end_of_line = lf [*.yml] indent_size = 2 diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 6de9e5d0..00000000 --- a/.prettierrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "trailingComma": "none", - "tabWidth": 4, - "useTabs": false, - "semi": true, - "singleQuote": true, - "quoteProps": "consistent", - "bracketSpacing": true, - "arrowParens": "avoid", - "printWidth": 120, - "endOfLine": "lf", - "bracketSameLine": true -} diff --git a/daggerheart.d.ts b/daggerheart.d.ts index ab754b17..7ff7fd59 100644 --- a/daggerheart.d.ts +++ b/daggerheart.d.ts @@ -1,8 +1,11 @@ import '@client/global.mjs'; +import '@common/global.mjs'; +import '@common/primitives/global.mjs'; import Canvas from '@client/canvas/board.mjs'; // Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such // This declare global hopefully fixes that +// Note: eslint is not aware of these, whatever is added here should go in the eslint's globals list declare global { /** * A simple event framework used throughout Foundry Virtual Tabletop. @@ -12,9 +15,28 @@ declare global { class Hooks extends foundry.helpers.Hooks {} const fromUuid = foundry.utils.fromUuid; const fromUuidSync = foundry.utils.fromUuidSync; - + /** + * A representation of a color in hexadecimal format. + * This class provides methods for transformations and manipulations of colors. + */ + class Color extends foundry.utils.Color {} /** * The singleton game canvas */ const canvas: Canvas; + + const ActiveEffect: foundry.documents.ActiveEffect; + const Actor: foundry.documents.Actor; + const BaseScene: foundry.documents.BaseScene; + const ChatMessage: foundry.documents.ChatMessage; + const Combat: foundry.documents.Combat; + const Combatant: foundry.documents.Combatant; + const Item: foundry.documents.Item; + const Macro: foundry.documents.Macro; + const Scene: foundry.documents.Scene; + const TokenDocument: foundry.documents.TokenDocument; + + const Collection: foundry.utils.Collection; + const FormDataExtended: foundry.applications.ux.FormDataExtended; + const TextEditor: foundry.applications.ux.TextEditor; } diff --git a/daggerheart.mjs b/daggerheart.mjs index 25c41ced..7bfdf874 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -453,8 +453,8 @@ Hooks.on('renderDialogV2', (_dialog, html) => { const cls = html.classList.contains('item-create') ? documents.DHItem.implementation : html.classList.contains('actor-create') - ? documents.DhpActor.implementation - : null; + ? documents.DhpActor.implementation + : null; if (!cls) return; const form = html.querySelector('form'); diff --git a/eslint.config.mjs b/eslint.config.mjs index ce2bb86f..3c9b8fd9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,14 +1,101 @@ import globals from 'globals'; -import { defineConfig } from 'eslint/config'; -import prettier from 'eslint-plugin-prettier'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import tseslint from 'typescript-eslint'; +import js from '@eslint/js'; +import stylistic from '@stylistic/eslint-plugin'; + +/** @type {Partial} */ +export const stylisticRules = { + '@stylistic/indent': [ + 'error', + 4, + { + SwitchCase: 1 + } + ], + '@stylistic/max-len': ['error', { + code: 120, + ignoreComments: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true + }], + '@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }], + '@stylistic/arrow-parens': ['error', 'as-needed'], + '@stylistic/quote-props': ['error', 'as-needed'], + '@stylistic/array-bracket-newline': ['error', 'consistent'], + '@stylistic/key-spacing': 'error', + '@stylistic/comma-dangle': ['error', 'never'], + '@stylistic/space-in-parens': ['error', 'never'], + '@stylistic/space-infix-ops': 2, + '@stylistic/keyword-spacing': 2, + '@stylistic/semi-spacing': 2, + '@stylistic/no-multi-spaces': 2, + '@stylistic/no-extra-semi': 2, + '@stylistic/no-whitespace-before-property': 2, + '@stylistic/space-unary-ops': 2 +}; export default defineConfig([ - { files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: globals.browser } }, - { plugins: { prettier } }, + globalIgnores(['foundry/**/*', 'build/**/*']), + { + files: ['gulpfile.js', 'postcss.config.js'], + languageOptions: { globals: globals.node } + }, { files: ['**/*.{js,mjs,cjs}'], + plugins: { + '@stylistic': stylistic + }, + languageOptions: { + globals: { + ...globals.browser, + CONFIG: 'readonly', + CONST: 'readonly', + // Global classes + Color: 'readonly', + Handlebars: 'readonly', + Hooks: 'readonly', + PIXI: 'readonly', + ProseMirror: 'readonly', + Roll: 'readonly', + // global namespaces + canvas: 'readonly', + foundry: 'readonly', + game: 'readonly', + ui: 'readonly', + // global functions + fromUuid: 'readonly', + fromUuidSync: 'readonly', + getDocumentClass: 'readonly', + _del: 'readonly', + _replace: 'readonly', + _loc: 'readonly', + // Documents + ActiveEffect: 'readonly', + Actor: 'readonly', + BaseScene: 'readonly', + ChatMessage: 'readonly', + Combat: 'readonly', + Combatant: 'readonly', + Item: 'readonly', + Macro: 'readonly', + Scene: 'readonly', + TokenDocument: 'readonly', + // Other + Collection: 'readonly', + FormDataExtended: 'readonly', + TextEditor: 'readonly' + } + }, rules: { - 'prettier/prettier': 'error' + 'no-undef': 'error', + // 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + ...stylisticRules } + }, + { + files: ['**/*.ts'], + extends: [js.configs.recommended, tseslint.configs.recommended] } ]); diff --git a/jsconfig.json b/jsconfig.json index 00bab1f5..a0d51d0b 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "module": "ES6", - "target": "ES6", + "module": "es2022", + "target": "es2022", "paths": { "@client/*": ["./foundry/client/*"], "@common/*": ["./foundry/common/*"] diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index 82ca9ccb..517f95da 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -154,8 +154,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : this.tabGroups.primary !== 'equipment' - ? v.active - : false; + ? v.active + : false; v.cssClass = v.active ? 'active' : ''; switch (v.id) { @@ -211,9 +211,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl context.suggestedTraits = this.setup.class.system ? Object.keys(this.setup.class.system.characterGuide.suggestedTraits).map(traitKey => { - const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey]; - return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`; - }) + const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey]; + return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`; + }) : []; context.traits = { values: Object.keys(this.setup.traits).map(traitKey => { @@ -450,7 +450,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl if (equipment.includes(type)) presets.filter = { 'system.tier': { key: 'system.tier', value: 1 }, - 'type': { key: 'type', value: type } + type: { key: 'type', value: type } }; ui.compendiumBrowser.open(presets); diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 76b2e751..9a98b197 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -196,14 +196,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1 ? this.config.costs.filter(x => x.extKey !== button.dataset.key) : [ - ...this.config.costs, - { - extKey: button.dataset.key, - key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope', - value: 1, - name: this.config.data?.system.experiences?.[button.dataset.key]?.name - } - ]; + ...this.config.costs, + { + extKey: button.dataset.key, + key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope', + value: 1, + name: this.config.data?.system.experiences?.[button.dataset.key]?.name + } + ]; this.render(); } @@ -213,8 +213,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.config.actionType = this.reactionOverride ? 'reaction' : this.config.actionType === 'reaction' - ? 'action' - : this.config.actionType; + ? 'action' + : this.config.actionType; this.render(); } } diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index b916a5de..e5108e34 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -138,13 +138,13 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap const stressReductionStress = this.availableStressReductions ? stressReductions.reduce((acc, red) => acc + red.cost, 0) : 0; + const stress = this.actor.system.resources.stress; context.stress = selectedStressMarks.length > 0 || this.availableStressReductions ? { - value: - this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress, - max: this.actor.system.resources.stress.max - } + value: stress.value + selectedStressMarks.length + stressReductionStress, + max: stress.max + } : null; context.maxArmorUsed = maxArmorUsed; diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 367540bf..e209cc3b 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -259,10 +259,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV const resetValue = increasing ? 0 : feature.system.resource.max - ? new Roll( + ? new Roll( Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData()) ).evaluateSync().total - : 0; + : 0; await feature.update({ 'system.resource.value': resetValue }); } diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index dd504b4b..7196d848 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -167,8 +167,8 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat partContext.groupRoll = { totalLabel: leader?.rollData ? game.i18n.format('DAGGERHEART.GENERAL.withThing', { - thing: leader.roll.totalLabel - }) + thing: leader.roll.totalLabel + }) : null, totalDualityClass: leader?.roll?.isCritical ? 'critical' : leader?.roll?.withHope ? 'hope' : 'fear', total: leaderTotal + modifierTotal, diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 3dc6b0fc..b2ce0258 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -653,8 +653,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const baseSecondaryRoll = selectedRoll ? memberValues.find(x => !x.selected) : memberValues.length > 1 - ? memberValues[1] - : null; + ? memberValues[1] + : null; if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null; diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs index 671b01a1..4805cd9e 100644 --- a/module/applications/hud/tokenHUD.mjs +++ b/module/applications/hud/tokenHUD.mjs @@ -50,11 +50,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { ).showGenericStatusEffects; context.genericStatusEffects = useGeneric ? Object.keys(context.statusEffects).reduce((acc, key) => { - const effect = context.statusEffects[key]; - if (!effect.systemEffect) acc[key] = effect; + const effect = context.statusEffects[key]; + if (!effect.systemEffect) acc[key] = effect; - return acc; - }, {}) + return acc; + }, {}) : null; context.hasCompanion = this.actor.system.companion; @@ -68,11 +68,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { const warning = tokensWithoutActors.length === 1 ? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', { - name: tokensWithoutActors[0].name - }) + name: tokensWithoutActors[0].name + }) : game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', { - names: tokensWithoutActors.map(x => x.name).join(', ') - }); + names: tokensWithoutActors.map(x => x.name).join(', ') + }); const tokens = canvas.tokens.controlled .filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type)) @@ -174,8 +174,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { nonZeroIndex === sideMiddle ? 0 : nonZeroIndex < sideMiddle - ? -nonZeroIndex - : nonZeroIndex - sideMiddle; + ? -nonZeroIndex + : nonZeroIndex - sideMiddle; return { x: actorX - sizeX * distance, y: actorY - sizeY * distanceCoefficient }; } else if (index < side + inbetween) { const inbetweenIndex = nonZeroIndex - side; @@ -183,8 +183,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { inbetweenIndex === inbetweenMiddle ? 0 : inbetweenIndex < inbetweenMiddle - ? -inbetweenIndex - : inbetweenIndex - inbetweenMiddle; + ? -inbetweenIndex + : inbetweenIndex - inbetweenMiddle; return { x: actorX + sizeX * distanceCoefficient, y: actorY + sizeY * distance }; } else if (index < 2 * side + inbetween) { const sideIndex = nonZeroIndex - side - inbetween; @@ -192,8 +192,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { sideIndex === sideMiddle ? 0 : sideIndex < sideMiddle - ? sideIndex - : -(sideIndex - sideMiddle); + ? sideIndex + : -(sideIndex - sideMiddle); return { x: actorX + sizeX * distance, y: actorY + sizeY * distanceCoefficient }; } else { const inbetweenIndex = nonZeroIndex - 2 * side - inbetween; @@ -201,8 +201,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { inbetweenIndex === inbetweenMiddle ? 0 : inbetweenIndex < inbetweenMiddle - ? inbetweenIndex - : -(inbetweenIndex - inbetweenMiddle); + ? inbetweenIndex + : -(inbetweenIndex - inbetweenMiddle); return { x: actorX - sizeX * distanceCoefficient, y: actorY + sizeY * distance }; } }) diff --git a/module/applications/levelup/characterLevelup.mjs b/module/applications/levelup/characterLevelup.mjs index e8d6cf1c..a2df63c1 100644 --- a/module/applications/levelup/characterLevelup.mjs +++ b/module/applications/levelup/characterLevelup.mjs @@ -210,9 +210,9 @@ export default class DhCharacterLevelUp extends LevelUpBase { achievementExperiences = level.achievements.experiences ? Object.values(level.achievements.experiences).reduce((acc, experience) => { - if (experience.name) acc.push(experience); - return acc; - }, []) + if (experience.name) acc.push(experience); + return acc; + }, []) : []; } @@ -315,15 +315,15 @@ export default class DhCharacterLevelUp extends LevelUpBase { : null; advancement[choiceKey] = multiclassItem ? { - ...multiclassItem.toObject(), - domain: checkbox.secondaryData.domain - ? game.i18n.localize( - CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain] - .label - ) - : null, - subclass: subclass ? subclass.name : null - } + ...multiclassItem.toObject(), + domain: checkbox.secondaryData.domain + ? game.i18n.localize( + CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain] + .label + ) + : null, + subclass: subclass ? subclass.name : null + } : {}; break; } diff --git a/module/applications/levelup/companionLevelup.mjs b/module/applications/levelup/companionLevelup.mjs index d6bf2d78..92cf3050 100644 --- a/module/applications/levelup/companionLevelup.mjs +++ b/module/applications/levelup/companionLevelup.mjs @@ -77,9 +77,9 @@ export default class DhCompanionLevelUp extends BaseLevelUp { achievementExperiences = level.achievements.experiences ? Object.values(level.achievements.experiences).reduce((acc, experience) => { - if (experience.name) acc.push(experience); - return acc; - }, []) + if (experience.name) acc.push(experience); + return acc; + }, []) : []; } context.achievements = { @@ -155,15 +155,15 @@ export default class DhCompanionLevelUp extends BaseLevelUp { vicious: { damage: advancement.vicious?.damage ? { - old: actorDamageDice, - new: advancement.vicious.damage - } + old: actorDamageDice, + new: advancement.vicious.damage + } : null, range: advancement.vicious?.range ? { - old: game.i18n.localize(`DAGGERHEART.CONFIG.Range.${actorRange}.name`), - new: game.i18n.localize(advancement.vicious.range.label) - } + old: game.i18n.localize(`DAGGERHEART.CONFIG.Range.${actorRange}.name`), + new: game.i18n.localize(advancement.vicious.range.label) + } : null }, simple: advancement.simple ?? {} diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index 03638548..cafc5c89 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -135,192 +135,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections }; context.showTabs = this.tabGroups.primary !== 'summary'; break; - - const actorArmor = this.actor.system.armor; - const levelKeys = Object.keys(this.levelup.levels); - let achivementProficiency = 0; - const achievementCards = []; - let achievementExperiences = []; - for (var levelKey of levelKeys) { - const level = this.levelup.levels[levelKey]; - if (Number(levelKey) < this.levelup.startLevel) continue; - - achivementProficiency += level.achievements.proficiency ?? 0; - const cards = level.achievements.domainCards ? Object.values(level.achievements.domainCards) : null; - if (cards) { - for (var card of cards) { - const itemCard = await foundry.utils.fromUuid(card.uuid); - achievementCards.push(itemCard); - } - } - - achievementExperiences = level.achievements.experiences - ? Object.values(level.achievements.experiences).reduce((acc, experience) => { - if (experience.name) acc.push(experience); - return acc; - }, []) - : []; - } - - context.achievements = { - proficiency: { - old: this.actor.system.proficiency, - new: this.actor.system.proficiency + achivementProficiency, - shown: achivementProficiency > 0 - }, - damageThresholds: { - major: { - old: this.actor.system.damageThresholds.major, - new: this.actor.system.damageThresholds.major + changedActorLevel - currentActorLevel - }, - severe: { - old: this.actor.system.damageThresholds.severe, - new: - this.actor.system.damageThresholds.severe + - (actorArmor - ? changedActorLevel - currentActorLevel - : (changedActorLevel - currentActorLevel) * 2) - }, - unarmored: !actorArmor - }, - domainCards: { - values: achievementCards, - shown: achievementCards.length > 0 - }, - experiences: { - values: achievementExperiences - } - }; - - const advancement = {}; - for (var levelKey of levelKeys) { - const level = this.levelup.levels[levelKey]; - if (Number(levelKey) < this.levelup.startLevel) continue; - - for (var choiceKey of Object.keys(level.choices)) { - const choice = level.choices[choiceKey]; - for (var checkbox of Object.values(choice)) { - switch (choiceKey) { - case 'proficiency': - case 'hitPoint': - case 'stress': - case 'evasion': - advancement[choiceKey] = advancement[choiceKey] - ? advancement[choiceKey] + Number(checkbox.value) - : Number(checkbox.value); - break; - case 'trait': - if (!advancement[choiceKey]) advancement[choiceKey] = {}; - for (var traitKey of checkbox.data) { - if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0; - advancement[choiceKey][traitKey] += 1; - } - break; - case 'domainCard': - if (!advancement[choiceKey]) advancement[choiceKey] = []; - if (checkbox.data.length === 1) { - const choiceItem = await foundry.utils.fromUuid(checkbox.data[0]); - advancement[choiceKey].push(choiceItem.toObject()); - } - break; - case 'experience': - if (!advancement[choiceKey]) advancement[choiceKey] = []; - const data = checkbox.data.map(data => { - const experience = Object.keys(this.actor.system.experiences).find( - x => x === data - ); - return this.actor.system.experiences[experience]?.description ?? ''; - }); - advancement[choiceKey].push({ data: data, value: checkbox.value }); - break; - case 'subclass': - if (checkbox.data[0]) { - const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]); - if (!advancement[choiceKey]) advancement[choiceKey] = []; - advancement[choiceKey].push({ - ...subclassItem.toObject(), - featureLabel: game.i18n.localize( - subclassFeatureLabels[Number(checkbox.secondaryData.featureState)] - ) - }); - } - break; - case 'multiclass': - const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]); - const subclass = multiclassItem - ? await foundry.utils.fromUuid(checkbox.secondaryData.subclass) - : null; - advancement[choiceKey] = multiclassItem - ? { - ...multiclassItem.toObject(), - domain: checkbox.secondaryData.domain - ? game.i18n.localize( - CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain] - .label - ) - : null, - subclass: subclass ? subclass.name : null - } - : {}; - break; - } - } - } - } - - context.advancements = { - statistics: { - proficiency: { - old: context.achievements.proficiency.new, - new: context.achievements.proficiency.new + (advancement.proficiency ?? 0) - }, - hitPoints: { - old: this.actor.system.resources.hitPoints.max, - new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0) - }, - stress: { - old: this.actor.system.resources.stress.max, - new: this.actor.system.resources.stress.max + (advancement.stress ?? 0) - }, - evasion: { - old: this.actor.system.evasion, - new: this.actor.system.evasion + (advancement.evasion ?? 0) - } - }, - traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => { - if (advancement.trait?.[traitKey]) { - if (!acc) acc = {}; - acc[traitKey] = { - label: game.i18n.localize(abilities[traitKey].label), - old: this.actor.system.traits[traitKey].value, - new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey] - }; - } - return acc; - }, null), - domainCards: advancement.domainCard ?? [], - experiences: - advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ?? - [], - multiclass: advancement.multiclass, - subclass: advancement.subclass - }; - - context.advancements.statistics.proficiency.shown = - context.advancements.statistics.proficiency.new > context.advancements.statistics.proficiency.old; - context.advancements.statistics.hitPoints.shown = - context.advancements.statistics.hitPoints.new > context.advancements.statistics.hitPoints.old; - context.advancements.statistics.stress.shown = - context.advancements.statistics.stress.new > context.advancements.statistics.stress.old; - context.advancements.statistics.evasion.shown = - context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old; - context.advancements.statistics.shown = - context.advancements.statistics.proficiency.shown || - context.advancements.statistics.hitPoints.shown || - context.advancements.statistics.stress.shown || - context.advancements.statistics.evasion.shown; - - break; } return context; @@ -384,37 +198,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) this._dragDrop.forEach(d => d.bind(htmlElement)); } - tagifyUpdate = - type => - async (_, { option, removed }) => { - const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce( - (acc, choiceKey) => { - const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey]; - Object.keys(choice).forEach(checkboxNr => { - const checkbox = choice[checkboxNr]; - if ( - choiceKey === type && - (removed ? checkbox.data.includes(option) : checkbox.data.length < checkbox.amount) - ) { - acc = `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}.data`; - } - }); + tagifyUpdate = type => async (_, { option, removed }) => { + const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce( + (acc, choiceKey) => { + const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey]; + Object.keys(choice).forEach(checkboxNr => { + const checkbox = choice[checkboxNr]; + if ( + choiceKey === type && + (removed ? checkbox.data.includes(option) : checkbox.data.length < checkbox.amount) + ) { + acc = `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}.data`; + } + }); - return acc; - }, - null - ); + return acc; + }, + null + ); - if (!updatePath) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectionsLeft')); - return; - } + if (!updatePath) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectionsLeft')); + return; + } - const currentData = foundry.utils.getProperty(this.levelup, updatePath); - const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option]; - await this.levelup.updateSource({ [updatePath]: updatedData }); - this.render(); - }; + const currentData = foundry.utils.getProperty(this.levelup, updatePath); + const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option]; + await this.levelup.updateSource({ [updatePath]: updatedData }); + this.render(); + }; static async updateForm(event, _, formData) { const { levelup } = foundry.utils.expandObject(formData.object); @@ -593,10 +405,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const domainCards = this.levelup.levels[this.levelup.currentLevel].achievements.domainCards; const illegalDomainCards = option.secondaryData.domain ? Object.keys(domainCards) - .map(key => ({ ...domainCards[key], key })) - .filter( - x => x.uuid && foundry.utils.fromUuidSync(x.uuid).system.domain === option.secondaryData.domain - ) + .map(key => ({ ...domainCards[key], key })) + .filter( + x => x.uuid && foundry.utils.fromUuidSync(x.uuid).system.domain === option.secondaryData.domain + ) : []; illegalDomainCards.forEach(card => { update[`levels.${this.levelup.currentLevel}.achievements.domainCards.${card.key}.uuid`] = null; diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 09bb00f2..c4dfc397 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -251,8 +251,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli const configTitle = isDowntime ? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMove') : type === 'armorFeatures' - ? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.armorFeature') - : game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.weaponFeature'); + ? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.armorFeature') + : game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.weaponFeature'); const editedBase = await game.system.api.applications.sheetConfigs.SettingFeatureConfig.configure( configTitle, diff --git a/module/applications/sheets-configs/setting-feature-config.mjs b/module/applications/sheets-configs/setting-feature-config.mjs index a5bcc4f9..531158cd 100644 --- a/module/applications/sheets-configs/setting-feature-config.mjs +++ b/module/applications/sheets-configs/setting-feature-config.mjs @@ -168,8 +168,8 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App updatedEffects = deleteEffect ? currentEffects.filter(x => x.id !== effectData.id) : existingEffectIndex === -1 - ? [...currentEffects, effectData] - : currentEffects.with(existingEffectIndex, effectData); + ? [...currentEffects, effectData] + : currentEffects.with(existingEffectIndex, effectData); await this.updateMove({ [`${this.movePath}.effects`]: updatedEffects }); @@ -235,9 +235,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App return this.hasEffects ? tabs : Object.keys(tabs).reduce((acc, key) => { - if (key !== 'effects') acc[key] = tabs[key]; - return acc; - }, {}); + if (key !== 'effects') acc[key] = tabs[key]; + return acc; + }, {}); } /** @override */ diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 5d0e7144..bc2cdb41 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -785,11 +785,11 @@ export default class CharacterSheet extends DHBaseActorSheet { filter: key === 'subclasses' ? { - 'system.linkedClass.uuid': { - key: 'system.linkedClass.uuid', - value: this.document.system.class.value?._stats.compendiumSource - } - } + 'system.linkedClass.uuid': { + key: 'system.linkedClass.uuid', + value: this.document.system.class.value?._stats.compendiumSource + } + } : undefined, render: { noFolder: true diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 927a8810..3af8ea5f 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -162,9 +162,9 @@ export default class Party extends DHBaseActorSheet { difficulty: actor.system.difficulty, traits: actor.system.traits ? traits.map(t => ({ - label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`), - value: actor.system.traits[t].value - })) + label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`), + value: actor.system.traits[t].value + })) : null, weapons }); @@ -306,7 +306,7 @@ export default class Party extends DHBaseActorSheet { static async downtimeMoveQuery({ actorId, downtimeType }) { const actor = await foundry.utils.fromUuid(actorId); - if (!actor || !actor?.isOwner) reject(); + if (!actor || !actor?.isOwner) return; new game.system.api.applications.dialogs.Downtime(actor, downtimeType === 'shortRest').render({ force: true }); diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 2b0c3e55..63bbb536 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -722,10 +722,10 @@ export default function DHApplicationMixin(Base) { const parent = featureOnCharacter ? this.document.parent : parentIsItem && documentClass === 'Item' - ? type === 'action' - ? this.document.system - : null - : this.document; + ? type === 'action' + ? this.document.system + : null + : this.document; let systemData = {}; if (featureOnCharacter) { diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 7b820822..812ad311 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -377,7 +377,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { action: 'update', documentName: 'Item', parent: targetActor, - updates: [{ '_id': existing.id, 'system.quantity': existing.system.quantity + quantity }] + updates: [{ _id: existing.id, 'system.quantity': existing.system.quantity + quantity }] }); } else { const itemsToCreate = []; @@ -410,7 +410,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { action: 'update', documentName: 'Item', parent: originActor, - updates: [{ '_id': item.id, 'system.quantity': item.system.quantity - quantity }] + updates: [{ _id: item.id, 'system.quantity': item.system.quantity - quantity }] }); } diff --git a/module/applications/sheets/api/item-attachment-sheet.mjs b/module/applications/sheets/api/item-attachment-sheet.mjs index bcf2fc3c..ea4d5352 100644 --- a/module/applications/sheets/api/item-attachment-sheet.mjs +++ b/module/applications/sheets/api/item-attachment-sheet.mjs @@ -29,16 +29,6 @@ export default function ItemAttachmentSheet(Base) { } }; - async _preparePartContext(partId, context) { - await super._preparePartContext(partId, context); - - if (partId === 'attachments') { - context.attachedItems = await prepareAttachmentContext(this.document); - } - - return context; - } - async _onDrop(event) { const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); diff --git a/module/applications/sidebar/tabs/actorDirectory.mjs b/module/applications/sidebar/tabs/actorDirectory.mjs index 89da1426..a6f48b54 100644 --- a/module/applications/sidebar/tabs/actorDirectory.mjs +++ b/module/applications/sidebar/tabs/actorDirectory.mjs @@ -13,8 +13,8 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs. return document.type === 'adversary' ? game.i18n.localize(adversaryTypes[document.system.type]?.label ?? 'TYPES.Actor.adversary') : document.type === 'environment' - ? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment') - : null; + ? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment') + : null; }; } diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 7036a5df..199ee87d 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -41,8 +41,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const advantage = rollCommand.advantage ? CONFIG.DH.ACTIONS.advantageState.advantage.value : rollCommand.disadvantage - ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value - : undefined; + ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value + : undefined; const difficulty = rollCommand.difficulty; const grantResources = rollCommand.grantResources; @@ -50,8 +50,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const title = (flavor ?? traitValue) ? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label) - }) + ability: game.i18n.localize(CONFIG.DH.ACTOR.abilities[traitValue].label) + }) : game.i18n.localize('DAGGERHEART.GENERAL.duality'); enrichedDualityRoll({ diff --git a/module/applications/ui/countdownEdit.mjs b/module/applications/ui/countdownEdit.mjs index b418107c..5974e1f2 100644 --- a/module/applications/ui/countdownEdit.mjs +++ b/module/applications/ui/countdownEdit.mjs @@ -56,8 +56,8 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio ? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id ? 'DAGGERHEART.UI.Countdowns.increasingLoop' : countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id - ? 'DAGGERHEART.UI.Countdowns.decreasingLoop' - : 'DAGGERHEART.UI.Countdowns.loop' + ? 'DAGGERHEART.UI.Countdowns.decreasingLoop' + : 'DAGGERHEART.UI.Countdowns.loop' : null; const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic; acc[key] = { @@ -148,11 +148,11 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio } async gmSetSetting(data) { - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data), - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.Countdown } - }); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.Countdown } + }); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); } diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 6fa05e29..2a2a113e 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -101,8 +101,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application ? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id ? 'DAGGERHEART.UI.Countdowns.increasingLoop' : countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id - ? 'DAGGERHEART.UI.Countdowns.decreasingLoop' - : 'DAGGERHEART.UI.Countdowns.loop' + ? 'DAGGERHEART.UI.Countdowns.decreasingLoop' + : 'DAGGERHEART.UI.Countdowns.loop' : null; const loopDisabled = !countdownEditable || @@ -181,8 +181,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id ? Number(progressMax) + 1 : countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id - ? Math.max(Number(progressMax) - 1, 0) - : progressMax; + ? Math.max(Number(progressMax) - 1, 0) + : progressMax; await waitForDiceSoNice(message); await settings.updateSource({ @@ -212,11 +212,11 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application } static async gmSetSetting(data) { - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data), - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.Countdown } - }); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.Countdown } + }); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); } diff --git a/module/applications/ui/effectsDisplay.mjs b/module/applications/ui/effectsDisplay.mjs index 035041e1..a64b1b22 100644 --- a/module/applications/ui/effectsDisplay.mjs +++ b/module/applications/ui/effectsDisplay.mjs @@ -67,10 +67,10 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica const actor = token ? token.actor : canvas.tokens.controlled.length === 0 - ? !game.user.isGM - ? game.user.character - : null - : canvas.tokens.controlled[0].actor; + ? !game.user.isGM + ? game.user.character + : null + : canvas.tokens.controlled[0].actor; return getIconVisibleActiveEffects(actor?.getActiveEffects() ?? []); }; diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 68e325c2..02eed7db 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -155,15 +155,15 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint); const adjustedOriginPoint = originEdge ? canvas.grid.getTopLeftPoint({ - x: originEdge.x + Math.sign(originPoint.x - originEdge.x), - y: originEdge.y + Math.sign(originPoint.y - originEdge.y) - }) + x: originEdge.x + Math.sign(originPoint.x - originEdge.x), + y: originEdge.y + Math.sign(originPoint.y - originEdge.y) + }) : originPoint; const adjustDestinationPoint = targetEdge ? canvas.grid.getTopLeftPoint({ - x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x), - y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y) - }) + x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x), + y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y) + }) : targetPoint; const distance = canvas.grid.measurePath([ { ...adjustedOriginPoint, elevation: 0 }, diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index ae5fa71b..83572dc0 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -127,8 +127,8 @@ export const typeConfig = { isSecondary ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false - ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' - : '-' + ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' + : '-' }, { key: 'system.tier', diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index 1f7e1c92..1988b1d8 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -79,8 +79,8 @@ export default class DHAttackAction extends DHDamageAction { const str = damageString ? damageString : game.i18n.format('DAGGERHEART.GENERAL.missingX', { - x: game.i18n.localize('DAGGERHEART.GENERAL.damage') - }); + x: game.i18n.localize('DAGGERHEART.GENERAL.damage') + }); const icons = Array.from(type) .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon) diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index acd104a7..c71f5ef9 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -144,8 +144,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return this.item instanceof DhpActor ? this.item : this.item?.parent instanceof DhpActor - ? this.item.parent - : null; + ? this.item.parent + : null; } /** @@ -223,7 +223,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * @returns {object} */ async use(event, configOptions = {}) { - if (!this.actor) throw new Error("An Action can't be used outside of an Actor context."); + if (!this.actor) throw new Error('An Action can\'t be used outside of an Actor context.'); let config = this.prepareConfig(event, configOptions); if (!config) return; @@ -300,17 +300,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel const groupAttackTokens = this.damage.groupAttack ? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens( - this.actor.id, - this.damage.groupAttack - ) + this.actor.id, + this.damage.groupAttack + ) : null; config.damageOptions = { groupAttack: this.damage.groupAttack ? { - numAttackers: Math.max(groupAttackTokens.length, 1), - range: this.damage.groupAttack - } + numAttackers: Math.max(groupAttackTokens.length, 1), + range: this.damage.groupAttack + } : null }; } diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 0fbea122..7e037f5b 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -90,13 +90,13 @@ export default class BeastformEffect extends BaseEffect { ...baseUpdate, x, y, - 'texture': { + texture: { enabled: this.characterTokenData.usesDynamicToken, src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg, scaleX: this.characterTokenData.tokenSize.scale, scaleY: this.characterTokenData.tokenSize.scale }, - 'ring': { + ring: { subject: { texture: token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs index 217ff9dd..0c226513 100644 --- a/module/data/activeEffect/changeTypes/armor.mjs +++ b/module/data/activeEffect/changeTypes/armor.mjs @@ -166,10 +166,10 @@ export default class ArmorChange extends foundry.abstract.DataModel { value: change.type === 'armor' ? { - ...change.value, - current: Math.min(change.value.current, newMax), - max: newMax - } + ...change.value, + current: Math.min(change.value.current, newMax), + max: newMax + } : change.value })) ]; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 10d53c13..aed27650 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -315,8 +315,8 @@ export default class DhCharacter extends DhCreature { return currentLevel === 1 ? 1 : Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find( - tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end - ).tier; + tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end + ).tier; } get ancestry() { @@ -520,20 +520,20 @@ export default class DhCharacter extends DhCreature { if (armorSource.type === 'armor') { armorUpdates[armorSource.parent.id].updates.push({ - '_id': armorSource.id, + _id: armorSource.id, 'system.armor.current': armorSource.system.armor.current + usedArmorChange }); } else { effectUpdates[armorSource.parent.id].updates.push({ - '_id': armorSource.id, + _id: armorSource.id, 'system.changes': armorSource.system.changes.map(change => ({ ...change, value: change.type === 'armor' ? { - ...change.value, - current: armorSource.system.armorChange.value.current + usedArmorChange - } + ...change.value, + current: armorSource.system.armorChange.value.current + usedArmorChange + } : change.value })) }); @@ -621,21 +621,21 @@ export default class DhCharacter extends DhCreature { }, ...(multiclassFeatures.length ? { - multiclassFeatures: { - title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`, - type: 'multiclass', - values: multiclassFeatures - } - } + multiclassFeatures: { + title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`, + type: 'multiclass', + values: multiclassFeatures + } + } : {}), ...(multiclassSubclassFeatures.length ? { - multiclassSubclassFeatures: { - title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`, - type: 'multiclassSubclass', - values: multiclassSubclassFeatures - } - } + multiclassSubclassFeatures: { + title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`, + type: 'multiclassSubclass', + values: multiclassSubclassFeatures + } + } : {}), companionFeatures: { title: game.i18n.localize('DAGGERHEART.ACTORS.Character.companionFeatures'), @@ -659,8 +659,8 @@ export default class DhCharacter extends DhCreature { (this.primaryWeapon && this.secondaryWeapon) ? burden.twoHanded.value : this.primaryWeapon || this.secondaryWeapon - ? burden.oneHanded.value - : null; + ? burden.oneHanded.value + : null; } get deathMoveViable() { @@ -726,8 +726,8 @@ export default class DhCharacter extends DhCreature { currentLevel === 1 ? null : Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find( - tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end - ).tier; + tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end + ).tier; if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto) { for (let levelKey in this.levelData.levelups) { const level = this.levelData.levelups[levelKey]; diff --git a/module/data/companionLevelup.mjs b/module/data/companionLevelup.mjs index 7ab61210..e24820de 100644 --- a/module/data/companionLevelup.mjs +++ b/module/data/companionLevelup.mjs @@ -22,12 +22,12 @@ export class DhCompanionLevelup extends foundry.abstract.DataModel { const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {}; const experiences = initialAchievements.experience ? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => { - acc[foundry.utils.randomID()] = { - name: '', - modifier: initialAchievements.experience.modifier - }; - return acc; - }, {}) + acc[foundry.utils.randomID()] = { + name: '', + modifier: initialAchievements.experience.modifier + }; + return acc; + }, {}) : {}; const currentChoices = pcLevelData.levelups[i]?.selections?.length; @@ -302,9 +302,9 @@ export class DhLevelupLevel extends foundry.abstract.DataModel { experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {}, domainCards: levelData.achievements?.domainCards ? levelData.achievements.domainCards.reduce((acc, card, index) => { - acc[index] = { ...card }; - return acc; - }, {}) + acc[index] = { ...card }; + return acc; + }, {}) : (achievements.domainCards ?? {}), proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null }, diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 7d27197d..54971d34 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -77,11 +77,11 @@ export class DhCountdown extends foundry.abstract.DataModel { static defaultCountdown(type, playerHidden) { const ownership = playerHidden ? game.users.reduce((acc, user) => { - if (!user.isGM) { - acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE; - } - return acc; - }, {}) + if (!user.isGM) { + acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE; + } + return acc; + }, {}) : undefined; return { @@ -102,8 +102,8 @@ export class DhCountdown extends foundry.abstract.DataModel { value: user.isGM ? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER : this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1 - ? this.ownership.players[user.id].type - : this.ownership.default, + ? this.ownership.players[user.id].type + : this.ownership.default, isGM: user.isGM }; diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs index e3be9937..0eeb95c2 100644 --- a/module/data/fields/action/beastformField.mjs +++ b/module/data/fields/action/beastformField.mjs @@ -105,8 +105,8 @@ export default class BeastformField extends fields.SchemaField { baseSize === 'custom' ? 'custom' : (Object.keys(CONFIG.DH.ACTOR.tokenSize).find( - x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1 - ) ?? baseSize); + x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1 + ) ?? baseSize); formData.system.tokenSize = { ...evolvedData.form.system.tokenSize, size: evolvedSize diff --git a/module/data/fields/action/costField.mjs b/module/data/fields/action/costField.mjs index 1928af41..82cfcd23 100644 --- a/module/data/fields/action/costField.mjs +++ b/module/data/fields/action/costField.mjs @@ -116,8 +116,8 @@ export default class CostField extends fields.ArrayField { c.key === 'fear' ? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) : resources[c.key].isReversed - ? resources[c.key].max - resources[c.key].value - : resources[c.key].value; + ? resources[c.key].max - resources[c.key].value + : resources[c.key].value; if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step); return c; }); @@ -149,8 +149,8 @@ export default class CostField extends fields.ArrayField { !resources[c.key] ? a : a && resources[c.key].isReversed - ? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max - : resources[c.key]?.value >= (c.total ?? c.value), + ? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max + : resources[c.key]?.value >= (c.total ?? c.value), true ); } diff --git a/module/data/fields/action/countdownField.mjs b/module/data/fields/action/countdownField.mjs index 990f8ef1..96d9dd91 100644 --- a/module/data/fields/action/countdownField.mjs +++ b/module/data/fields/action/countdownField.mjs @@ -87,11 +87,11 @@ export default class CountdownField extends fields.ArrayField { CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting.toObject() - ), - game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.Refresh, - data: { refreshType: RefreshType.Countdown } - }); + ); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.Countdown } + }); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); }, data, diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index d2ee1682..e943d63d 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -61,11 +61,11 @@ export default class EffectsField extends fields.ArrayField { token: messageToken, conditionImmunities: Object.values(conditionImmunities).some(x => x) ? game.i18n.format('DAGGERHEART.UI.Chat.effectSummary.immunityTo', { - immunities: Object.keys(conditionImmunities) - .filter(x => conditionImmunities[x]) - .map(x => game.i18n.localize(conditions[x].name)) - .join(', ') - }) + immunities: Object.keys(conditionImmunities) + .filter(x => conditionImmunities[x]) + .map(x => game.i18n.localize(conditions[x].name)) + .join(', ') + }) : null }); diff --git a/module/data/fields/action/saveField.mjs b/module/data/fields/action/saveField.mjs index 0629353e..7343ab85 100644 --- a/module/data/fields/action/saveField.mjs +++ b/module/data/fields/action/saveField.mjs @@ -69,11 +69,11 @@ export default class SaveField extends fields.SchemaField { game.user === actor.owner ? SaveField.rollSave.call(this, actor, event) : actor.owner.query('reactionRoll', { - actionId: this.uuid, - actorId: actor.uuid, - event, - message - }); + actionId: this.uuid, + actorId: actor.uuid, + event, + message + }); const result = await rollSave; await SaveField.updateSaveMessage.call(this, result, message, target.id); subResolve(); @@ -97,8 +97,8 @@ export default class SaveField extends fields.SchemaField { const title = actor.isNPC ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') : game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: game.i18n.localize(abilities[this.save.trait]?.label) - }), + ability: game.i18n.localize(abilities[this.save.trait]?.label) + }), rollConfig = { event, title, diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index ee9d9839..ba274cc7 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -208,8 +208,8 @@ export default class DHBeastform extends BaseDataItem { const autoTokenSize = this.tokenSize.size !== 'custom' ? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes[ - this.tokenSize.size - ] + this.tokenSize.size + ] : null; const width = autoTokenSize ?? this.tokenSize.width; const height = autoTokenSize ?? this.tokenSize.height; diff --git a/module/data/levelup.mjs b/module/data/levelup.mjs index 4dc1c058..e30bf52d 100644 --- a/module/data/levelup.mjs +++ b/module/data/levelup.mjs @@ -19,12 +19,12 @@ export class DhLevelup extends foundry.abstract.DataModel { const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {}; const experiences = initialAchievements.experience ? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => { - acc[foundry.utils.randomID()] = { - name: '', - modifier: initialAchievements.experience.modifier - }; - return acc; - }, {}) + acc[foundry.utils.randomID()] = { + name: '', + modifier: initialAchievements.experience.modifier + }; + return acc; + }, {}) : {}; const domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => { @@ -298,9 +298,9 @@ export class DhLevelupLevel extends foundry.abstract.DataModel { experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {}, domainCards: levelData.achievements?.domainCards ? levelData.achievements.domainCards.reduce((acc, card, index) => { - acc[index] = { ...card }; - return acc; - }, {}) + acc[index] = { ...card }; + return acc; + }, {}) : (achievements.domainCards ?? {}), proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null }, diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index fb20870f..c28db98f 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -104,9 +104,9 @@ export default class DHRoll extends Roll { if (action?.chatDisplay) { actionDescription = action ? await foundry.applications.ux.TextEditor.implementation.enrichHTML(action.description, { - relativeTo: config.data, - rollData: config.data.getRollData?.() ?? {} - }) + relativeTo: config.data, + rollData: config.data.getRollData?.() ?? {} + }) : null; config.actionChatMessageHandled = true; } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 1d2d556a..1cfed094 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -109,10 +109,10 @@ export default class DualityRoll extends D20Roll { const label = this.guaranteedCritical ? 'DAGGERHEART.GENERAL.guaranteedCriticalSuccess' : this.isCritical - ? 'DAGGERHEART.GENERAL.criticalSuccess' - : this.withHope - ? 'DAGGERHEART.GENERAL.hope' - : 'DAGGERHEART.GENERAL.fear'; + ? 'DAGGERHEART.GENERAL.criticalSuccess' + : this.withHope + ? 'DAGGERHEART.GENERAL.hope' + : 'DAGGERHEART.GENERAL.fear'; return game.i18n.localize(label); } @@ -147,8 +147,8 @@ export default class DualityRoll extends D20Roll { const advDieClass = this.hasAdvantage ? game.system.api.dice.diceTypes.AdvantageDie : this.hasDisadvantage - ? game.system.api.dice.diceTypes.DisadvantageDie - : null; + ? game.system.api.dice.diceTypes.DisadvantageDie + : null; if (advDieClass) { const advDie = new advDieClass({ faces: this.advantageFaces, number: this.advantageNumber }); if (this.terms.length < 4) { diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index f9239a90..3518210b 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -175,8 +175,8 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { return model instanceof documentClass ? model : model.parent - ? this.#resolveParentDocument(model.parent, documentClass) - : null; + ? this.#resolveParentDocument(model.parent, documentClass) + : null; } static getChangeValue(model, change, effect) { diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 893e6e5c..480f8c69 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -9,9 +9,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { actor && this.isContentVisible ? actor : { - img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg', - name: '' - }; + img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg', + name: '' + }; /* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */ const html = await super.renderHTML({ actor: actorData, author: this.author }); @@ -290,14 +290,14 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { behaviors: effects.length > 0 ? [ - { - name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'), - type: 'applyActiveEffect', - system: { - effects: effects - } - } - ] + { + name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'), + type: 'applyActiveEffect', + system: { + effects: effects + } + } + ] : [], displayMeasurements: true, locked: false, diff --git a/module/documents/item.mjs b/module/documents/item.mjs index f46e24e6..4716068d 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -98,8 +98,8 @@ export default class DHItem extends foundry.documents.Item { isInventoryItem === true ? 'Inventory Items' //TODO localize : isInventoryItem === false - ? 'Character Items' //TODO localize - : 'Other'; //TODO localize + ? 'Character Items' //TODO localize + : 'Other'; //TODO localize return { value: type, label, group }; } diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 30862724..8e91d4f0 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -324,7 +324,7 @@ export default class DHToken extends CONFIG.Token.documentClass { } let x = 0.5 * bottom; let y = 0.25; - for (let k = width - bottom; k--; ) { + for (let k = width - bottom; k--;) { points.push(x, y); x += 0.5; y -= 0.25; @@ -333,7 +333,7 @@ export default class DHToken extends CONFIG.Token.documentClass { y += 0.25; } points.push(x, y); - for (let k = bottom; k--; ) { + for (let k = bottom; k--;) { y += 0.5; points.push(x, y); x += 0.5; @@ -341,14 +341,14 @@ export default class DHToken extends CONFIG.Token.documentClass { points.push(x, y); } y += 0.5; - for (let k = top; k--; ) { + for (let k = top; k--;) { points.push(x, y); x -= 0.5; y += 0.25; points.push(x, y); y += 0.5; } - for (let k = width - top; k--; ) { + for (let k = width - top; k--;) { points.push(x, y); x -= 0.5; y += 0.25; @@ -357,7 +357,7 @@ export default class DHToken extends CONFIG.Token.documentClass { y -= 0.25; } points.push(x, y); - for (let k = top; k--; ) { + for (let k = top; k--;) { y -= 0.5; points.push(x, y); x -= 0.5; @@ -365,7 +365,7 @@ export default class DHToken extends CONFIG.Token.documentClass { points.push(x, y); } y -= 0.5; - for (let k = bottom; k--; ) { + for (let k = bottom; k--;) { points.push(x, y); x += 0.5; y -= 0.25; diff --git a/module/enrichers/DualityRollEnricher.mjs b/module/enrichers/DualityRollEnricher.mjs index 5b66179f..a7db01a4 100644 --- a/module/enrichers/DualityRollEnricher.mjs +++ b/module/enrichers/DualityRollEnricher.mjs @@ -15,8 +15,8 @@ function getDualityMessage(roll, flavor) { (roll?.trait ? game.i18n.format('DAGGERHEART.GENERAL.rollWith', { roll: trait }) : roll?.reaction - ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') - : game.i18n.localize('DAGGERHEART.GENERAL.duality')); + ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') + : game.i18n.localize('DAGGERHEART.GENERAL.duality')); const dataLabel = trait ? game.i18n.localize(abilities[roll.trait].label) @@ -25,14 +25,14 @@ function getDualityMessage(roll, flavor) { const advantage = roll?.advantage ? CONFIG.DH.ACTIONS.advantageState.advantage.value : roll?.disadvantage - ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value - : undefined; + ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value + : undefined; const advantageLabel = advantage === CONFIG.DH.ACTIONS.advantageState.advantage.value ? 'Advantage' : advantage === CONFIG.DH.ACTIONS.advantageState.disadvantage.value - ? 'Disadvantage' - : undefined; + ? 'Disadvantage' + : undefined; const dualityElement = document.createElement('span'); dualityElement.innerHTML = ` diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index 8db3ec14..bbe93b17 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -8,8 +8,8 @@ export default function DhTemplateEnricher(match, _options) { const range = params.range && Number.isNaN(Number(params.range)) ? Object.values(CONFIG.DH.GENERAL.templateRanges).find( - x => x.id.toLowerCase() === params.range || x.short === params.range - )?.id + x => x.id.toLowerCase() === params.range || x.short === params.range + )?.id : params.range; if (!CONFIG.DH.GENERAL.templateTypes[type] || !range) return match[0]; diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index ddc353b1..af6c2777 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -108,9 +108,9 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {} const options = Array.isArray(baseOptions) ? baseOptions : Object.keys(baseOptions).map(optionKey => ({ - ...baseOptions[optionKey], - id: optionKey - })); + ...baseOptions[optionKey], + id: optionKey + })); const tagifyElement = new Tagify(element, { tagTextProp: 'name', @@ -605,8 +605,8 @@ export function calculateExpectedValue(formulaOrTerms) { const terms = Array.isArray(formulaOrTerms) ? formulaOrTerms : typeof formulaOrTerms === 'string' - ? parseTermsFromSimpleFormula(formulaOrTerms) - : [formulaOrTerms]; + ? parseTermsFromSimpleFormula(formulaOrTerms) + : [formulaOrTerms]; return terms.reduce((r, t) => r + (t.bonus ?? 0) + (t.diceQuantity ? (t.diceQuantity * (t.faces + 1)) / 2 : 0), 0); } @@ -656,8 +656,8 @@ export async function RefreshFeatures( 'resource.value': increasing ? 0 : game.system.api.documents.DhActiveEffect.effectSafeEval( - Roll.replaceFormulaData(item.system.resource.max, actor.getRollData()) - ) + Roll.replaceFormulaData(item.system.resource.max, actor.getRollData()) + ) }; } if (item.system.metadata?.hasActions) { diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index b4c446b2..b718a127 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -24,8 +24,8 @@ export async function runMigrations() { const { originItemType, isMulticlass, identifier } = item.system; const base = originItemType ? actor.items.find( - x => x.type === originItemType && Boolean(isMulticlass) === Boolean(x.system.isMulticlass) - ) + x => x.type === originItemType && Boolean(isMulticlass) === Boolean(x.system.isMulticlass) + ) : null; if (base) { const feature = base.system.features.find(x => x.item && x.item.uuid === item.uuid); diff --git a/package-lock.json b/package-lock.json index 28223032..dee096eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,18 +13,20 @@ "rollup": "^4.40.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@foundryvtt/foundryvtt-cli": "^1.0.2", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", + "@stylistic/eslint-plugin": "^5.10.0", "concurrently": "^8.2.2", "eslint": "^10.2.1", - "eslint-plugin-prettier": "^5.5.5", "globals": "^17.5.0", "husky": "^9.1.7", "lint-staged": "^16.4.0", "postcss": "^8.4.32", - "prettier": "^3.5.3", - "rollup-plugin-postcss": "^4.0.2" + "rollup-plugin-postcss": "^4.0.2", + "typescript": "^6.0.3", + "typescript-eslint": "^8.60.1" } }, "node_modules/@babel/runtime": { @@ -158,6 +160,27 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, "node_modules/@eslint/object-schema": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", @@ -420,19 +443,6 @@ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, "node_modules/@rollup/plugin-commonjs": { "version": "25.0.8", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", @@ -761,6 +771,58 @@ "util": "^0.12.4" } }, + "node_modules/@stylistic/eslint-plugin": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/types": "^8.56.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.0.0 || ^10.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -795,6 +857,288 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@yaireo/tagify": { "version": "4.35.1", "resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.1.tgz", @@ -1853,10 +2197,11 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2241,37 +2586,6 @@ } } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", - "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.1", - "synckit": "^0.11.12" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, "node_modules/eslint-scope": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", @@ -2511,13 +2825,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -2554,6 +2861,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -5217,34 +5542,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", - "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -6053,22 +6350,6 @@ "node": ">= 10" } }, - "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, "node_modules/teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -6116,6 +6397,23 @@ "node": ">=18" } }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6147,6 +6445,19 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6171,6 +6482,44 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz", + "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.1", + "@typescript-eslint/parser": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", diff --git a/package.json b/package.json index 73a7fe99..ede90401 100644 --- a/package.json +++ b/package.json @@ -24,18 +24,20 @@ "prepare": "husky" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@foundryvtt/foundryvtt-cli": "^1.0.2", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", + "@stylistic/eslint-plugin": "^5.10.0", "concurrently": "^8.2.2", "eslint": "^10.2.1", - "eslint-plugin-prettier": "^5.5.5", "globals": "^17.5.0", "husky": "^9.1.7", "lint-staged": "^16.4.0", "postcss": "^8.4.32", - "prettier": "^3.5.3", - "rollup-plugin-postcss": "^4.0.2" + "rollup-plugin-postcss": "^4.0.2", + "typescript": "^6.0.3", + "typescript-eslint": "^8.60.1" }, "lint-staged": { "**/*": "eslint --fix" diff --git a/tools/analyze-damage.mjs b/tools/analyze-damage.mjs index 7b3fb9e5..31c254ce 100644 --- a/tools/analyze-damage.mjs +++ b/tools/analyze-damage.mjs @@ -82,7 +82,7 @@ function getMean(numbers) { } function getMedianAverageDeviation(numbers, { median }) { - const residuals = allDamage.map(d => Math.abs(d - median)); + const residuals = numbers.map(d => Math.abs(d - median)); return getMedian(residuals); } @@ -98,8 +98,8 @@ function parseDamage(damage) { p.value.custom.enabled ? p.value.custom.formula : [p.value.flatMultiplier ? `${p.value.flatMultiplier}${p.value.dice}` : 0, p.value.bonus ?? 0] - .filter(p => !!p) - .join('+') + .filter(p => !!p) + .join('+') ) .join('+'); return getExpectedDamage(formula); diff --git a/tools/create-symlink.mjs b/tools/create-symlink.mjs index fd828c73..4e14d5df 100644 --- a/tools/create-symlink.mjs +++ b/tools/create-symlink.mjs @@ -2,6 +2,8 @@ import fs from 'fs'; import path from 'path'; import readline from 'readline'; +console.log('Creates a foundry symlink in the base folder for type purposes\n'); + const askQuestion = question => { const rl = readline.createInterface({ input: process.stdin, diff --git a/tools/eslint.config.mjs b/tools/eslint.config.mjs new file mode 100644 index 00000000..36a5174a --- /dev/null +++ b/tools/eslint.config.mjs @@ -0,0 +1,20 @@ +import globals from 'globals'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import { stylisticRules } from '../eslint.config.mjs'; +import stylistic from '@stylistic/eslint-plugin'; + +export default defineConfig([ + globalIgnores(['foundry/**/*']), + { + files: ['**/*.{js,mjs,cjs}'], + plugins: { + '@stylistic': stylistic + }, + languageOptions: { globals: globals.node }, + rules: { + 'no-undef': 'error', + 'no-unused-vars': 0, + ...stylisticRules + } + } +]);