From ec3aecfe99c984a870adc2604f1733a0636df2d0 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Fri, 13 Jun 2025 14:15:02 +0200 Subject: [PATCH] Levelup Followup (#126) * Levelup applies bonuses to character * Added visualisation of domain card levels * Fixed domaincard level max for selections in a tier * A trait can now only be level up once within the same tier --- lang/en.json | 11 +- module/applications/levelup.mjs | 216 +++++++++++++----- module/applications/sheets/items/class.mjs | 50 +--- module/config/actorConfig.mjs | 6 + module/config/domainConfig.mjs | 18 +- module/data/actor/character.mjs | 78 ++++++- module/data/item/class.mjs | 1 - module/data/item/domainCard.mjs | 9 +- module/data/item/subclass.mjs | 23 +- module/data/levelup.mjs | 65 ++++-- module/documents/actor.mjs | 173 ++++++++++++-- module/helpers/utils.mjs | 22 +- styles/daggerheart.css | 1 + styles/less/global/elements.less | 1 + templates/components/card-preview.hbs | 2 +- templates/sheets/parts/attributes.hbs | 10 +- templates/sheets/parts/defense.hbs | 2 +- templates/sheets/parts/experience.hbs | 2 +- templates/sheets/parts/health.hbs | 10 +- templates/sheets/parts/weapons.hbs | 2 +- .../levelup/parts/selectable-card-preview.hbs | 7 +- templates/views/levelup/tabs/selections.hbs | 26 ++- templates/views/levelup/tabs/summary.hbs | 33 ++- 23 files changed, 566 insertions(+), 202 deletions(-) diff --git a/lang/en.json b/lang/en.json index 00064585..0b43ee8f 100755 --- a/lang/en.json +++ b/lang/en.json @@ -352,30 +352,39 @@ }, "Domains": { "Arcana": { + "label": "Arcana", "Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled." }, "Blade": { + "label": "Blade", "Description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death." }, "Bone": { + "label": "Bone", "Description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training." }, "Codex": { + "label": "Codex", "Description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge." }, "Grace": { + "label": "Grace", "Description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language." }, "Midnight": { + "label": "Midnight", "Description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas." }, "Sage": { + "label": "Sage", "Description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator." }, "Splendor": { + "label": "Splendor", "Description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life." }, "Valor": { + "label": "Valor", "Description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others." } }, @@ -834,7 +843,7 @@ "content": "Returning to the previous level selection will remove all selections made for this level. Do you want to proceed?" }, "Selections": { - "emptyDomainCardHint": "Domain Card Level {level} or below" + "emptyDomainCardHint": "{domain} level {level} or below" }, "summary": { "levelAchievements": "Level Achievements", diff --git a/module/applications/levelup.mjs b/module/applications/levelup.mjs index 61a85677..3094c5bb 100644 --- a/module/applications/levelup.mjs +++ b/module/applications/levelup.mjs @@ -1,4 +1,4 @@ -import { abilities } from '../config/actorConfig.mjs'; +import { abilities, subclassFeatureLabels } from '../config/actorConfig.mjs'; import { domains } from '../config/domainConfig.mjs'; import { DhLevelup } from '../data/levelup.mjs'; import { getDeleteKeys, tagifyElement } from '../helpers/utils.mjs'; @@ -35,6 +35,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) viewCompendium: this.viewCompendium, selectPreview: this.selectPreview, selectDomain: this.selectDomain, + selectSubclass: this.selectSubclass, updateCurrentLevel: this.updateCurrentLevel, activatePart: this.activatePart }, @@ -178,6 +179,20 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) }; const allDomainCardKeys = Object.keys(allDomainCards); + const classDomainsData = this.actor.system.class.value.system.domains.map(domain => ({ + domain, + multiclass: false + })); + const multiclassDomainsData = (this.actor.system.multiclass?.value?.system?.domains ?? []).map( + domain => ({ domain, multiclass: true }) + ); + const domainsData = [...classDomainsData, ...multiclassDomainsData]; + const multiclassDomain = this.levelup.classUpgradeChoices?.multiclass?.domain; + if (multiclassDomain) { + if (!domainsData.some(x => x.domain === multiclassDomain)) + domainsData.push({ domain: multiclassDomain, multiclass: true }); + } + context.domainCards = []; for (var key of allDomainCardKeys) { const domainCard = allDomainCards[key]; @@ -188,35 +203,56 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) context.domainCards.push({ ...(card.toObject?.() ?? card), - emptySubtext: game.i18n.format( - 'DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', - { level: domainCard.level } - ), + emptySubtexts: domainsData.map(domain => { + const levelBase = domain.multiclass + ? Math.ceil(this.levelup.currentLevel / 2) + : this.levelup.currentLevel; + const levelMax = domainCard.secondaryData?.limit + ? Math.min(domainCard.secondaryData.limit, levelBase) + : levelBase; + + return game.i18n.format('DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', { + domain: game.i18n.localize(domains[domain.domain].label), + level: levelMax + }); + }), path: domainCard.data ? `${domainCard.path}.data` : `levels.${domainCard.level}.achievements.domainCards.${key}.uuid`, - limit: domainCard.level, + limit: domainCard.secondaryData?.limit ?? null, compendium: 'domains' }); } const subclassSelections = advancementChoices.subclass?.flatMap(x => x.data) ?? []; + const possibleSubclasses = [this.actor.system.class.subclass]; + if (this.actor.system.multiclass?.subclass) { + possibleSubclasses.push(this.actor.system.multiclass.subclass); + } - const multiclassSubclass = this.actor.system.multiclass?.system?.subclasses?.[0]; - const possibleSubclasses = [ - this.actor.system.class.subclass, - ...(multiclassSubclass ? [multiclassSubclass] : []) - ]; - const selectedSubclasses = possibleSubclasses.filter(x => subclassSelections.includes(x.uuid)); context.subclassCards = []; if (advancementChoices.subclass?.length > 0) { + const featureStateIncrease = Object.values(this.levelup.levels).reduce((acc, level) => { + acc += Object.values(level.choices).filter(choice => { + return Object.values(choice).every(checkbox => checkbox.type === 'subclass'); + }).length; + return acc; + }, 0); + for (var subclass of possibleSubclasses) { + const choice = + advancementChoices.subclass.find(x => x.data[0] === subclass.uuid) ?? + advancementChoices.subclass.find(x => x.data.length === 0); + const featureState = subclass.system.featureState + featureStateIncrease; const data = await foundry.utils.fromUuid(subclass.uuid); - const selected = selectedSubclasses.some(x => x.uuid === data.uuid); context.subclassCards.push({ ...data.toObject(), + path: choice?.path, uuid: data.uuid, - selected: selected + selected: subclassSelections.includes(subclass.uuid), + featureState: featureState, + featureLabel: game.i18n.localize(subclassFeatureLabels[featureState]), + isMulticlass: subclass.system.isMulticlass ? 'true' : 'false' }); } } @@ -237,10 +273,19 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) return { ...domain, - selected: key === data.secondaryData, - disabled: (data.secondaryData && key !== data.secondaryData) || alreadySelected + selected: key === data.secondaryData.domain, + disabled: + (data.secondaryData.domain && key !== data.secondaryData.domain) || + alreadySelected }; }) ?? [], + subclasses: + multiclass?.system?.subclasses.map(subclass => ({ + ...subclass, + uuid: subclass.uuid, + selected: data.secondaryData.subclass === subclass.uuid, + disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid + })) ?? [], compendium: 'classes', limit: 1 }; @@ -277,8 +322,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) context.achievements = { proficiency: { - old: this.actor.system.proficiency, - new: this.actor.system.proficiency + achivementProficiency, + old: this.actor.system.proficiency.total, + new: this.actor.system.proficiency.total + achivementProficiency, shown: achivementProficiency > 0 }, damageThresholds: { @@ -322,6 +367,13 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) ? advancement[choiceKey] + Number(checkbox.value) : Number(checkbox.value); break; + case 'trait': + if (!advancement[choiceKey]) advancement[choiceKey] = {}; + for (var traitKey of checkbox.data) { + if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0; + advancement[choiceKey][traitKey] += 1; + } + break; case 'domainCard': if (!advancement[choiceKey]) advancement[choiceKey] = []; if (checkbox.data.length === 1) { @@ -339,6 +391,33 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) }); advancement[choiceKey].push({ data: data, value: checkbox.value }); break; + case 'subclass': + if (checkbox.data[0]) { + const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]); + if (!advancement[choiceKey]) advancement[choiceKey] = []; + advancement[choiceKey].push({ + ...subclassItem.toObject(), + featureLabel: game.i18n.localize( + subclassFeatureLabels[Number(checkbox.secondaryData.featureState)] + ) + }); + } + break; + case 'multiclass': + const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]); + const subclass = multiclassItem + ? await foundry.utils.fromUuid(checkbox.secondaryData.subclass) + : null; + advancement[choiceKey] = multiclassItem + ? { + ...multiclassItem.toObject(), + domain: checkbox.secondaryData.domain + ? game.i18n.localize(domains[checkbox.secondaryData.domain].label) + : null, + subclass: subclass ? subclass.name : null + } + : {}; + break; } } } @@ -351,26 +430,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) new: context.achievements.proficiency.new + (advancement.proficiency ?? 0) }, hitPoints: { - old: this.actor.system.resources.hitPoints.max, - new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0) + old: this.actor.system.resources.hitPoints.maxTotal, + new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0) }, stress: { - old: this.actor.system.resources.stress.max, - new: this.actor.system.resources.stress.max + (advancement.stress ?? 0) + old: this.actor.system.resources.stress.maxTotal, + new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0) }, evasion: { - old: this.actor.system.evasion, - new: this.actor.system.evasion + (advancement.evasion ?? 0) + old: this.actor.system.evasion.total, + new: this.actor.system.evasion.total + (advancement.evasion ?? 0) } }, - traits: - advancement.trait?.flatMap(x => - x.data.map(data => game.i18n.localize(abilities[data].label)) - ) ?? [], + traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => { + if (advancement.trait?.[traitKey]) { + if (!acc) acc = {}; + acc[traitKey] = { + label: game.i18n.localize(abilities[traitKey].label), + old: this.actor.system.traits[traitKey].total, + new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey] + }; + } + return acc; + }, null), domainCards: advancement.domainCard ?? [], experiences: advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ?? - [] + [], + multiclass: advancement.multiclass, + subclass: advancement.subclass }; context.advancements.statistics.proficiency.shown = @@ -419,7 +507,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const traitsTagify = htmlElement.querySelector('.levelup-trait-increases'); if (traitsTagify) { - tagifyElement(traitsTagify, abilities, this.tagifyUpdate('trait').bind(this)); + tagifyElement(traitsTagify, this.levelup.unmarkedTraits, this.tagifyUpdate('trait').bind(this)); } const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases'); @@ -485,8 +573,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) if (event.target.closest('.domain-cards')) { const target = event.target.closest('.card-preview-container'); if (item.type === 'domainCard') { + const { multiclass } = this.levelup.classUpgradeChoices; + const isMulticlass = !multiclass ? false : item.system.domain === multiclass.domain; if ( - !this.actor.system.class.value.system.domains.includes(item.system.domain) && + !this.actor.system.domains.includes(item.system.domain) && this.levelup.classUpgradeChoices?.multiclass?.domain !== item.system.domain ) { ui.notifications.error( @@ -495,7 +585,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) return; } - if (item.system.level > Number(target.dataset.limit)) { + const levelBase = isMulticlass ? Math.ceil(this.levelup.currentLevel / 2) : this.levelup.currentLevel; + const levelMax = target.dataset.limit ? Math.min(Number(target.dataset.limit), levelBase) : levelBase; + if (levelMax < item.system.level) { ui.notifications.error( game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel') ); @@ -547,8 +639,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) amount: target.dataset.amount ? Number(target.dataset.amount) : null, value: target.dataset.value, type: target.dataset.type, - data: item.uuid, - secondaryData: null + data: item.uuid } }); this.render(); @@ -562,16 +653,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const update = {}; if (!button.checked) { - if (button.dataset.cost > 1) { + const basePath = `levels.${this.levelup.currentLevel}.choices`; + const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`); + if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) { // Simple handling that doesn't cover potential Custom LevelTiers. - update[`levels.${this.levelup.currentLevel}.choices.-=${button.dataset.option}`] = null; + update[`${basePath}.-=${button.dataset.option}`] = null; } else { - update[ - `levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.-=${button.dataset.checkboxNr}` - ] = null; + update[`${basePath}.${button.dataset.option}.-=${button.dataset.checkboxNr}`] = null; } } else { - if (!this.levelup.levels[this.levelup.currentLevel].nrSelections.available) { + if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) { ui.notifications.info( game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.info.insufficentAdvancements') ); @@ -579,15 +670,23 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) return; } - update[ - `levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.${button.dataset.checkboxNr}` - ] = { + const updateData = { tier: Number(button.dataset.tier), minCost: Number(button.dataset.cost), amount: button.dataset.amount ? Number(button.dataset.amount) : null, value: button.dataset.value, type: button.dataset.type }; + + if (button.dataset.type === 'domainCard') { + updateData.secondaryData = { + limit: Math.max(...this.levelup.tiers[button.dataset.tier].belongingLevels) + }; + } + + update[ + `levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.${button.dataset.checkboxNr}` + ] = updateData; } await this.levelup.updateSource(update); @@ -600,24 +699,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) static async selectPreview(_, button) { const remove = button.dataset.selected; - const selectionData = Object.values(this.levelup.selectionData); - 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; + await this.levelup.updateSource({ + [`${button.dataset.path}`]: { + data: remove ? [] : [button.dataset.uuid], + secondaryData: { + featureState: button.dataset.featureState, + isMulticlass: button.dataset.isMulticlass + } + } + }); - 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 = foundry.utils.getProperty(this.levelup, button.dataset.path); - const domain = option.secondaryData ? null : button.dataset.domain; + const domain = option.secondaryData.domain ? null : button.dataset.domain; await this.levelup.updateSource({ - multiclass: { domain }, - [`${button.dataset.path}.secondaryData`]: domain + [`${button.dataset.path}.secondaryData.domain`]: domain + }); + this.render(); + } + + static async selectSubclass(_, button) { + const option = foundry.utils.getProperty(this.levelup, button.dataset.path); + const subclass = option.secondaryData.subclass ? null : button.dataset.subclass; + + await this.levelup.updateSource({ + [`${button.dataset.path}.secondaryData.subclass`]: subclass }); this.render(); } diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index b0d8bd1d..c8f5c1e1 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -1,5 +1,5 @@ +import { tagifyElement } from '../../../helpers/utils.mjs'; import DaggerheartSheet from '../daggerheart-sheet.mjs'; -import Tagify from '@yaireo/tagify'; const { ItemSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; @@ -72,55 +72,14 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { super._attachPartListeners(partId, htmlElement, options); const domainInput = htmlElement.querySelector('.domain-input'); - const domainTagify = new Tagify(domainInput, { - tagTextProp: 'name', - enforceWhitelist: true, - whitelist: Object.keys(SYSTEM.DOMAIN.domains).map(key => { - const domain = SYSTEM.DOMAIN.domains[key]; - return { - value: key, - name: game.i18n.localize(domain.label), - src: domain.src, - background: domain.background - }; - }), - maxTags: 2, - callbacks: { invalid: this.onAddTag }, - dropdown: { - mapValueTo: 'name', - searchKeys: ['name'], - enabled: 0, - maxItems: 20, - closeOnSelect: true, - highlightFirst: false - }, - templates: { - tag(tagData) { - //z-index: unset; background-image: ${tagData.background}; Maybe a domain specific background for the chips? - return ` - -
- ${tagData[this.settings.tagTextProp] || tagData.value} - -
-
`; - } - } - }); - - domainTagify.on('change', this.onDomainSelect.bind(this)); + tagifyElement(domainInput, SYSTEM.DOMAIN.domains, this.onDomainSelect.bind(this)); } async _prepareContext(_options) { const context = await super._prepareContext(_options); context.document = this.document; context.tabs = super._getTabs(this.constructor.TABS); - context.domains = this.document.system.domains.map(x => SYSTEM.DOMAIN.domains[x].label); + context.domains = this.document.system.domains; return context; } @@ -136,8 +95,7 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { } } - async onDomainSelect(event) { - const domains = event.detail?.value ? JSON.parse(event.detail.value) : []; + async onDomainSelect(domains) { await this.document.update({ 'system.domains': domains.map(x => x.value) }); this.render(true); } diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 3698d19e..6af480d7 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -409,3 +409,9 @@ export const levelupData = { } } }; + +export const subclassFeatureLabels = { + 1: 'DAGGERHEART.Sheets.PC.DomainCard.FoundationTitle', + 2: 'DAGGERHEART.Sheets.PC.DomainCard.SpecializationTitle', + 3: 'DAGGERHEART.Sheets.PC.DomainCard.MasteryTitle' +}; diff --git a/module/config/domainConfig.mjs b/module/config/domainConfig.mjs index 394f6fb3..a32091c2 100644 --- a/module/config/domainConfig.mjs +++ b/module/config/domainConfig.mjs @@ -1,56 +1,56 @@ export const domains = { arcana: { id: 'arcana', - label: 'Arcana', + label: 'DAGGERHEART.Domains.Arcana.label', src: 'icons/magic/symbols/circled-gem-pink.webp', description: 'DAGGERHEART.Domains.Arcana' }, blade: { id: 'blade', - label: 'Blade', + label: 'DAGGERHEART.Domains.Blade.label', src: 'icons/weapons/swords/sword-broad-crystal-paired.webp', description: 'DAGGERHEART.Domains.Blade' }, bone: { id: 'bone', - label: 'Bone', + label: 'DAGGERHEART.Domains.Bone.label', src: 'icons/skills/wounds/bone-broken-marrow-red.webp', description: 'DAGGERHEART.Domains.Bone' }, codex: { id: 'codex', - label: 'Codex', + label: 'DAGGERHEART.Domains.Codex.label', src: 'icons/sundries/books/book-embossed-jewel-gold-purple.webp', description: 'DAGGERHEART.Domains.Codex' }, grace: { id: 'grace', - label: 'Grace', + label: 'DAGGERHEART.Domains.Grace.label', src: 'icons/skills/movement/feet-winged-boots-glowing-yellow.webp', description: 'DAGGERHEART.Domains.Grace' }, midnight: { id: 'midnight', - label: 'Midnight', + label: 'DAGGERHEART.Domains.Midnight.label', src: 'icons/environment/settlement/watchtower-castle-night.webp', background: 'systems/daggerheart/assets/backgrounds/MidnightBackground.webp', description: 'DAGGERHEART.Domains.Midnight' }, sage: { id: 'sage', - label: 'Sage', + label: 'DAGGERHEART.Domains.Sage.label', src: 'icons/sundries/misc/pipe-wooden-straight-brown.webp', description: 'DAGGERHEART.Domains.Sage' }, splendor: { id: 'splendor', - label: 'Splendor', + label: 'DAGGERHEART.Domains.Splendor.label', src: 'icons/magic/control/control-influence-crown-gold.webp', description: 'DAGGERHEART.Domains.Splendor' }, valor: { id: 'valor', - label: 'Valor', + label: 'DAGGERHEART.Domains.Valor.label', src: 'icons/magic/control/control-influence-rally-purple.webp', description: 'DAGGERHEART.Domains.Valor' } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index def502b5..2bc3ca06 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -6,12 +6,14 @@ import BaseDataActor from './base.mjs'; const attributeField = () => new foundry.data.fields.SchemaField({ value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), + bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }), tierMarked: new foundry.data.fields.BooleanField({ initial: false }) }); const resourceField = max => new foundry.data.fields.SchemaField({ value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), + bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }), max: new foundry.data.fields.NumberField({ initial: max, integer: true }) }); @@ -40,12 +42,18 @@ export default class DhCharacter extends BaseDataActor { presence: attributeField(), knowledge: attributeField() }), - proficiency: new fields.NumberField({ initial: 1, integer: true }), - evasion: new fields.NumberField({ initial: 0, integer: true }), + proficiency: new fields.SchemaField({ + value: new fields.NumberField({ initial: 1, integer: true }), + bonus: new fields.NumberField({ initial: 0, integer: true }) + }), + evasion: new fields.SchemaField({ + bonus: new fields.NumberField({ initial: 0, integer: true }) + }), experiences: new fields.TypedObjectField( new fields.SchemaField({ description: new fields.StringField({}), - value: new fields.NumberField({ integer: true, nullable: true, initial: null }) + value: new fields.NumberField({ integer: true, initial: 0 }), + bonus: new fields.NumberField({ integer: true, initial: 0 }) }), { initial: { @@ -89,8 +97,8 @@ export default class DhCharacter extends BaseDataActor { } get domains() { - const classDomains = this.class ? this.class.system.domains : []; - const multiclassDomains = this.multiclass ? this.multiclass.system.domains : []; + const classDomains = this.class.value ? this.class.value.system.domains : []; + const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : []; return [...classDomains, ...multiclassDomains]; } @@ -163,9 +171,46 @@ export default class DhCharacter extends BaseDataActor { } prepareBaseData() { - for (var attributeKey in this.traits) { - const attribute = this.traits[attributeKey]; - /* Levleup handling */ + const currentLevel = this.levelData.level.current; + const currentTier = + currentLevel === 1 + ? null + : Object.values(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers).tiers).find( + tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end + ).tier; + for (let levelKey in this.levelData.levelups) { + const level = this.levelData.levelups[levelKey]; + + this.proficiency.bonus += level.achievements.proficiency; + + for (let selection of level.selections) { + switch (selection.type) { + case 'trait': + selection.data.forEach(data => { + this.traits[data].bonus += 1; + this.traits[data].tierMarked = selection.tier === currentTier; + }); + break; + case 'hitPoint': + this.resources.hitPoints.bonus += selection.value; + break; + case 'stress': + this.resources.stress.bonus += selection.value; + break; + case 'evasion': + this.evasion.bonus += selection.value; + break; + case 'proficiency': + this.proficiency.bonus = selection.value; + break; + case 'experience': + Object.keys(this.experiences).forEach(key => { + const experience = this.experiences[key]; + experience.bonus += selection.value; + }); + break; + } + } } const armor = this.armor; @@ -182,6 +227,21 @@ export default class DhCharacter extends BaseDataActor { prepareDerivedData() { this.resources.hope.max -= Object.keys(this.scars).length; this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max); + + for (var traitKey in this.traits) { + var trait = this.traits[traitKey]; + trait.total = trait.value + trait.bonus; + } + + for (var experienceKey in this.experiences) { + var experience = this.experiences[experienceKey]; + experience.total = experience.value + experience.bonus; + } + + this.resources.hitPoints.maxTotal = this.resources.hitPoints.max + this.resources.hitPoints.bonus; + this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus; + this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus; + this.proficiency.total = this.proficiency.value + this.proficiency.bonus; } } @@ -225,7 +285,7 @@ class DhPCLevelData extends foundry.abstract.DataModel { minCost: new fields.NumberField({ integer: true }), amount: new fields.NumberField({ integer: true }), data: new fields.ArrayField(new fields.StringField({ required: true })), - secondaryData: new fields.StringField(), + secondaryData: new fields.TypedObjectField(new fields.StringField({ required: true })), itemUuid: new fields.StringField({ required: true }) }) ) diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 0584f1db..335014b9 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -17,7 +17,6 @@ export default class DHClass extends BaseDataItem { return { ...super.defineSchema(), domains: new fields.ArrayField(new fields.StringField(), { max: 2 }), - classItems: new fields.ArrayField(new ForeignDocumentUUIDField({ type: 'Item' })), evasion: new fields.NumberField({ initial: 0, integer: true }), features: new fields.ArrayField(new ForeignDocumentUUIDField({ type: 'Item' })), diff --git a/module/data/item/domainCard.mjs b/module/data/item/domainCard.mjs index d24f53f7..814626e3 100644 --- a/module/data/item/domainCard.mjs +++ b/module/data/item/domainCard.mjs @@ -36,17 +36,12 @@ export default class DHDomainCard extends BaseDataItem { return false; } - if (!this.actor.system.domains.find(x => x === item.system.domain)) { + if (!this.actor.system.domains.find(x => x === this.domain)) { ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.LacksDomain')); return false; } - if (this.actor.system.domainCards.total.length === 5) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.MaxLoadoutReached')); - return false; - } - - if (this.actor.system.domainCards.total.find(x => x.name === item.name)) { + if (this.actor.system.domainCards.total.find(x => x.name === this.parent.name)) { ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.DuplicateDomainCard')); return false; } diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index ea506efa..bb315fcd 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -25,24 +25,37 @@ export default class DHSubclass extends BaseDataItem { foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }), specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }), masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }), + featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }), isMulticlass: new fields.BooleanField({ initial: false }) }; } + get features() { + return { + foundation: this.foundationFeature, + specialization: this.featureState >= 2 ? this.specializationFeature : null, + mastery: this.featureState === 3 ? this.masteryFeature : null + }; + } + async _preCreate(data, options, user) { const allowed = await super._preCreate(data, options, user); if (allowed === false) return; if (this.actor?.type === 'character') { - const path = data.system.isMulticlass ? 'system.multiclass' : 'system.class'; - const classData = foundry.utils.getProperty(this.actor, path); - if (!classData.value) { + const classData = this.actor.items.find( + x => x.type === 'class' && x.system.isMulticlass === data.system.isMulticlass + ); + const subclassData = this.actor.items.find( + x => x.type === 'subclass' && x.system.isMulticlass === data.system.isMulticlass + ); + if (!classData) { ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.MissingClass')); return false; - } else if (classData.subclass) { + } else if (subclassData) { ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassAlreadySelected')); return false; - } else if (classData.value.system.subclasses.every(x => x.uuid !== `Item.${data._id}`)) { + } else if (classData.system.subclasses.every(x => x.uuid !== `Item.${data._id}`)) { ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassNotInClass')); return false; } diff --git a/module/data/levelup.mjs b/module/data/levelup.mjs index 0f9204e0..a964d716 100644 --- a/module/data/levelup.mjs +++ b/module/data/levelup.mjs @@ -1,3 +1,4 @@ +import { abilities } from '../config/actorConfig.mjs'; import { chunkify } from '../helpers/utils.mjs'; import { LevelOptionType } from './levelTier.mjs'; @@ -97,11 +98,12 @@ export class DhLevelup extends foundry.abstract.DataModel { case 'experience': case 'domainCard': case 'subclass': - return checkbox.amount ? checkbox.data.length === checkbox.amount : checkbox.data.length === 1; + return checkbox.data.length === (checkbox.amount ?? 1); case 'multiclass': const classSelected = checkbox.data.length === 1; - const domainSelected = checkbox.secondaryData; - return classSelected && domainSelected; + const domainSelected = checkbox.secondaryData.domain; + const subclassSelected = checkbox.secondaryData.subclass; + return classSelected && domainSelected && subclassSelected; default: return true; } @@ -128,8 +130,37 @@ export class DhLevelup extends foundry.abstract.DataModel { .every(this.#levelFinished.bind(this)); } + get unmarkedTraits() { + const possibleLevels = Object.values(this.tiers).reduce((acc, tier) => { + if (tier.belongingLevels.includes(this.currentLevel)) acc = tier.belongingLevels; + return acc; + }, []); + + return Object.keys(this.levels) + .filter(key => possibleLevels.some(x => x === Number(key))) + .reduce( + (acc, levelKey) => { + const level = this.levels[levelKey]; + Object.values(level.choices).forEach(choice => + Object.values(choice).forEach(checkbox => { + if ( + checkbox.type === 'trait' && + checkbox.data.length > 0 && + Number(levelKey) !== this.currentLevel + ) { + checkbox.data.forEach(data => delete acc[data]); + } + }) + ); + + return acc; + }, + { ...abilities } + ); + } + get classUpgradeChoices() { - let subclass = null; + let subclasses = []; let multiclass = null; Object.keys(this.levels).forEach(levelKey => { const level = this.levels[levelKey]; @@ -138,21 +169,22 @@ export class DhLevelup extends foundry.abstract.DataModel { if (checkbox.type === 'multiclass') { multiclass = { class: checkbox.data.length > 0 ? checkbox.data[0] : null, - domain: checkbox.secondaryData ?? null, + domain: checkbox.secondaryData.domain ?? null, + subclass: checkbox.secondaryData.subclass ?? null, tier: checkbox.tier, level: levelKey }; } if (checkbox.type === 'subclass') { - subclass = { + subclasses.push({ tier: checkbox.tier, level: levelKey - }; + }); } }); }); }); - return { subclass, multiclass }; + return { subclasses, multiclass }; } get tiersForRendering() { @@ -179,11 +211,11 @@ export class DhLevelup extends foundry.abstract.DataModel { }, {}) ); - const { multiclass, subclass } = this.classUpgradeChoices; - return tierKeys.map(tierKey => { + const { multiclass, subclasses } = this.classUpgradeChoices; + return tierKeys.map((tierKey, tierIndex) => { const tier = this.tiers[tierKey]; const multiclassInTier = multiclass?.tier === Number(tierKey); - const subclassInTier = subclass?.tier === Number(tierKey); + const subclassInTier = subclasses.some(x => x.tier === Number(tierKey)); return { name: tier.name, @@ -214,8 +246,15 @@ export class DhLevelup extends foundry.abstract.DataModel { return checkbox; }); + + let label = game.i18n.localize(option.label); + if (optionKey === 'domainCard') { + const maxLevel = tier.belongingLevels[tier.belongingLevels.length - 1]; + label = game.i18n.format(option.label, { maxLevel }); + } + return { - label: game.i18n.localize(option.label), + label: label, checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => { const anySelected = chunkedBoxes.some(x => x.selected); const anyDisabled = chunkedBoxes.some(x => x.disabled); @@ -287,7 +326,7 @@ export class DhLevelupLevel extends foundry.abstract.DataModel { amount: new fields.NumberField({ integer: true }), value: new fields.StringField(), data: new fields.ArrayField(new fields.StringField()), - secondaryData: new fields.StringField(), + secondaryData: new fields.TypedObjectField(new fields.StringField()), type: new fields.StringField({ required: true }) }) ) diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 2f24a272..fea4a426 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -40,17 +40,65 @@ export default class DhpActor extends Actor { return acc; }, {}); - const domainCards = Object.keys(this.system.levelData.levelups) + const domainCards = []; + const experiences = []; + const subclassFeatureState = { class: null, multiclass: null }; + let multiclass = null; + Object.keys(this.system.levelData.levelups) .filter(x => x > newLevel) - .flatMap(levelKey => { + .forEach(levelKey => { const level = this.system.levelData.levelups[levelKey]; const achievementCards = level.achievements.domainCards.map(x => x.itemUuid); const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid); - return [...achievementCards, ...advancementCards]; + domainCards.push(...achievementCards, ...advancementCards); + experiences.push(...Object.keys(level.achievements.experiences)); + + const subclass = level.selections.find(x => x.type === 'subclass'); + if (subclass) { + const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class'; + const subclassState = Number(subclass.secondaryData.featureState) - 1; + subclassFeatureState[path] = subclassFeatureState[path] + ? Math.min(subclassState, subclassFeatureState[path]) + : subclassState; + } + + multiclass = level.selections.find(x => x.type === 'multiclass'); }); - for (var domainCard of domainCards) { - const itemCard = await this.items.find(x => x.uuid === domainCard); + if (experiences.length > 0) { + this.update({ + 'system.experiences': experiences.reduce((acc, key) => { + acc[`-=${key}`] = null; + return acc; + }, {}) + }); + } + + if (subclassFeatureState.class) { + this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class }); + } + + if (subclassFeatureState.multiclass) { + this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass }); + } + + if (multiclass) { + const multiclassSubclass = this.items.find(x => x.type === 'subclass' && x.system.isMulticlass); + const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid); + + multiclassSubclass.delete(); + multiclassItem.delete(); + + this.update({ + 'system.multiclass': { + value: null, + subclass: null + } + }); + } + + for (let domainCard of domainCards) { + const itemCard = this.items.find(x => x.uuid === domainCard); itemCard.delete(); } @@ -72,6 +120,94 @@ export default class DhpActor extends Actor { const levelups = {}; for (var levelKey of Object.keys(levelupData)) { const level = levelupData[levelKey]; + + for (var experienceKey in level.achievements.experiences) { + const experience = level.achievements.experiences[experienceKey]; + await this.update({ + [`system.experiences.${experienceKey}`]: { + description: experience.name, + value: experience.modifier + } + }); + } + + let multiclass = null; + const domainCards = []; + const subclassFeatureState = { class: null, multiclass: null }; + const selections = []; + for (var optionKey of Object.keys(level.choices)) { + const selection = level.choices[optionKey]; + for (var checkboxNr of Object.keys(selection)) { + const checkbox = selection[checkboxNr]; + + if (checkbox.type === 'multiclass') { + multiclass = { + ...checkbox, + level: Number(levelKey), + optionKey: optionKey, + checkboxNr: Number(checkboxNr) + }; + } else if (checkbox.type === 'domainCard') { + domainCards.push({ + ...checkbox, + level: Number(levelKey), + optionKey: optionKey, + checkboxNr: Number(checkboxNr) + }); + } else { + if (checkbox.type === 'subclass') { + const path = checkbox.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class'; + subclassFeatureState[path] = Math.max( + Number(checkbox.secondaryData.featureState), + subclassFeatureState[path] + ); + } + + selections.push({ + ...checkbox, + level: Number(levelKey), + optionKey: optionKey, + checkboxNr: Number(checkboxNr) + }); + } + } + } + + if (multiclass) { + const subclassItem = await foundry.utils.fromUuid(multiclass.secondaryData.subclass); + const subclassData = subclassItem.toObject(); + const multiclassItem = await foundry.utils.fromUuid(multiclass.data[0]); + const multiclassData = multiclassItem.toObject(); + + const embeddedItem = await this.createEmbeddedDocuments('Item', [ + { + ...multiclassData, + system: { + ...multiclassData.system, + domains: [multiclass.secondaryData.domain], + isMulticlass: true + } + } + ]); + + await this.createEmbeddedDocuments('Item', [ + { + ...subclassData, + system: { + ...subclassData.system, + isMulticlass: true + } + } + ]); + selections.push({ ...multiclass, itemUuid: embeddedItem[0].uuid }); + } + + for (var domainCard of domainCards) { + const item = await foundry.utils.fromUuid(domainCard.data[0]); + const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]); + selections.push({ ...domainCard, itemUuid: embeddedItem[0].uuid }); + } + const achievementDomainCards = []; for (var card of Object.values(level.achievements.domainCards)) { const item = await foundry.utils.fromUuid(card.uuid); @@ -80,27 +216,14 @@ export default class DhpActor extends Actor { achievementDomainCards.push(card); } - const selections = []; - for (var optionKey of Object.keys(level.choices)) { - const selection = level.choices[optionKey]; - for (var checkboxNr of Object.keys(selection)) { - const checkbox = selection[checkboxNr]; - let itemUuid = null; + if (subclassFeatureState.class) { + await this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class }); + } - if (checkbox.type === 'domainCard') { - const item = await foundry.utils.fromUuid(checkbox.data[0]); - const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]); - itemUuid = embeddedItem[0].uuid; - } - - selections.push({ - ...checkbox, - level: Number(levelKey), - optionKey: optionKey, - checkboxNr: Number(checkboxNr), - itemUuid - }); - } + if (subclassFeatureState.multiclass) { + await this.system.multiclass.subclass.update({ + 'system.featureState': subclassFeatureState.multiclass + }); } levelups[levelKey] = { diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index d39b6544..1e12e430 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -174,17 +174,17 @@ export const tagifyElement = (element, options, onChange, tagifyOptions = {}) => templates: { tag(tagData) { return ` - -
- ${tagData[this.settings.tagTextProp] || tagData.value} - ${tagData.src ? `` : ''} -
-
`; + contenteditable='false' + spellcheck='false' + tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}" + class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}" + ${this.getAttributes(tagData)}> + +
+ ${tagData[this.settings.tagTextProp] || tagData.value} + ${tagData.src ? `` : ''} +
+ `; } } }); diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 5cca9027..975d7c25 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -3379,6 +3379,7 @@ div.daggerheart.views.multiclass { flex: 1; border-radius: 0 0 4px 4px; display: flex; + flex-direction: column; align-items: center; justify-content: center; font-size: 18px; diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index d6e02335..139d5c53 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -354,6 +354,7 @@ flex: 1; border-radius: 0 0 4px 4px; display: flex; + flex-direction: column; align-items: center; justify-content: center; font-size: 18px; diff --git a/templates/components/card-preview.hbs b/templates/components/card-preview.hbs index dc132b94..ba817371 100644 --- a/templates/components/card-preview.hbs +++ b/templates/components/card-preview.hbs @@ -6,7 +6,7 @@
-
{{this.emptySubtext}}
+
{{> @partial-block }}
{{/if}} diff --git a/templates/sheets/parts/attributes.hbs b/templates/sheets/parts/attributes.hbs index 1ebec896..08e414cd 100644 --- a/templates/sheets/parts/attributes.hbs +++ b/templates/sheets/parts/attributes.hbs @@ -7,19 +7,19 @@ {{#each this.attributes as |attribute key|}}
- +
{{key}}
{{#if ../editAttributes}} - + {{#if (not (eq attribute.total 0))}}{{/if}} {{#each ../abilityScoreArray as |option|}} - + {{/each}} {{else}} -
{{attribute.value}}
+
{{attribute.total}}
{{/if}}
diff --git a/templates/sheets/parts/defense.hbs b/templates/sheets/parts/defense.hbs index 2041e9a6..7553373c 100644 --- a/templates/sheets/parts/defense.hbs +++ b/templates/sheets/parts/defense.hbs @@ -4,7 +4,7 @@
-
{{document.system.evasion}}
+
{{document.system.evasion.total}}
{{localize "DAGGERHEART.Sheets.PC.Defense.Evasion"}}
diff --git a/templates/sheets/parts/experience.hbs b/templates/sheets/parts/experience.hbs index ace6970e..5bc7e1c2 100644 --- a/templates/sheets/parts/experience.hbs +++ b/templates/sheets/parts/experience.hbs @@ -3,7 +3,7 @@ {{#each document.system.experiences as |experience id|}}
-
{{experience.value}}
+
{{experience.total}}
{{/each}} {{#times (subtract 5 (length document.system.experiences))}} diff --git a/templates/sheets/parts/health.hbs b/templates/sheets/parts/health.hbs index 4b5a9fac..18419461 100644 --- a/templates/sheets/parts/health.hbs +++ b/templates/sheets/parts/health.hbs @@ -21,7 +21,7 @@
{{localize "DAGGERHEART.Sheets.PC.Health.Severe"}}
- +
@@ -30,22 +30,22 @@
- {{#times document.system.resources.hitPoints.max}} + {{#times document.system.resources.hitPoints.maxTotal}} {{#with (add this 1)}} {{/with}} {{/times}} - {{#times (subtract 12 document.system.resources.hitPoints.max)}} + {{#times (subtract 12 document.system.resources.hitPoints.maxTotal)}} {{/times}}
- {{#times document.system.resources.stress.max}} + {{#times document.system.resources.stress.maxTotal}} {{#with (add this 1)}} {{/with}} {{/times}} - {{#times (subtract 12 document.system.resources.stress.max)}} + {{#times (subtract 12 document.system.resources.stress.maxTotal)}} {{/times}}
diff --git a/templates/sheets/parts/weapons.hbs b/templates/sheets/parts/weapons.hbs index a53fffcb..a020d55f 100644 --- a/templates/sheets/parts/weapons.hbs +++ b/templates/sheets/parts/weapons.hbs @@ -5,7 +5,7 @@
{{localize "DAGGERHEART.Sheets.PC.Weapons.ProficiencyTitle"}} {{#times 6}} - + {{/times}}
diff --git a/templates/views/levelup/parts/selectable-card-preview.hbs b/templates/views/levelup/parts/selectable-card-preview.hbs index 039983e5..6be03b37 100644 --- a/templates/views/levelup/parts/selectable-card-preview.hbs +++ b/templates/views/levelup/parts/selectable-card-preview.hbs @@ -1,4 +1,4 @@ -
+
{{#if this.selected}} @@ -7,5 +7,8 @@
{{/if}}
-
{{this.name}}
+
+
{{this.name}}
+ {{this.featureLabel}} +
\ No newline at end of file diff --git a/templates/views/levelup/tabs/selections.hbs b/templates/views/levelup/tabs/selections.hbs index 1f8b2b21..03145347 100644 --- a/templates/views/levelup/tabs/selections.hbs +++ b/templates/views/levelup/tabs/selections.hbs @@ -51,7 +51,11 @@
{{#each this.domainCards}} - {{> "systems/daggerheart/templates/components/card-preview.hbs" this }} + {{#> "systems/daggerheart/templates/components/card-preview.hbs" this }} + {{#each this.emptySubtexts}} +
{{this}}
+ {{/each}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}} {{/each}}
@@ -63,7 +67,7 @@
{{#each this.subclassCards}} - {{> "systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs" img=this.img name=this.name path=this.path selected=this.selected uuid=this.uuid disabled=this.disabled }} + {{> "systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs" img=this.img header=this.featureLabel name=this.name path=this.path selected=this.selected uuid=this.uuid isMulticlass=this.isMulticlass featureState=this.featureState disabled=this.disabled }} {{/each}}
@@ -74,7 +78,9 @@

{{localize "DAGGERHEART.Application.LevelUp.summary.multiclass"}}

- {{> "systems/daggerheart/templates/components/card-preview.hbs" this.multiclass }} + {{#> "systems/daggerheart/templates/components/card-preview.hbs" this.multiclass }} + {{this.multiclass.emptySubtext}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}}
{{#each this.multiclass.domains}}
@@ -88,6 +94,20 @@
{{/each}}
+ +
+ {{#each this.multiclass.subclasses}} +
+
{{localize this.name}}
+ + {{#if this.selected}} +
+ +
+ {{/if}} +
+ {{/each}} +
{{/if}} diff --git a/templates/views/levelup/tabs/summary.hbs b/templates/views/levelup/tabs/summary.hbs index 77925cff..c1c1d13f 100644 --- a/templates/views/levelup/tabs/summary.hbs +++ b/templates/views/levelup/tabs/summary.hbs @@ -95,11 +95,14 @@ {{#if this.advancements.traits}}
{{localize "DAGGERHEART.Application.LevelUp.summary.traits"}}
-
+ {{#each this.advancements.traits}} -
{{this}}
+
+ {{this.label}}: {{this.old}} + + {{this.new}} +
{{/each}} -
{{/if}} @@ -124,6 +127,30 @@
{{/if}} + + {{#if this.advancements.subclass}} +
+
{{localize "DAGGERHEART.Application.LevelUp.summary.subclass"}}
+
+ {{#each this.advancements.subclass}} +
{{this.name}} - {{this.featureLabel}}
+ {{/each}} +
+
+ {{/if}} + + {{#if this.advancements.multiclass}} + {{#with this.advancements.multiclass}} +
+
{{localize "DAGGERHEART.Application.LevelUp.summary.multiclass"}}
+
+
{{this.name}}
+
{{this.domain}}
+
{{this.subclass}}
+
+
+ {{/with}} + {{/if}}