From 0cc1597dfe1a37566180dca810f892c28556610e Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:43:35 +0200 Subject: [PATCH] [Fix] Class Feature Change (#364) * Changed to just use a features field * Subclass now uses a simple Features field --- lang/en.json | 11 ++- .../characterCreation/characterCreation.mjs | 4 +- module/applications/sheets/items/ancestry.mjs | 25 ++----- module/applications/sheets/items/class.mjs | 48 ++++++++----- module/applications/sheets/items/subclass.mjs | 69 +++++++++++++++---- module/config/actorConfig.mjs | 2 +- module/config/itemConfig.mjs | 7 +- module/data/actor/character.mjs | 17 ++--- module/data/item/class.mjs | 17 +++-- module/data/item/domainCard.mjs | 1 - module/data/item/subclass.mjs | 22 +++--- .../sheets/items/domainCard/settings.hbs | 2 - templates/sheets/items/subclass/features.hbs | 30 ++++---- 13 files changed, 158 insertions(+), 97 deletions(-) diff --git a/lang/en.json b/lang/en.json index 3456b6b5..80cc67f7 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1325,8 +1325,8 @@ }, "DomainCard": { "type": "Type", - "foundation": "Foundation", "recallCost": "Recall Cost", + "foundationTitle": "Foundation", "specializationTitle": "Specialization", "masteryTitle": "Mastery" }, @@ -1541,7 +1541,14 @@ "actionIsMissing": "Action is missing", "attackIsMissing": "Attack is missing", "unownedActionMacro": "Cannot make a Use macro for an Action not on your character", - "unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters" + "unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters", + "featureNotHope": "This feature is used as something else than a Hope feature and cannot be used here.", + "featureNotClass": "This feature is used as something else than a Class feature and cannot be used here.", + "featureNotPrimary": "This feature is used as something else than a Primary feature and cannot be used here.", + "featureNotSecondary": "This feature is used as something else than a Secondary feature and cannot be used here.", + "featureNotFoundation": "This feature is used as something else than a Foundation feature and cannot be used here.", + "featureNotSpecialization": "This feature is used as something else than a Specialization feature and cannot be used here.", + "featureNotMastery": "This feature is used as something else than a Mastery feature and cannot be used here." }, "Tooltip": { "openItemWorld": "Open Item World", diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index ed0ee5a7..b8759cc5 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -506,9 +506,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl name: this.setup.ancestryName ?? this.setup.primaryAncestry.name, system: { ...this.setup.primaryAncestry.system, - features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid], - primaryFeature: primaryAncestryFeature.uuid, - secondaryFeature: secondaryAncestryFeature.uuid + features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid] } }; diff --git a/module/applications/sheets/items/ancestry.mjs b/module/applications/sheets/items/ancestry.mjs index bd9e3792..de55301d 100644 --- a/module/applications/sheets/items/ancestry.mjs +++ b/module/applications/sheets/items/ancestry.mjs @@ -63,22 +63,8 @@ export default class AncestrySheet extends DHHeritageSheet { event.stopPropagation(); const target = button.closest('.feature-item'); const feature = this.document.system[`${target.dataset.type}Feature`]; - const featureExists = feature && Object.keys(feature).length > 0; - if (featureExists) { - const confirmed = await foundry.applications.api.DialogV2.confirm({ - window: { - title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: game.i18n.localize(`TYPES.Item.feature`), - name: feature.name - }) - }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name }) - }); - if (!confirmed) return; - } - - if (featureExists && target.dataset.type === 'primary') await feature.update({ 'system.primary': null }); + if (feature) await feature.update({ 'system.subType': null }); await this.document.update({ 'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid) }); @@ -94,15 +80,18 @@ export default class AncestrySheet extends DHHeritageSheet { */ async _onDrop(event) { event.stopPropagation(); - event.preventDefault(); - const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const item = await fromUuid(data.uuid); if (item?.type === 'feature') { const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary'; - await item.update({ 'system.subType': subType }); + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes[subType]) { + const error = subType === 'primary' ? 'featureNotPrimary' : 'featureNotSecondary'; + ui.notifications.warn(game.i18n.localize(`DAGGERHEART.UI.Notifications.${error}`)); + return; + } + await item.update({ 'system.subType': subType }); await this.document.update({ 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index b49105be..3a34bcec 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -78,6 +78,7 @@ export default class ClassSheet extends DHBaseItemSheet { /* -------------------------------------------- */ async _onDrop(event) { + event.stopPropagation(); const data = TextEditor.getDragEventData(event); const item = await fromUuid(data.uuid); const target = event.target.closest('fieldset.drop-section'); @@ -87,12 +88,24 @@ export default class ClassSheet extends DHBaseItemSheet { }); } else if (item.type === 'feature') { if (target.classList.contains('hope-feature')) { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.hope) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotHope')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.hope }); await this.document.update({ - 'system.hopeFeatures': [...this.document.system.hopeFeatures.map(x => x.uuid), item.uuid] + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); } else if (target.classList.contains('class-feature')) { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.class) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotClass')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.class }); await this.document.update({ - 'system.classFeatures': [...this.document.system.classFeatures.map(x => x.uuid), item.uuid] + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); } } else if (item.type === 'weapon') { @@ -177,28 +190,25 @@ export default class ClassSheet extends DHBaseItemSheet { doc.sheet.render({ force: true }); } - getActionPath(type) { - return type === 'hope' ? 'hopeFeatures' : 'classFeatures'; - } - static async addFeature(_, target) { - const actionPath = this.getActionPath(target.dataset.type); const feature = await game.items.documentClass.create({ type: 'feature', - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }) + name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }), + system: { + subType: + target.dataset.type === 'hope' + ? CONFIG.DH.ITEM.featureSubTypes.hope + : CONFIG.DH.ITEM.featureSubTypes.class + } }); await this.document.update({ - [`system.${actionPath}`]: [ - ...this.document.system[actionPath].filter(x => x).map(x => x.uuid), - feature.uuid - ] + [`system.features`]: [...this.document.system.features.filter(x => x).map(x => x.uuid), feature.uuid] }); } static async editFeature(_, button) { const target = button.closest('.feature-item'); - const actionPath = this.getActionPath(button.dataset.type); - const feature = this.document.system[actionPath].find(x => x?.id === target.dataset.featureId); + const feature = this.document.system.features.find(x => x?.id === target.dataset.featureId); if (!feature) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing')); return; @@ -210,10 +220,16 @@ export default class ClassSheet extends DHBaseItemSheet { static async deleteFeature(event, button) { event.stopPropagation(); const target = button.closest('.feature-item'); - const actionPath = this.getActionPath(button.dataset.type); + + const feature = this.document.system.features.find( + feature => feature && feature.id === target.dataset.featureId + ); + if (feature) { + await feature.update({ 'system.subType': null }); + } await this.document.update({ - [`system.${actionPath}`]: this.document.system[actionPath] + [`system.features`]: this.document.system.features .filter(feature => feature && feature.id !== target.dataset.featureId) .map(x => x.uuid) }); diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index 31eca43a..fcf62f69 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -40,28 +40,46 @@ export default class SubclassSheet extends DHBaseItemSheet { static async addFeature(_, target) { const feature = await game.items.documentClass.create({ type: 'feature', - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }) + name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }), + system: { + subType: + target.dataset.type === 'foundation' + ? CONFIG.DH.ITEM.featureSubTypes.foundation + : target.dataset.type === 'specialization' + ? CONFIG.DH.ITEM.featureSubTypes.specialization + : CONFIG.DH.ITEM.featureSubTypes.mastery + } }); await this.document.update({ - [`system.${target.dataset.type}`]: feature.uuid + [`system.features`]: [...this.document.system.features.map(x => x.uuid), feature.uuid] }); } static async editFeature(_, button) { - const feature = this.document.system[button.dataset.type]; + const feature = this.document.system.features.find(x => x.id === button.dataset.feature); if (!feature) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing')); return; } + if (feature) { + await feature.update({ 'system.subType': null }); + } + feature.sheet.render(true); } - static async deleteFeature(event, button) { + static async deleteFeature(event, target) { event.stopPropagation(); + const feature = this.document.system.features.find(feature => feature.id === target.dataset.feature); + if (feature) { + await feature.update({ 'system.subType': null }); + } await this.document.update({ - [`system.${button.dataset.type}`]: null + [`system.features`]: this.document.system.features + .filter(feature => feature && feature.id !== target.dataset.feature) + .map(x => x.uuid) }); } @@ -82,18 +100,45 @@ export default class SubclassSheet extends DHBaseItemSheet { } async _onDrop(event) { + event.stopPropagation(); + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); if (data.fromInternal) return; const item = await fromUuid(data.uuid); - if (item?.type === 'feature') { - const dropSection = event.target.closest('.drop-section'); - if (this.document.system[dropSection.dataset.type]) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsFull')); - return; - } + const target = event.target.closest('fieldset.drop-section'); + if (item.type === 'feature') { + if (target.dataset.type === 'foundation') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.foundation) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotFoundation')); + return; + } - await this.document.update({ [`system.${dropSection.dataset.type}`]: item.uuid }); + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.foundation }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } else if (target.dataset.type === 'specialization') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.specialization) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotSpecialization')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.specialization }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } else if (target.dataset.type === 'mastery') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.mastery) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotMastery')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.mastery }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } } } } diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 02cfd4a9..d05dc81c 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -411,7 +411,7 @@ export const levelupData = { }; export const subclassFeatureLabels = { - 1: 'DAGGERHEART.ITEMS.DomainCard.foundation', + 1: 'DAGGERHEART.ITEMS.DomainCard.foundationTitle', 2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle', 3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle' }; diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index ede5ef08..b26a26ca 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -1322,7 +1322,12 @@ export const featureTypes = { export const featureSubTypes = { primary: 'primary', - secondary: 'secondary' + secondary: 'secondary', + hope: 'hope', + class: 'class', + foundation: 'foundation', + specialization: 'specialization', + mastery: 'mastery' }; export const actionTypes = { diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 6b0a2fd7..1c15d036 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -124,12 +124,9 @@ export default class DhCharacter extends BaseDataActor { label: 'DAGGERHEART.GENERAL.Range.other' }) }), - rally: new fields.ArrayField( - new fields.StringField(), - { - label: 'DAGGERHEART.CLASS.Feature.rallyDice' - } - ) + rally: new fields.ArrayField(new fields.StringField(), { + label: 'DAGGERHEART.CLASS.Feature.rallyDice' + }) }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), rules: new fields.SchemaField({ @@ -260,11 +257,11 @@ export default class DhCharacter extends BaseDataActor { classFeatures.push(item); } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { const subclassState = this.class.subclass.system.featureState; - const identifier = item.system.identifier; + const subType = item.system.subType; if ( - identifier === 'foundationFeature' || - (identifier === 'specializationFeature' && subclassState >= 2) || - (identifier === 'masterFeature' && subclassState >= 3) + subType === CONFIG.DH.ITEM.featureSubTypes.foundation || + (subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) || + (subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3) ) { subclassFeatures.push(item); } diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 281b0a48..b8a9ab81 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -27,8 +27,7 @@ export default class DHClass extends BaseDataItem { label: 'DAGGERHEART.GENERAL.hitPoints.plural' }), evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }), - hopeFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }), - classFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }), + features: new ForeignDocumentUUIDArrayField({ type: 'Item' }), subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), inventory: new fields.SchemaField({ take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), @@ -52,12 +51,18 @@ export default class DHClass extends BaseDataItem { }; } - get hopeFeature() { - return this.hopeFeatures.length > 0 ? this.hopeFeatures[0] : null; + get hopeFeatures() { + return ( + this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.hope) ?? + (this.features.filter(x => !x).length > 0 ? {} : null) + ); } - get features() { - return [...this.hopeFeatures.filter(x => x), ...this.classFeatures.filter(x => x)]; + get classFeatures() { + return ( + this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.class) ?? + (this.features.filter(x => !x).length > 0 ? {} : null) + ); } async _preCreate(data, options, user) { diff --git a/module/data/item/domainCard.mjs b/module/data/item/domainCard.mjs index 89bbfb40..df60b9d1 100644 --- a/module/data/item/domainCard.mjs +++ b/module/data/item/domainCard.mjs @@ -29,7 +29,6 @@ export default class DHDomainCard extends BaseDataItem { required: true, initial: CONFIG.DH.DOMAIN.cardTypes.ability.id }), - foundation: new fields.BooleanField({ initial: false }), inVault: new fields.BooleanField({ initial: false }), actions: new fields.ArrayField(new ActionField()) }; diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 265c2566..e0a76092 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -1,4 +1,4 @@ -import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import BaseDataItem from './base.mjs'; export default class DHSubclass extends BaseDataItem { @@ -22,20 +22,22 @@ export default class DHSubclass extends BaseDataItem { nullable: true, initial: null }), - foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }), - specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }), - masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }), + features: new ForeignDocumentUUIDArrayField({ type: 'Item' }), featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }), isMulticlass: new fields.BooleanField({ initial: false }) }; } - get features() { - return [ - { ...this.foundationFeature?.toObject(), identifier: 'foundationFeature' }, - { ...this.specializationFeature?.toObject(), identifier: 'specializationFeature' }, - { ...this.masteryFeature?.toObject(), identifier: 'masteryFeature' } - ]; + get foundationFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.foundation); + } + + get specializationFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.specialization); + } + + get masteryFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.mastery); } async _preCreate(data, options, user) { diff --git a/templates/sheets/items/domainCard/settings.hbs b/templates/sheets/items/domainCard/settings.hbs index 2faa6934..5518b4c3 100644 --- a/templates/sheets/items/domainCard/settings.hbs +++ b/templates/sheets/items/domainCard/settings.hbs @@ -8,8 +8,6 @@ {{localize "DAGGERHEART.GENERAL.type"}} {{formField systemFields.type value=source.system.type localize=true}} - {{localize "DAGGERHEART.ITEMS.DomainCard.foundation"}} - {{formField systemFields.foundation value=source.system.foundation }} {{localize "DAGGERHEART.GENERAL.Domain.single"}} {{formField systemFields.domain value=source.system.domain localize=true}} {{localize "DAGGERHEART.GENERAL.level"}} diff --git a/templates/sheets/items/subclass/features.hbs b/templates/sheets/items/subclass/features.hbs index d2424d01..1a75974e 100644 --- a/templates/sheets/items/subclass/features.hbs +++ b/templates/sheets/items/subclass/features.hbs @@ -3,42 +3,42 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > -
+
{{localize "DAGGERHEART.GENERAL.Tabs.foundation"}} - +
- {{#if source.system.foundationFeature}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='foundationFeature' feature=source.system.foundationFeature}} - {{/if}} + {{#each source.system.foundationFeatures as | feature | }} + {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='foundation' feature=feature}} + {{/each}}
-
+
{{localize "DAGGERHEART.GENERAL.Tabs.specialization"}} - +
- {{#if source.system.specializationFeature}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='specializationFeature' feature=source.system.specializationFeature}} - {{/if}} + {{#each source.system.specializationFeatures as | feature |}} + {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='specialization' feature=feature}} + {{/each}}
-
+
{{localize "DAGGERHEART.GENERAL.Tabs.mastery"}} - +
- {{#if source.system.masteryFeature}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='masteryFeature' feature=source.system.masteryFeature}} - {{/if}} + {{#each source.system.masteryFeatures as | feature |}} + {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='mastery' feature=feature}} + {{/each}}
\ No newline at end of file