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/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/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/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) => { 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', 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 90937db4..7bc5fa25 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -449,14 +449,9 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) { effects: data.effects?.map(effect => effect.toObject()) }); } - 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) { 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" 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 @@