From 5b94675db1e167826d21ed7532d06589579687cd Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:18:36 +0200 Subject: [PATCH] Feature/132 weapon armor features (#136) * Added effects for Weapon and Armor traits * Fixed so @ strings can be parsed as active effect values * Added actions --- lang/en.json | 13 +- module/applications/sheets/items/armor.mjs | 26 ++ module/applications/sheets/items/weapon.mjs | 26 ++ module/config/itemConfig.mjs | 425 ++++++++++++++++-- module/data/action/action.mjs | 3 +- module/data/action/actionDice.mjs | 2 +- module/data/actor/character.mjs | 7 +- module/data/item/armor.mjs | 59 ++- module/data/item/weapon.mjs | 62 ++- module/documents/activeEffect.mjs | 13 + .../sheets/character/sections/inventory.hbs | 2 +- templates/sheets/items/armor/settings.hbs | 4 +- templates/sheets/items/weapon/settings.hbs | 4 +- 13 files changed, 598 insertions(+), 48 deletions(-) diff --git a/lang/en.json b/lang/en.json index 0b43ee8f..a18527d0 100755 --- a/lang/en.json +++ b/lang/en.json @@ -264,6 +264,7 @@ } }, "Tiers": { + "singular": "Tier", "tier1": "Tier 1", "tier2": "Tier 2", "tier3": "Tier 3", @@ -622,7 +623,7 @@ "WeaponFeature": { "Barrier": { "Name": "Barrier", - "Description": "+{armorBonus} to Armor Score; -1 to Evasion" + "Description": "+{armorScore} to Armor Score; -1 to Evasion" }, "Bonded": { "Name": "Bonded", @@ -692,6 +693,10 @@ "Name": "Greedy", "Description": "Spend a handful of gold to gain a +1 bonus to your Proficiency on a damage roll." }, + "Healing": { + "Name": "Healing", + "Description": "During downtime, automatically clear a Hit Point." + }, "Heavy": { "Name": "Heavy", "Description": "-1 to Evasion" @@ -716,6 +721,10 @@ "Name": "Locked On", "Description": "On a successful attack, your next attack against the same target with your primary weapon automatically succeeds." }, + "Lucky": { + "Name": "Lucky", + "Description": "On a failed attack, you can mark a Stress to reroll your attack." + }, "Long": { "Name": "Long", "Description": "This weapon's attack targets all adversaries in a line within range." @@ -750,7 +759,7 @@ }, "Protective": { "Name": "Protective", - "Description": "+{armorBonus} to Armor Score" + "Description": "+{tier} to Armor Score" }, "Quick": { "Name": "Quick", diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 43e6dce9..32c482ba 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -1,3 +1,5 @@ +import { armorFeatures } from '../../../config/itemConfig.mjs'; +import { tagifyElement } from '../../../helpers/utils.mjs'; import DHItemSheetV2 from '../item.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; @@ -20,4 +22,28 @@ export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) { scrollable: ['.settings'] } }; + + async _preparePartContext(partId, context) { + super._preparePartContext(partId, context); + + switch (partId) { + case 'settings': + context.features = this.document.system.features.map(x => x.value); + break; + } + + return context; + } + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + const featureInput = htmlElement.querySelector('.features-input'); + tagifyElement(featureInput, armorFeatures, this.onFeatureSelect.bind(this)); + } + + async onFeatureSelect(features) { + await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) }); + this.render(true); + } } diff --git a/module/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index a54c0140..b381e8bf 100644 --- a/module/applications/sheets/items/weapon.mjs +++ b/module/applications/sheets/items/weapon.mjs @@ -1,3 +1,5 @@ +import { weaponFeatures } from '../../../config/itemConfig.mjs'; +import { tagifyElement } from '../../../helpers/utils.mjs'; import DHItemSheetV2 from '../item.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; @@ -19,4 +21,28 @@ export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) { scrollable: ['.settings'] } }; + + async _preparePartContext(partId, context) { + super._preparePartContext(partId, context); + + switch (partId) { + case 'settings': + context.features = this.document.system.features.map(x => x.value); + break; + } + + return context; + } + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + const featureInput = htmlElement.querySelector('.features-input'); + tagifyElement(featureInput, weaponFeatures, this.onFeatureSelect.bind(this)); + } + + async onFeatureSelect(features) { + await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) }); + this.render(true); + } } diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index fe2037f2..a2a786b9 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -5,15 +5,78 @@ export const armorFeatures = { }, channeling: { label: 'DAGGERHEART.ArmorFeature.Channeling.Name', - description: 'DAGGERHEART.ArmorFeature.Channeling.Description' + description: 'DAGGERHEART.ArmorFeature.Channeling.Description', + effects: [ + { + changes: [ + { + key: 'system.bonuses.spellcast', + mode: 2, + value: '1' + } + ] + } + ] }, difficult: { label: 'DAGGERHEART.ArmorFeature.Difficult.Name', - description: 'DAGGERHEART.ArmorFeature.Difficult.Description' + description: 'DAGGERHEART.ArmorFeature.Difficult.Description', + effects: [ + { + changes: [ + { + key: 'system.traits.agility.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.strength.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.finesse.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.instinct.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.presence.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.knowledge.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, flexible: { label: 'DAGGERHEART.ArmorFeature.Flexible.Name', - description: 'DAGGERHEART.ArmorFeature.Flexible.Description' + description: 'DAGGERHEART.ArmorFeature.Flexible.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '1' + } + ] + } + ] }, fortified: { label: 'DAGGERHEART.ArmorFeature.Fortified.Name', @@ -21,11 +84,33 @@ export const armorFeatures = { }, gilded: { label: 'DAGGERHEART.ArmorFeature.Gilded.Name', - description: 'DAGGERHEART.ArmorFeature.Gilded.Description' + description: 'DAGGERHEART.ArmorFeature.Gilded.Description', + effects: [ + { + changes: [ + { + key: 'system.traits.presence.bonus', + mode: 2, + value: '1' + } + ] + } + ] }, heavy: { label: 'DAGGERHEART.ArmorFeature.Heavy.Name', - description: 'DAGGERHEART.ArmorFeature.Heavy.Description' + description: 'DAGGERHEART.ArmorFeature.Heavy.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, hopeful: { label: 'DAGGERHEART.ArmorFeature.Hopeful.Name', @@ -77,7 +162,23 @@ export const armorFeatures = { }, veryheavy: { label: 'DAGGERHEART.ArmorFeature.VeryHeavy.Name', - description: 'DAGGERHEART.ArmorFeature.VeryHeavy.Description' + description: 'DAGGERHEART.ArmorFeature.VeryHeavy.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-2' + }, + { + key: 'system.traits.agility.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, warded: { label: 'DAGGERHEART.ArmorFeature.Warded.Name', @@ -89,13 +190,41 @@ export const weaponFeatures = { barrier: { label: 'DAGGERHEART.WeaponFeature.Barrier.Name', description: 'DAGGERHEART.WeaponFeature.Barrier.Description', - override: { - armorBonus: 1 - } + effects: [ + { + changes: [ + { + key: 'system.bonuses.armorScore', + mode: 2, + value: '@system.tier + 1' + } + ] + }, + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, bonded: { label: 'DAGGERHEART.WeaponFeature.Bonded.Name', - description: 'DAGGERHEART.WeaponFeature.Bonded.Description' + description: 'DAGGERHEART.WeaponFeature.Bonded.Description', + effects: [ + { + changes: [ + { + key: 'system.bonuses.damage', + mode: 2, + value: 'system.levelData.levels.current' + } + ] + } + ] }, bouncing: { label: 'DAGGERHEART.WeaponFeature.Bouncing.Name', @@ -103,7 +232,27 @@ export const weaponFeatures = { }, brave: { label: 'DAGGERHEART.WeaponFeature.Brave.Name', - description: 'DAGGERHEART.WeaponFeature.Brave.Description' + description: 'DAGGERHEART.WeaponFeature.Brave.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + }, + { + changes: [ + { + key: 'system.damageThresholds.severe', + mode: 2, + value: '3' + } + ] + } + ] }, brutal: { label: 'DAGGERHEART.WeaponFeature.Brutal.Name', @@ -111,15 +260,55 @@ export const weaponFeatures = { }, charged: { label: 'DAGGERHEART.WeaponFeature.Charged.Name', - description: 'DAGGERHEART.WeaponFeature.Charged.Description' + description: 'DAGGERHEART.WeaponFeature.Charged.Description', + actions: [ + { + type: 'effect', + name: 'DAGGERHEART.WeaponFeature.Concussive.Name', + img: 'icons/skills/melee/shield-damaged-broken-brown.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + // Should add an effect with path system.proficiency.bonus +1 + } + ] }, concussive: { label: 'DAGGERHEART.WeaponFeature.Concussive.Name', - description: 'DAGGERHEART.WeaponFeature.Concussive.Description' + description: 'DAGGERHEART.WeaponFeature.Concussive.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Concussive.Name', + img: 'icons/skills/melee/shield-damaged-broken-brown.webp', + actionType: 'action', + cost: [ + { + type: 'hope', + value: 1 + } + ] + } + ] }, cumbersome: { label: 'DAGGERHEART.WeaponFeature.Cumbersome.Name', - description: 'DAGGERHEART.WeaponFeature.Cumbersome.Description' + description: 'DAGGERHEART.WeaponFeature.Cumbersome.Description', + effects: [ + { + changes: [ + { + key: 'system.traits.finesse.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, deadly: { label: 'DAGGERHEART.WeaponFeature.Deadly.Name', @@ -128,18 +317,64 @@ export const weaponFeatures = { deflecting: { label: 'DAGGERHEART.WeaponFeature.Deflecting.Name', description: 'DAGGERHEART.WeaponFeature.Deflecting.Description' + // actions: [{ + // type: 'effect', + // name: 'DAGGERHEART.WeaponFeature.Deflecting.Name', + // img: 'icons/skills/melee/strike-flail-destructive-yellow.webp', + // actionType: 'reaction', + // cost: [{ + // type: 'armorSlot', // Needs armorSlot as type + // value: 1 + // }], + // }], }, destructive: { label: 'DAGGERHEART.WeaponFeature.Destructive.Name', - description: 'DAGGERHEART.WeaponFeature.Destructive.Description' + description: 'DAGGERHEART.WeaponFeature.Destructive.Description', + effects: [ + { + changes: [ + { + key: 'system.traits.agility.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, devastating: { label: 'DAGGERHEART.WeaponFeature.Devastating.Name', - description: 'DAGGERHEART.WeaponFeature.Devastating.Description' + description: 'DAGGERHEART.WeaponFeature.Devastating.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Devastating.Name', + img: 'icons/skills/melee/strike-flail-destructive-yellow.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, doubleduty: { label: 'DAGGERHEART.WeaponFeature.DoubleDuty.Name', - description: 'DAGGERHEART.WeaponFeature.DoubleDuty.Description' + description: 'DAGGERHEART.WeaponFeature.DoubleDuty.Description', + effects: [ + { + changes: [ + { + key: 'system.bonuses.armorScore', + mode: 2, + value: '1' + } + ] + } + ] }, doubledup: { label: 'DAGGERHEART.WeaponFeature.DoubledUp.Name', @@ -155,15 +390,61 @@ export const weaponFeatures = { }, grappling: { label: 'DAGGERHEART.WeaponFeature.Grappling.Name', - description: 'DAGGERHEART.WeaponFeature.Grappling.Description' + description: 'DAGGERHEART.WeaponFeature.Grappling.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Grappling.Name', + img: 'icons/magic/control/debuff-chains-ropes-net-white.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, greedy: { label: 'DAGGERHEART.WeaponFeature.Greedy.Name', description: 'DAGGERHEART.WeaponFeature.Greedy.Description' }, + healing: { + label: 'DAGGERHEART.WeaponFeature.Healing.Name', + description: 'DAGGERHEART.WeaponFeature.Healing.Description', + actions: [ + { + type: 'healing', + name: 'DAGGERHEART.WeaponFeature.Healing.Name', + img: 'icons/magic/life/cross-beam-green.webp', + actionType: 'action', + healing: { + type: 'health', + value: { + custom: { + enabled: true, + formula: '1' + } + } + } + } + ] + }, heavy: { label: 'DAGGERHEART.WeaponFeature.Heavy.Name', - description: 'DAGGERHEART.WeaponFeature.Heavy.Description' + description: 'DAGGERHEART.WeaponFeature.Heavy.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, hooked: { label: 'DAGGERHEART.WeaponFeature.Hooked.Name', @@ -189,13 +470,56 @@ export const weaponFeatures = { label: 'DAGGERHEART.WeaponFeature.Long.Name', description: 'DAGGERHEART.WeaponFeature.Long.Description' }, + lucky: { + label: 'DAGGERHEART.WeaponFeature.Lucky.Name', + description: 'DAGGERHEART.WeaponFeature.Lucky.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Lucky.Name', + img: 'icons/magic/control/buff-luck-fortune-green.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] + }, massive: { label: 'DAGGERHEART.WeaponFeature.Massive.Name', - description: 'DAGGERHEART.WeaponFeature.Massive.Description' + description: 'DAGGERHEART.WeaponFeature.Massive.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, painful: { label: 'DAGGERHEART.WeaponFeature.Painful.Name', - description: 'DAGGERHEART.WeaponFeature.Painful.Description' + description: 'DAGGERHEART.WeaponFeature.Painful.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Painful.Name', + img: 'icons/skills/wounds/injury-face-impact-orange.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, paired: { label: 'DAGGERHEART.WeaponFeature.Paired.Name', @@ -223,17 +547,50 @@ export const weaponFeatures = { protective: { label: 'DAGGERHEART.WeaponFeature.Protective.Name', description: 'DAGGERHEART.WeaponFeature.Protective.Description', - override: { - armorBonus: 1 - } + effects: [ + { + changes: [ + { + key: 'system.bonuses.armorScore', + mode: 2, + value: '@system.tier' + } + ] + } + ] }, quick: { label: 'DAGGERHEART.WeaponFeature.Quick.Name', - description: 'DAGGERHEART.WeaponFeature.Quick.Description' + description: 'DAGGERHEART.WeaponFeature.Quick.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Quick.Name', + img: 'icons/skills/movement/arrow-upward-yellow.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, reliable: { label: 'DAGGERHEART.WeaponFeature.Reliable.Name', - description: 'DAGGERHEART.WeaponFeature.Reliable.Description' + description: 'DAGGERHEART.WeaponFeature.Reliable.Description', + effects: [ + { + changes: [ + { + key: 'system.bonuses.attack', + mode: 2, + value: 1 + } + ] + } + ] }, reloading: { label: 'DAGGERHEART.WeaponFeature.Reloading.Name', @@ -265,7 +622,21 @@ export const weaponFeatures = { }, startling: { label: 'DAGGERHEART.WeaponFeature.Startling.Name', - description: 'DAGGERHEART.WeaponFeature.Startling.Description' + description: 'DAGGERHEART.WeaponFeature.Startling.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Startling.Name', + img: 'icons/magic/control/fear-fright-mask-orange.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, timebending: { label: 'DAGGERHEART.WeaponFeature.Timebending.Name', diff --git a/module/data/action/action.mjs b/module/data/action/action.mjs index 0462980f..7b877415 100644 --- a/module/data/action/action.mjs +++ b/module/data/action/action.mjs @@ -132,7 +132,8 @@ export class DHBaseAction extends foundry.abstract.DataModel { async use(event) { if (this.roll.type && this.roll.trait) { - const modifierValue = this.actor.system.traits[this.roll.trait].value; + const modifierValue = + this.actor.system.traits[this.roll.trait].value + (this.actor.system.bonuses.attack ?? 0); const config = { event: event, title: this.chatTitle, diff --git a/module/data/action/actionDice.mjs b/module/data/action/actionDice.mjs index 9fd445cc..1d8f1fe7 100644 --- a/module/data/action/actionDice.mjs +++ b/module/data/action/actionDice.mjs @@ -23,7 +23,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel { getFormula(actor) { return this.custom.enabled ? this.custom.formula - : `${actor.system[this.multiplier] ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; + : `${actor.system[this.multiplier]?.total ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; } } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 2bc3ca06..1ebe217c 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -84,7 +84,12 @@ export default class DhCharacter extends BaseDataActor { value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) }), - levelData: new fields.EmbeddedDataField(DhPCLevelData) + levelData: new fields.EmbeddedDataField(DhPCLevelData), + bonuses: new fields.SchemaField({ + attack: new fields.NumberField({ integer: true, initial: 0 }), + spellcast: new fields.NumberField({ integer: true, initial: 0 }), + armorScore: new fields.NumberField({ integer: true, initial: 0 }) + }) }; } diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 7ee20c29..b0fdf0ae 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -1,14 +1,15 @@ import BaseDataItem from './base.mjs'; import ActionField from '../fields/actionField.mjs'; +import { armorFeatures } from '../../config/itemConfig.mjs'; export default class DHArmor extends BaseDataItem { /** @inheritDoc */ static get metadata() { return foundry.utils.mergeObject(super.metadata, { - label: "TYPES.Item.armor", - type: "armor", + label: 'TYPES.Item.armor', + type: 'armor', hasDescription: true, - isQuantifiable: true, + isQuantifiable: true }); } @@ -17,9 +18,16 @@ export default class DHArmor extends BaseDataItem { const fields = foundry.data.fields; return { ...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 }), - feature: new fields.StringField({ choices: SYSTEM.ITEM.armorFeatures, blank: true }), + features: new fields.ArrayField( + new fields.SchemaField({ + value: new fields.StringField({ required: true, choices: SYSTEM.ITEM.armorFeatures, blank: true }), + effectIds: new fields.ArrayField(new fields.StringField({ required: true })), + actionIds: new fields.ArrayField(new fields.StringField({ required: true })) + }) + ), marks: new fields.SchemaField({ max: new fields.NumberField({ initial: 6, integer: true }), value: new fields.NumberField({ initial: 0, integer: true }) @@ -35,4 +43,47 @@ export default class DHArmor extends BaseDataItem { get featureInfo() { return this.feature ? CONFIG.daggerheart.ITEM.armorFeatures[this.feature] : null; } + + async _preUpdate(changes, options, user) { + const allowed = await super._preUpdate(changes, options, user); + if (allowed === false) return false; + + if (changes.system.features) { + const removed = this.features.filter(x => !changes.system.features.includes(x)); + const added = changes.system.features.filter(x => !this.features.includes(x)); + + for (var feature of removed) { + for (var effectId of feature.effectIds) { + await this.parent.effects.get(effectId).delete(); + } + + changes.system.actions = this.actions.filter(x => !feature.actionIds.includes(x._id)); + } + + for (var feature of added) { + const featureData = armorFeatures[feature.value]; + if (featureData.effects?.length > 0) { + const embeddedItems = await this.parent.createEmbeddedDocuments('ActiveEffect', [ + { + name: game.i18n.localize(featureData.label), + description: game.i18n.localize(featureData.description), + changes: featureData.effects.flatMap(x => x.changes) + } + ]); + feature.effectIds = embeddedItems.map(x => x.id); + } + if (featureData.actions?.length > 0) { + const newActions = featureData.actions.map(action => { + const cls = actionsTypes[action.type]; + return new cls( + { ...action, _id: foundry.utils.randomID(), name: game.i18n.localize(action.name) }, + { parent: this } + ); + }); + changes.system.actions = [...this.actions, ...newActions]; + feature.actionIds = newActions.map(x => x._id); + } + } + } + } } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index c007bd35..3da7705e 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -1,8 +1,8 @@ import BaseDataItem from './base.mjs'; import FormulaField from '../fields/formulaField.mjs'; -import PseudoDocumentsField from '../fields/pseudoDocumentsField.mjs'; -import BaseFeatureData from '../pseudo-documents/feature/baseFeatureData.mjs'; import ActionField from '../fields/actionField.mjs'; +import { weaponFeatures } from '../../config/itemConfig.mjs'; +import { actionsTypes } from '../../data/_module.mjs'; export default class DHWeapon extends BaseDataItem { /** @inheritDoc */ @@ -23,6 +23,7 @@ export default class DHWeapon extends BaseDataItem { const fields = foundry.data.fields; return { ...super.defineSchema(), + tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }), equipped: new fields.BooleanField({ initial: false }), //SETTINGS @@ -39,14 +40,57 @@ export default class DHWeapon extends BaseDataItem { initial: 'physical' }) }), - feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, blank: true }), - featureTest: new PseudoDocumentsField(BaseFeatureData, { - required: true, - nullable: true, - max: 1, - validTypes: ['weapon'] - }), + features: new fields.ArrayField( + new fields.SchemaField({ + value: new fields.StringField({ required: true, choices: SYSTEM.ITEM.weaponFeatures, blank: true }), + effectIds: new fields.ArrayField(new fields.StringField({ required: true })), + actionIds: new fields.ArrayField(new fields.StringField({ required: true })) + }) + ), actions: new fields.ArrayField(new ActionField()) }; } + + async _preUpdate(changes, options, user) { + const allowed = await super._preUpdate(changes, options, user); + if (allowed === false) return false; + + if (changes.system.features) { + const removed = this.features.filter(x => !changes.system.features.includes(x)); + const added = changes.system.features.filter(x => !this.features.includes(x)); + + for (var feature of removed) { + for (var effectId of feature.effectIds) { + await this.parent.effects.get(effectId).delete(); + } + + changes.system.actions = this.actions.filter(x => !feature.actionIds.includes(x._id)); + } + + for (var feature of added) { + const featureData = weaponFeatures[feature.value]; + if (featureData.effects?.length > 0) { + const embeddedItems = await this.parent.createEmbeddedDocuments('ActiveEffect', [ + { + name: game.i18n.localize(featureData.label), + description: game.i18n.localize(featureData.description), + changes: featureData.effects.flatMap(x => x.changes) + } + ]); + feature.effectIds = embeddedItems.map(x => x.id); + } + if (featureData.actions?.length > 0) { + const newActions = featureData.actions.map(action => { + const cls = actionsTypes[action.type]; + return new cls( + { ...action, _id: foundry.utils.randomID(), name: game.i18n.localize(action.name) }, + { parent: this } + ); + }); + changes.system.actions = [...this.actions, ...newActions]; + feature.actionIds = newActions.map(x => x._id); + } + } + } + } } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index d6671501..7bbf79e3 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -1,4 +1,12 @@ export default class DhActiveEffect extends ActiveEffect { + get isSuppressed() { + if (['weapon', 'armor'].includes(this.parent.type)) { + return !this.parent.system.equipped; + } + + return super.isSuppressed; + } + async _preCreate(data, options, user) { const update = {}; if (!data.img) { @@ -11,4 +19,9 @@ export default class DhActiveEffect extends ActiveEffect { await super._preCreate(data, options, user); } + + static applyField(model, change, field) { + change.value = Roll.safeEval(Roll.replaceFormulaData(change.value, change.effect.parent)); + super.applyField(model, change, field); + } } diff --git a/templates/sheets/character/sections/inventory.hbs b/templates/sheets/character/sections/inventory.hbs index f56138dc..6f4aeb49 100644 --- a/templates/sheets/character/sections/inventory.hbs +++ b/templates/sheets/character/sections/inventory.hbs @@ -13,7 +13,7 @@