diff --git a/lang/en.json b/lang/en.json index 0c7c0649..a4c2511c 100755 --- a/lang/en.json +++ b/lang/en.json @@ -786,6 +786,7 @@ "error": { "domainCardWrongDomain": "You don't have access to that Domain", "domainCardToHighLevel": "The Domain Card is too high level to be selected", + "domainCardDuplicate": "You already have that domain card!", "noSelectionsLeft": "Nothing more to select!", "alreadySelectedClass": "You already have that class!" } diff --git a/module/applications/levelup.mjs b/module/applications/levelup.mjs index 06780fcb..82ca528b 100644 --- a/module/applications/levelup.mjs +++ b/module/applications/levelup.mjs @@ -32,7 +32,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) actions: { save: this.save, viewCompendium: this.viewCompendium, - selectPreview: this.selectPreview + selectPreview: this.selectPreview, + selectDomain: this.selectDomain }, form: { handler: this.updateForm, @@ -100,13 +101,17 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const traits = Object.values(context.advancementChoices.trait ?? {}); context.traits = { - values: traits.filter(trait => trait.data.length > 0).flatMap(trait => trait.data), - active: traits.length > 0 + values: traits.filter(trait => !trait.locked && trait.data.length > 0).flatMap(trait => trait.data), + active: traits.length > 0 && traits.filter(trait => !trait.locked).length > 0 }; - const experienceIncreases = Object.values(context.advancementChoices.experience ?? {}); + const experienceIncreases = Object.values(context.advancementChoices.experience ?? {}).filter( + x => !x.locked + ); context.experienceIncreases = { - values: experienceIncreases.filter(trait => trait.data.length > 0).flatMap(trait => trait.data), + values: experienceIncreases + .filter(trait => !trait.locked && trait.data.length > 0) + .flatMap(trait => trait.data), active: experienceIncreases.length > 0 }; @@ -127,6 +132,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) context.domainCards = []; for (var domainCard of allDomainCardValues) { + if (domainCard.locked) continue; + const uuid = domainCard.data?.length > 0 ? domainCard.data[0] : domainCard.uuid; const card = uuid ? await foundry.utils.fromUuid(uuid) : { path: domainCard.path }; context.domainCards.push({ @@ -135,7 +142,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) 'DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', { level: domainCard.level } ), - limit: domainCard.level + limit: domainCard.level, + compendium: 'domains' }); } @@ -165,12 +173,13 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const multiclasses = Object.values(context.advancementChoices.multiclass ?? {}); if (multiclasses?.[0]) { const data = multiclasses[0]; - const path = `tiers.${data.tier}.levels.${data.level}.optionSelections.${data.optionKey}.${data.checkboxNr}.data`; - const multiclass = - data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : { path: path }; + const path = `tiers.${data.tier}.levels.${data.level}.optionSelections.${data.optionKey}.${data.checkboxNr}`; + const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {}; context.multiclass = { ...(multiclass.toObject?.() ?? multiclass), + uuid: multiclass.uuid, + path: path, domains: multiclass?.system?.domains.map(key => { const domain = domains[key]; @@ -179,7 +188,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) return { ...domain, selected: key === data.secondaryData, - disabled: (key !== data.secondaryData && data.secondaryData) || alreadySelected + disabled: (data.secondaryData && key !== data.secondaryData) || alreadySelected }; }) ?? [], compendium: 'classes', @@ -267,7 +276,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) tagifyUpdate = type => async (_, { option, removed }) => { - /* Needs to take Amount into account to allow multiple to be stored in the same option. Array structure? */ const updatePath = this.levelup.selectionData.reduce((acc, data) => { if (data.optionKey === type && removed ? data.data.includes(option) : data.data.length < data.amount) { return `tiers.${data.tier}.levels.${data.level}.optionSelections.${data.optionKey}.${data.checkboxNr}.data`; @@ -298,7 +306,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) async _onDrop(event) { const data = foundry.applications.ux.TextEditor.getDragEventData(event); const item = await fromUuid(data.uuid); - if (event.target.parentElement?.classList?.contains('domain-cards')) { + if (event.target.closest('.domain-cards')) { + const target = event.target.closest('.card-preview-container'); if (item.type === 'domainCard') { if (!this.actor.system.class.system.domains.includes(item.system.domain)) { // Also needs to check for multiclass adding a new domain @@ -308,17 +317,28 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) return; } - if (item.system.level > Number(event.target.dataset.limit)) { + if (item.system.level > Number(target.dataset.limit)) { ui.notifications.error( game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel') ); return; } - await this.levelup.updateSource({ [event.target.dataset.path]: item.uuid }); + if ( + Object.values(this.levelup.domainCards).some(x => x.uuid === item.uuid) || + this.levelup.selectionData.some(x => x.type === 'domainCard' && x.data.includes(item.uuid)) + ) { + ui.notifications.error( + game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardDuplicate') + ); + return; + } + + await this.levelup.updateSource({ [target.dataset.path]: item.uuid }); this.render(); } - } else if (event.target.parentElement?.classList?.contains('multiclass-cards')) { + } else if (event.target.closest('.multiclass-cards')) { + const target = event.target.closest('.card-preview-container'); if (item.type === 'class') { if (item.name === this.actor.system.class.name) { ui.notifications.error( @@ -326,7 +346,13 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) ); return; } - await this.levelup.updateSource({ [event.target.dataset.path]: item.uuid }); + + await this.levelup.updateSource({ + [target.dataset.path]: { + data: item.uuid, + secondaryData: null + } + }); this.render(); } } @@ -336,7 +362,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) event.stopPropagation(); const button = event.currentTarget; - // const advancementSelections = this.getAdvancementSelectionUpdates(button); if (!button.checked) { await this.levelup.updateSource({ [`tiers.${button.dataset.tier}.levels.${button.dataset.level}.optionSelections.${button.dataset.option}.-=${button.dataset.checkboxNr}`]: @@ -397,15 +422,20 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const option = remove ? selectionData.find(x => x.type === 'subclass' && x.data.includes(button.dataset.uuid)) : selectionData.find(x => x.type === 'subclass' && x.data.length === 0); - if (!option) { - return; // Notification? - } + if (!option) return; const path = `tiers.${option.tier}.levels.${option.level}.optionSelections.${option.optionKey}.${option.checkboxNr}.data`; await this.levelup.updateSource({ [path]: remove ? [] : button.dataset.uuid }); this.render(); } + static async selectDomain(_, button) { + const option = this.levelup.selectionData.find(x => x.type === 'multiclass'); + const path = `tiers.${option.tier}.levels.${option.level}.optionSelections.${option.optionKey}.${option.checkboxNr}.secondaryData`; + await this.levelup.updateSource({ [path]: option.secondaryData ? null : button.dataset.domain }); + this.render(); + } + static async save() { await this.actor.levelUp(this.levelup.levelupData); this.close(); diff --git a/module/data/levelup.mjs b/module/data/levelup.mjs index b7d8f8df..b4f9ecab 100644 --- a/module/data/levelup.mjs +++ b/module/data/levelup.mjs @@ -46,7 +46,16 @@ export class DhLevelup extends foundry.abstract.DataModel { const domainCards = Object.keys(tiers).reduce((acc, tierKey) => { const tier = tiers[tierKey]; for (var level of tier.belongingLevels) { - if (level <= pcLevelData.level.changed) { + if (level <= pcLevelData.level.current) { + const cardId = foundry.utils.randomID(); + acc[cardId] = { + ...pcLevelData.levelups[level].domainCards[0], + path: `domainCards.${cardId}.uuid`, + locked: true, + level: level, + tier: tierKey + }; + } else if (level <= pcLevelData.level.changed) { for (var domainCardSlot = 1; domainCardSlot <= tier.domainCardByLevel; domainCardSlot++) { const cardId = foundry.utils.randomID(); acc[cardId] = { @@ -103,7 +112,8 @@ export class DhLevelup extends foundry.abstract.DataModel { tier: new fields.NumberField({ required: true, integer: true }), level: new fields.NumberField({ required: true, integer: true }), domainCardSlot: new fields.NumberField({ required: true, integer: true }), - path: new fields.StringField({ required: true }) + path: new fields.StringField({ required: true }), + locked: new fields.BooleanField({ required: true, initial: false }) }) ), progressionLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })) @@ -181,7 +191,8 @@ export class DhLevelup extends foundry.abstract.DataModel { value: optionSelect.value, amount: optionSelect.amount, data: selectionObj.data, - secondaryData: selectionObj.secondaryData + secondaryData: selectionObj.secondaryData, + locked: selectionObj.locked }; }); }); @@ -191,6 +202,7 @@ export class DhLevelup extends foundry.abstract.DataModel { get levelupData() { const leveledSelections = this.selectionData.reduce((acc, data) => { + if (data.type === 'domainCard' && data.locked) return acc; if (!acc[data.level]) acc[data.level] = [data]; else acc[data.level].push(data); @@ -198,13 +210,15 @@ export class DhLevelup extends foundry.abstract.DataModel { }, {}); return this.progressionLevels.reduce((acc, level) => { acc[level] = { - achievements: { - experiences: this.allInitialAchievements[level].newExperiences, - proficiency: this.allInitialAchievements[level].proficiency - }, - domainCards: Object.values(this.domainCards).map(card => ({ ...card })), + domainCards: Object.values(this.domainCards).filter(x => !x.locked && x.level === level), selections: leveledSelections[level] }; + if (this.allInitialAchievements[level]) { + acc[level].achievements = { + experiences: this.allInitialAchievements[level].newExperiences, + proficiency: this.allInitialAchievements[level].proficiency + }; + } return acc; }, {}); @@ -348,8 +362,9 @@ class DhLevelupLevel extends foundry.abstract.DataModel { optionSelections: levelData.reduce((acc, data) => { if (!acc[data.optionKey]) acc[data.optionKey] = {}; acc[data.optionKey][data.checkboxNr] = { + ...data, minCost: optionSelections[data.optionKey].minCost, - minCost: optionSelections[data.optionKey].amount, + amount: optionSelections[data.optionKey].amount, locked: locked }; diff --git a/module/data/pc.mjs b/module/data/pc.mjs index 29c23777..87f8db8c 100644 --- a/module/data/pc.mjs +++ b/module/data/pc.mjs @@ -447,7 +447,8 @@ class DhPCLevelData extends foundry.abstract.DataModel { ), domainCards: new fields.ArrayField( new fields.SchemaField({ - uuid: new fields.StringField({ required: true }) + uuid: new fields.StringField({ required: true }), + itemUuid: new fields.StringField({ required: true }) }) ), selections: new fields.ArrayField( @@ -459,7 +460,8 @@ class DhPCLevelData extends foundry.abstract.DataModel { checkboxNr: new fields.NumberField({ required: true, integer: true }), value: new fields.NumberField({ integer: true }), amount: new fields.NumberField({ integer: true }), - data: new fields.ArrayField(new fields.StringField({ required: true })) + data: new fields.ArrayField(new fields.StringField({ required: true })), + uuid: new fields.StringField({ required: true }) }) ) }) diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 97ccb1db7..df5243f8 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -6,13 +6,16 @@ import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; export default class DhpActor extends Actor { async _preCreate(data, options, user) { - if ( (await super._preCreate(data, options, user)) === false ) return false; - + if ((await super._preCreate(data, options, user)) === false) return false; + // Configure prototype token settings const prototypeToken = {}; - if ( this.type === "pc" ) Object.assign(prototypeToken, { - sight: { enabled: true }, actorLink: true, disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY - }); + if (this.type === 'pc') + Object.assign(prototypeToken, { + sight: { enabled: true }, + actorLink: true, + disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY + }); this.updateSource({ prototypeToken }); } @@ -37,7 +40,7 @@ export default class DhpActor extends Actor { ); for (var domainCard of changes.domainCards) { - const uuid = domainCard.uuid ? domainCard.uuid : domainCard.data; + const uuid = domainCard.itemUuid ? domainCard.itemUuid : domainCard.uuid; const itemCard = await this.items.find(x => x.uuid === uuid); itemCard.delete(); } @@ -56,6 +59,14 @@ export default class DhpActor extends Actor { } } + for (var subclass of changes.subclasses) { + /* Implemented after datamodel rework is in */ + } + + if (changes.multiclass) { + /* Implemented after datamodel rework is in */ + } + const newLevelData = { level: { current: newLevel, @@ -98,11 +109,13 @@ export default class DhpActor extends Actor { multiclass: null, subclasses: [], traits: [], - experiences: [], + experiences: {}, experienceIncreases: [] }; for (var level = startLevel + 1; level <= endLevel; level++) { + if (!levelData[level]) continue; + const achievements = levelData[level].achievements; const selections = levelData[level].selections.reduce((acc, selection) => { if (!acc[selection.type]) acc[selection.type] = [selection]; @@ -122,16 +135,27 @@ export default class DhpActor extends Actor { : 0; changedFeatures.proficiency += (achievements?.proficiency ?? 0) + - (selections.evasion ? selections.evasion.reduce((acc, evasion) => acc + Number(evasion.value), 0) : 0); - changedFeatures.domainCards = [ - ...levelData[level].domainCards, - ...(selections.domainCard.flatMap(x => x.data.map(data => ({ ...x, data: data }))) ?? []) - ]; - changedFeatures.traits = selections.trait ? selections.trait.flatMap(x => x.data) : []; - changedFeatures.experiences = achievements?.experiences ? achievements.experiences : {}; - changedFeatures.experienceIncreases = selections.experience ?? []; - changedFeatures.subclasses = selections.subclasses ? [] : []; - changedFeatures.multiclass = selections.multiclass ? [] : []; + (selections.proficiency + ? selections.proficiency.reduce((acc, proficiency) => acc + Number(proficiency.value), 0) + : 0); + changedFeatures.domainCards.push( + ...[ + ...levelData[level].domainCards, + ...(selections.domainCard?.flatMap(x => x.data.map(data => ({ ...x, data: data }))) ?? []) + ] + ); + changedFeatures.traits.push(...(selections.trait ? selections.trait.flatMap(x => x.data) : [])); + changedFeatures.experiences = Object.keys(achievements?.experiences ? achievements.experiences : {}).reduce( + (acc, key) => { + acc[key] = achievements.experiences[key]; + + return acc; + }, + changedFeatures.experiences + ); + changedFeatures.experienceIncreases.push(...(selections.experience ?? [])); + changedFeatures.subclasses.push(...(selections.subclasses ? [] : [])); + changedFeatures.multiclass = selections.multiclass ? selections.multiclass[0] : null; } return changedFeatures; @@ -147,12 +171,13 @@ export default class DhpActor extends Actor { for (var card of changes.domainCards) { const fromAchievement = Boolean(card.uuid); const domainCard = await foundry.utils.fromUuid(fromAchievement ? card.uuid : card.data); - const newCard = (await this.createEmbeddedDocuments('Item', [domainCard]))[0]; + const createdCards = await this.createEmbeddedDocuments('Item', [domainCard]); + const newCard = createdCards[0]; if (fromAchievement) { const levelupCard = levelupData[card.level].domainCards.find( x => x.tier === card.tier && x.level === card.level ); - if (levelupCard) levelupCard.uuid = newCard.uuid; + if (levelupCard) levelupCard.itemUuid = newCard.uuid; } else { const levelupCard = levelupData[card.level].selections.find( x => @@ -161,7 +186,7 @@ export default class DhpActor extends Actor { x.optionKey === card.optionKey && x.checkboxNr === card.checkboxNr ); - if (levelupCard) levelupCard.data.findSplice(x => x === card.data, newCard.uuid); + if (levelupCard) levelupCard.uuid = newCard.uuid; } } @@ -184,6 +209,14 @@ export default class DhpActor extends Actor { } } + for (var subclass of changes.subclasses) { + /* Implemented after datamodel rework is in */ + } + + if (changes.multiclass) { + /* Implemented after datamodel rework is in */ + } + await this.update({ system: { 'traits': traitsUpdate, diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 1d705e64..fbc1aa31 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -2837,7 +2837,7 @@ div.daggerheart.views.multiclass { pointer-events: none; opacity: 0.4; } -.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container div { +.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container .levelup-domain-label { position: absolute; text-align: center; top: 4px; @@ -2848,6 +2848,24 @@ div.daggerheart.views.multiclass { .daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container img { height: 124px; } +.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container .levelup-domain-selected { + position: absolute; + height: 54px; + width: 54px; + border-radius: 50%; + border: 2px solid; + font-size: 48px; + 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); +} +.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container .levelup-domain-selected i { + position: relative; + right: 2px; +} .daggerheart.levelup .levelup-summary-container .level-achievements-container { display: flex; flex-direction: column; diff --git a/styles/levelup.less b/styles/levelup.less index 80554d39..622a979c 100644 --- a/styles/levelup.less +++ b/styles/levelup.less @@ -147,7 +147,7 @@ opacity: 0.4; } - div { + .levelup-domain-label { position: absolute; text-align: center; top: 4px; @@ -159,6 +159,26 @@ img { height: 124px; // Can it be dynamically sized? Won't follow any window resizing like this. } + + .levelup-domain-selected { + position: absolute; + height: 54px; + width: 54px; + border-radius: 50%; + border: 2px solid; + font-size: 48px; + 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); + + i { + position: relative; + right: 2px; + } + } } } } diff --git a/templates/views/levelup/tabs/selections.hbs b/templates/views/levelup/tabs/selections.hbs index 804ae031..b3eff3a6 100644 --- a/templates/views/levelup/tabs/selections.hbs +++ b/templates/views/levelup/tabs/selections.hbs @@ -71,9 +71,14 @@ {{> "systems/daggerheart/templates/components/card-preview.hbs" img=this.multiclass.img name=this.multiclass.name path=this.multiclass.path compendium=this.multiclass.compendium }}