From 523ecb506b8f4b91d97f71b2859c7b36c36b0b79 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 22 Aug 2025 01:38:07 +0200 Subject: [PATCH] [Fix] ItemLink Fix (#1032) * . * . * Removed outcommented code * Raised to minor version * Added confirmation on import of old character data --- daggerheart.mjs | 3 ++ lang/en.json | 4 +- .../sheets/api/application-mixin.mjs | 1 - module/applications/sheets/api/base-item.mjs | 9 ++-- module/config/settingsConfig.mjs | 3 +- module/data/actor/character.mjs | 12 ++--- module/data/item/base.mjs | 50 ++++++------------- module/data/item/feature.mjs | 18 ------- module/documents/actor.mjs | 42 +++++++++++----- module/helpers/utils.mjs | 11 ++++ module/systemRegistration/_module.mjs | 1 + module/systemRegistration/migrations.mjs | 41 +++++++++++++++ module/systemRegistration/settings.mjs | 6 +++ system.json | 2 +- 14 files changed, 122 insertions(+), 81 deletions(-) create mode 100644 module/systemRegistration/migrations.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index 795764cc..96a6783b 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -13,6 +13,7 @@ import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs' import { registerCountdownHooks } from './module/data/countdowns.mjs'; import { handlebarsRegistration, + runMigrations, settingsRegistration, socketRegistration } from './module/systemRegistration/_module.mjs'; @@ -168,6 +169,8 @@ Hooks.on('ready', async () => { game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage, true); } } + + runMigrations(); }); Hooks.once('dicesoniceready', () => {}); diff --git a/lang/en.json b/lang/en.json index 7daf6b14..6a34037d 100755 --- a/lang/en.json +++ b/lang/en.json @@ -193,7 +193,9 @@ "companionLevelup": { "confirmTitle": "Companion Levelup", "confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)" - } + }, + "InvalidOldCharacterImportTitle": "Old Character Import", + "InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?" }, "Companion": { "FIELDS": { diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index ab1c9ab2..bdcee27a 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -591,7 +591,6 @@ export default function DHApplicationMixin(Base) { if (featureOnCharacter) { systemData = { originItemType: this.document.type, - originId: this.document.id, identifier: this.document.system.isMulticlass ? 'multiclass' : null }; } diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index 9d7df6ee..e722b72e 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -149,12 +149,12 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { const { type } = target.dataset; const cls = foundry.documents.Item.implementation; + const multiclass = this.document.system.isMulticlass ? 'multiclass' : null; let systemData = {}; if (this.document.parent?.type === 'character') { systemData = { originItemType: this.document.type, - originId: this.document.id, - identifier: this.document.system.isMulticlass ? 'multiclass' : null + identifier: multiclass ?? type }; } @@ -275,14 +275,15 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { if (this.document.parent?.type === 'character') { const itemData = item.toObject(); + const multiclass = this.document.system.isMulticlass ? 'multiclass' : null; item = await cls.create( { ...itemData, + _stats: { compendiumSource: this.document.uuid }, system: { ...itemData.system, originItemType: this.document.type, - originId: this.document.id, - identifier: this.document.system.isMulticlass ? 'multiclass' : null + identifier: multiclass ?? target.dataset.type } }, { parent: this.document.parent } diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index dd8aeffe..76234a97 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -26,5 +26,6 @@ export const gameSettings = { Fear: 'ResourcesFear' }, LevelTiers: 'LevelTiers', - Countdowns: 'Countdowns' + Countdowns: 'Countdowns', + LastMigrationVersion: 'LastMigrationVersion' }; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index bb6a8c65..7dd7993c 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -444,16 +444,12 @@ export default class DhCharacter extends BaseDataActor { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { if (this.class.subclass) { const subclassState = this.class.subclass.system.featureState; - const subclass = - item.system.identifier === 'multiclass' ? this.multiclass.subclass : this.class.subclass; - const featureType = subclass - ? (subclass.system.features.find(x => x.item?.uuid === item.uuid)?.type ?? null) - : null; if ( - featureType === CONFIG.DH.ITEM.featureSubTypes.foundation || - (featureType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) || - (featureType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3) + item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation || + (item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization && + subclassState >= 2) || + (item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3) ) { subclassFeatures.push(item); } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index f0b22b44..f333537b 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -144,50 +144,30 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { } if (this.actor && this.actor.type === 'character' && this.features) { - const featureUpdates = {}; + 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 createData = foundry.utils.mergeObject( - feature.toObject(), - { - system: { - originItemType: this.parent.type, - originId: data._id, - identifier: this.isMulticlass ? 'multiclass' : null - } - }, - { inplace: false } + const multiclass = this.isMulticlass ? 'multiclass' : null; + features.push( + foundry.utils.mergeObject( + feature.toObject(), + { + _stats: { compendiumSource: fBase.uuid }, + system: { + originItemType: this.parent.type, + identifier: multiclass ?? (f.item ? f.type : null) + } + }, + { inplace: false } + ) ); - const [doc] = await this.actor.createEmbeddedDocuments('Item', [createData]); - - if (!featureUpdates.features) - featureUpdates.features = this.features.map(x => (x.item ? { ...x, item: x.item.uuid } : x.uuid)); - - if (f.item) { - const existingFeature = featureUpdates.features.find(x => x.item === f.item.uuid); - existingFeature.item = doc.uuid; - } else { - const replaceIndex = featureUpdates.features.findIndex(x => x === f.uuid); - featureUpdates.features.splice(replaceIndex, 1, doc.uuid); - } } - await this.updateSource(featureUpdates); + await this.actor.createEmbeddedDocuments('Item', features); } } - async _preDelete() { - if (!this.actor || this.actor.type !== 'character') return; - - const items = this.actor.items.filter(item => item.system.originId === this.parent.id); - if (items.length > 0) - await this.actor.deleteEmbeddedDocuments( - 'Item', - items.map(x => x.id) - ); - } - async _preUpdate(changed, options, userId) { const allowed = await super._preUpdate(changed, options, userId); if (allowed === false) return false; diff --git a/module/data/item/feature.mjs b/module/data/item/feature.mjs index 13c1d149..6e1aab41 100644 --- a/module/data/item/feature.mjs +++ b/module/data/item/feature.mjs @@ -1,5 +1,4 @@ import BaseDataItem from './base.mjs'; -import { ActionField, ActionsField } from '../fields/actionField.mjs'; export default class DHFeature extends BaseDataItem { /** @inheritDoc */ @@ -30,24 +29,7 @@ export default class DHFeature extends BaseDataItem { nullable: true, initial: null }), - originId: new fields.StringField({ nullable: true, initial: null }), identifier: new fields.StringField() }; } - - get spellcastingModifier() { - let traitValue = 0; - if (this.actor && this.originId && ['class', 'subclass'].includes(this.originItemType)) { - if (this.originItemType === 'subclass') { - traitValue = - this.actor.system.traits[this.actor.items.get(this.originId).system.spellcastingTrait]?.value ?? 0; - } else { - const { value: multiclass, subclass } = this.actor.system.multiclass; - const selectedSubclass = multiclass?.id === this.originId ? subclass : this.actor.system.class.subclass; - traitValue = this.actor.system.traits[selectedSubclass.system.spellcastingTrait]?.value ?? 0; - } - } - - return traitValue; - } } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 4a6d2d67..e1dd93af 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -1,7 +1,7 @@ import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; import { LevelOptionType } from '../data/levelTier.mjs'; import DHFeature from '../data/item/feature.mjs'; -import { damageKeyToNumber } from '../helpers/utils.mjs'; +import { damageKeyToNumber, versionCompare } from '../helpers/utils.mjs'; import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs'; export default class DhpActor extends Actor { @@ -27,7 +27,7 @@ export default class DhpActor extends Actor { /** @inheritDoc */ static migrateData(source) { - if(source.system?.attack && !source.system.attack.type) source.system.attack.type = "attack"; + if (source.system?.attack && !source.system.attack.type) source.system.attack.type = 'attack'; return super.migrateData(source); } @@ -571,19 +571,15 @@ export default class DhpActor extends Actor { if (armorSlotResult) { const { modifiedDamage, armorSpent, stressSpent } = armorSlotResult; updates.find(u => u.key === 'hitPoints').value = modifiedDamage; - if(armorSpent) { + if (armorSpent) { const armorUpdate = updates.find(u => u.key === 'armor'); - if(armorUpdate) - armorUpdate.value += armorSpent; - else - updates.push({ value: armorSpent, key: 'armor' }); + if (armorUpdate) armorUpdate.value += armorSpent; + else updates.push({ value: armorSpent, key: 'armor' }); } - if(stressSpent) { + if (stressSpent) { const stressUpdate = updates.find(u => u.key === 'stress'); - if(stressUpdate) - stressUpdate.value += stressSpent; - else - updates.push({ value: stressSpent, key: 'stress' }); + if (stressUpdate) stressUpdate.value += stressSpent; + else updates.push({ value: stressSpent, key: 'stress' }); } } } @@ -753,4 +749,26 @@ export default class DhpActor extends Actor { } } } + + /** @inheritdoc */ + async importFromJSON(json) { + if (!this.type === 'character') return await super.importFromJSON(json); + + if (!CONST.WORLD_DOCUMENT_TYPES.includes(this.documentName)) { + throw new Error('Only world Documents may be imported'); + } + + const parsedJSON = JSON.parse(json); + if (versionCompare(parsedJSON._stats.systemVersion, '1.1.0')) { + const confirmed = await foundry.applications.api.DialogV2.confirm({ + window: { + title: game.i18n.localize('DAGGERHEART.ACTORS.Character.InvalidOldCharacterImportTitle') + }, + content: game.i18n.localize('DAGGERHEART.ACTORS.Character.InvalidOldCharacterImportText') + }); + if (!confirmed) return; + } + + return await super.importFromJSON(json); + } } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 6f4e5a26..c8f4186f 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -420,3 +420,14 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) { export const slugify = name => { return name.toLowerCase().replaceAll(' ', '-').replaceAll('.', ''); }; + +export const versionCompare = (current, target) => { + const currentSplit = current.split('.').map(x => Number.parseInt(x)); + const targetSplit = target.split('.').map(x => Number.parseInt(x)); + for (var i = 0; i < currentSplit.length; i++) { + if (currentSplit[i] < targetSplit[i]) return true; + if (currentSplit[i] > targetSplit[i]) return false; + } + + return false; +}; diff --git a/module/systemRegistration/_module.mjs b/module/systemRegistration/_module.mjs index 38dcac4f..483a6b37 100644 --- a/module/systemRegistration/_module.mjs +++ b/module/systemRegistration/_module.mjs @@ -1,3 +1,4 @@ export { preloadHandlebarsTemplates as handlebarsRegistration } from './handlebars.mjs'; export * as settingsRegistration from './settings.mjs'; export * as socketRegistration from './socket.mjs'; +export { runMigrations } from './migrations.mjs'; diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs new file mode 100644 index 00000000..e84018fa --- /dev/null +++ b/module/systemRegistration/migrations.mjs @@ -0,0 +1,41 @@ +import { versionCompare } from '../helpers/utils.mjs'; + +export async function runMigrations() { + let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion); + if (!lastMigrationVersion) lastMigrationVersion = '1.0.6'; + + if (versionCompare(lastMigrationVersion, '1.1.0')) { + const compendiumActors = []; + for (let pack of game.packs) { + const documents = await pack.getDocuments(); + compendiumActors.push(...documents.filter(x => x.type === 'character')); + } + + [...compendiumActors, ...game.actors].forEach(actor => { + const items = actor.items.reduce((acc, item) => { + if (item.type === 'feature') { + const { originItemType, isMulticlass, identifier } = item.system; + const base = originItemType + ? actor.items.find( + 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); + if (feature && identifier !== 'multiclass') { + acc.push({ _id: item.id, system: { identifier: feature.type } }); + } + } + } + + return acc; + }, []); + + actor.updateEmbeddedDocuments('Item', items); + }); + + lastMigrationVersion = '1.1.0'; + } + + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion); +} diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index 3d9ffc19..4828ebb0 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -91,6 +91,12 @@ const registerMenus = () => { }; const registerNonConfigSettings = () => { + game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, { + scope: 'world', + config: false, + type: String + }); + game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers, { scope: 'world', config: false, diff --git a/system.json b/system.json index e6b7650f..d72c53d5 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.6", + "version": "1.1.0", "compatibility": { "minimum": "13", "verified": "13.347",