From 3cc8800950550b69a0775e179946358a0039dd62 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 28 May 2025 00:32:19 +0200 Subject: [PATCH] Added Levelup data model and started at the render --- module/applications/levelup.mjs | 725 ++++++++++++++------------ module/applications/sheets/pc.mjs | 6 +- module/data/levelTier.mjs | 55 +- module/data/levelup.mjs | 127 +++++ module/data/pc.mjs | 19 +- styles/daggerheart.css | 26 + styles/daggerheart.less | 1 + styles/levelup.less | 36 ++ templates/sheets/parts/attributes.hbs | 4 +- templates/views/levelup.hbs | 28 +- 10 files changed, 618 insertions(+), 409 deletions(-) create mode 100644 module/data/levelup.mjs create mode 100644 styles/levelup.less diff --git a/module/applications/levelup.mjs b/module/applications/levelup.mjs index 90500e2a..e5747488 100644 --- a/module/applications/levelup.mjs +++ b/module/applications/levelup.mjs @@ -1,16 +1,16 @@ -import SelectDialog from '../dialogs/selectDialog.mjs'; -import { getTier } from '../helpers/utils.mjs'; -import DhpMulticlassDialog from './multiclassDialog.mjs'; +import { DhLevelup } from '../data/levelup.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; -export default class DhpLevelup extends HandlebarsApplicationMixin(ApplicationV2) { +export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) { constructor(actor) { super({}); this.actor = actor; - this.data = foundry.utils.deepClone(actor.system.levelData); - this.activeLevel = actor.system.levelData.currentLevel + 1; + this.levelTiers = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers); + + const playerLevelupData = actor.system.levelData; + this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData)); } get title() { @@ -18,16 +18,16 @@ export default class DhpLevelup extends HandlebarsApplicationMixin(ApplicationV2 } static DEFAULT_OPTIONS = { - classes: ['daggerheart', 'views', 'levelup'], + classes: ['daggerheart', 'levelup'], position: { width: 1200, height: 'auto' }, window: { resizable: true }, - - actions: { - toggleBox: this.toggleBox, - advanceLevel: this.advanceLevel, - finishLevelup: this.finishLevelup + actions: {}, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false } }; @@ -39,333 +39,386 @@ export default class DhpLevelup extends HandlebarsApplicationMixin(ApplicationV2 }; async _prepareContext(_options) { - let selectedChoices = 0, - multiclassing = {}, - subclassing = {}; - const leveledTiers = Object.keys(this.data.levelups).reduce( - (acc, levelKey) => { - const levelData = this.data.levelups[levelKey]; - ['tier1', 'tier2', 'tier3'].forEach(tierKey => { - let tierUpdate = {}; - const tierData = levelData[tierKey]; - if (tierData) { - tierUpdate = Object.keys(tierData).reduce((acc, propertyKey) => { - const values = tierData[propertyKey]; - const level = Number.parseInt(levelKey); + const context = await super._prepareContext(_options); + context.levelup = this.levelup; - acc[propertyKey] = Object.values(values).map(value => { - if (value && level === this.activeLevel) selectedChoices++; - if (propertyKey === 'multiclass') multiclassing[levelKey] = true; - if (propertyKey === 'subclass') subclassing[tierKey] = true; - - return { level: level, value: value }; - }); - - return acc; - }, {}); - } - - Object.keys(tierUpdate).forEach(propertyKey => { - const property = tierUpdate[propertyKey]; - const propertyValues = foundry.utils.getProperty(acc, `${tierKey}.${propertyKey}`) ?? []; - foundry.utils.setProperty(acc, `${tierKey}.${propertyKey}`, [...propertyValues, ...property]); - }); - }); - - return acc; - }, - { tier1: {}, tier2: {}, tier3: {} } - ); - - const activeTier = getTier(this.activeLevel); - const data = Object.keys(SYSTEM.ACTOR.levelupData).reduce((acc, tierKey) => { - const tier = SYSTEM.ACTOR.levelupData[tierKey]; - acc[tierKey] = { - label: game.i18n.localize(tier.label), - info: game.i18n.localize(tier.info), - pretext: game.i18n.localize(tier.pretext), - postext: game.i18n.localize(tier.posttext), - active: tierKey <= activeTier, - choices: Object.keys(tier.choices).reduce((acc, propertyKey) => { - const property = tier.choices[propertyKey]; - acc[propertyKey] = { description: property.description, cost: property.cost ?? 1, values: [] }; - for (var i = 0; i < property.maxChoices; i++) { - const leveledValue = leveledTiers[tierKey][propertyKey]?.[i]; - const subclassLock = - propertyKey === 'subclass' && - Object.keys(multiclassing).find(x => getTier(Number.parseInt(x)) === tierKey); - const subclassMulticlassLock = propertyKey === 'multiclass' && subclassing[tierKey]; - const multiclassLock = - propertyKey === 'multiclass' && - Object.keys(multiclassing).length > 0 && - !( - leveledValue && - Object.keys(multiclassing).find(x => Number.parseInt(x) === leveledValue.level) - ); - const locked = - (leveledValue && leveledValue.level !== this.activeLevel) || - subclassLock || - subclassMulticlassLock || - multiclassLock; - const disabled = - tierKey > activeTier || - (selectedChoices === 2 && !(leveledValue && leveledValue.level === this.activeLevel)) || - locked; - - acc[propertyKey].values.push({ - selected: leveledValue?.value !== undefined, - path: `levelups.${this.activeLevel}.${tierKey}.${propertyKey}.${i}`, - description: game.i18n.localize(property.description), - disabled: disabled, - locked: locked - }); - } - - return acc; - }, {}) - }; - - return acc; - }, {}); - - return { - data: data, - activeLevel: this.activeLevel, - changedLevel: this.actor.system.levelData.changedLevel, - completedSelection: selectedChoices === 2 - }; + return context; } - static async toggleBox(_, button) { - const path = button.dataset.path; - if (foundry.utils.getProperty(this.data, path)) { - const pathParts = path.split('.'); - const arrayPart = pathParts.slice(0, pathParts.length - 1).join('.'); - let array = foundry.utils.getProperty(this.data, arrayPart); - if (button.dataset.levelAttribute === 'multiclass') { - array = []; - } else { - delete array[Number.parseInt(pathParts[pathParts.length - 1])]; - } - foundry.utils.setProperty(this.data, arrayPart, array); - } else { - const updates = [{ path: path, value: { level: this.activeLevel } }]; - const levelChoices = SYSTEM.ACTOR.levelChoices[button.dataset.levelAttribute]; - if (button.dataset.levelAttribute === 'subclass') { - if (!this.actor.system.multiclassSubclass) { - updates[0].value.value = { - multiclass: false, - feature: this.actor.system.subclass.system.specializationFeature.unlocked - ? 'mastery' - : 'specialization' - }; - } else { - const choices = [ - { name: this.actor.system.subclass.name, value: this.actor.system.subclass.uuid }, - { - name: this.actor.system.multiclassSubclass.name, - value: this.actor.system.multiclassSubclass.uuid - } - ]; - const indexes = await SelectDialog.selectItem({ - actor: this.actor, - choices: choices, - title: levelChoices.title, - nrChoices: 1 - }); - if (indexes.length === 0) { - this.render(); - return; - } - const multiclassSubclass = choices[indexes[0]].name === this.actor.system.multiclassSubclass.name; - updates[0].value.value = { - multiclass: multiclassSubclass, - feature: this.actor.system.multiclassSubclass.system.specializationFeature.unlocked - ? 'mastery' - : 'specialization' - }; - } - } else if (button.dataset.levelAttribute === 'multiclass') { - const multiclassAwait = new Promise(resolve => { - new DhpMulticlassDialog(this.actor.name, this.actor.system.class, resolve).render(true); - }); - const multiclassData = await multiclassAwait; - if (!multiclassData) { - this.render(); - return; - } - - const pathParts = path.split('.'); - const arrayPart = pathParts.slice(0, pathParts.length - 1).join('.'); - updates[0] = { - path: [arrayPart, '0'].join('.'), - value: { - level: this.activeLevel, - value: { - class: multiclassData.class, - subclass: multiclassData.subclass, - domain: multiclassData.domain, - level: this.activeLevel - } - } - }; - updates[1] = { - path: [arrayPart, '1'].join('.'), - value: { - level: this.activeLevel, - value: { - class: multiclassData.class, - subclass: multiclassData.subclass, - domain: multiclassData.domain, - level: this.activeLevel - } - } - }; - } else { - if (levelChoices.choices.length > 0) { - if (typeof levelChoices.choices === 'string') { - const choices = foundry.utils - .getProperty(this.actor, levelChoices.choices) - .map(x => ({ name: x.description, value: x.id })); - const indexes = await SelectDialog.selectItem({ - actor: this.actor, - choices: choices, - title: levelChoices.title, - nrChoices: levelChoices.nrChoices - }); - if (indexes.length === 0) { - this.render(); - return; - } - updates[0].value.value = choices - .filter((_, index) => indexes.includes(index)) - .map(x => x.value); - } else { - const indexes = await SelectDialog.selectItem({ - actor: this.actor, - choices: levelChoices.choices, - title: levelChoices.title, - nrChoices: levelChoices.nrChoices - }); - if (indexes.length === 0) { - this.render(); - return; - } - updates[0].value.value = levelChoices.choices[indexes[0]].path; - } - } - } - - const update = updates.reduce((acc, x) => { - acc[x.path] = x.value; - - return acc; - }, {}); - - this.data = foundry.utils.mergeObject(this.data, update); - } - + static async updateForm(event, _, formData) { + await this.document.update(formData.object); this.render(); } - - static advanceLevel() { - this.activeLevel += 1; - this.render(); - } - - static async finishLevelup() { - this.data.currentLevel = this.data.changedLevel; - let multiclass = null; - for (var level in this.data.levelups) { - for (var tier in this.data.levelups[level]) { - for (var category in this.data.levelups[level][tier]) { - for (var value in this.data.levelups[level][tier][category]) { - if (category === 'multiclass') { - multiclass = this.data.levelups[level][tier][category][value].value; - this.data.levelups[level][tier][category][value] = true; - } else { - this.data.levelups[level][tier][category][value] = - this.data.levelups[level][tier][category][value].value ?? true; - } - } - } - } - } - - const tiersMoved = - getTier(this.actor.system.levelData.changedLevel, true) - - getTier(this.actor.system.levelData.currentLevel, true); - const experiences = Array.from(Array(tiersMoved), (_, index) => ({ - id: foundry.utils.randomID(), - level: this.actor.system.experiences.length + index * 3, - description: '', - value: 1 - })); - - await this.actor.update( - { - system: { - levelData: this.data, - experiences: [...this.actor.system.experiences, ...experiences] - } - }, - { diff: false } - ); - - if (!this.actor.multiclass && multiclass) { - const multiclassClass = (await fromUuid(multiclass.class.uuid)).toObject(); - multiclassClass.system.domains = [multiclass.domain.id]; - multiclassClass.system.multiclass = multiclass.level; - - const multiclassFeatures = []; - for (var i = 0; i < multiclassClass.system.features.length; i++) { - const feature = (await fromUuid(multiclassClass.system.features[i].uuid)).toObject(); - feature.system.multiclass = multiclass.level; - multiclassFeatures.push(feature); - } - - const multiclassSubclass = (await fromUuid(multiclass.subclass.uuid)).toObject(); - multiclassSubclass.system.multiclass = multiclass.level; - - const multiclassSubclassFeatures = {}; - const features = [ - multiclassSubclass.system.foundationFeature, - multiclassSubclass.system.specializationFeature, - multiclassSubclass.system.masteryFeature - ]; - for (var i = 0; i < features.length; i++) { - const path = i === 0 ? 'foundationFeature' : i === 1 ? 'specializationFeature' : 'masteryFeature'; - const feature = features[i]; - for (var ability of feature.abilities) { - const data = (await fromUuid(ability.uuid)).toObject(); - if (i > 0) data.system.disabled = true; - data.system.multiclass = multiclass.level; - if (!multiclassSubclassFeatures[path]) multiclassSubclassFeatures[path] = [data]; - else multiclassSubclassFeatures[path].push(data); - // data.uuid = feature.uuid; - - // const abilityData = await this._onDropItemCreate(data); - // ability.uuid = abilityData[0].uuid; - - // createdItems.push(abilityData); - } - } - - for (let subclassFeaturesKey in multiclassSubclassFeatures) { - const values = multiclassSubclassFeatures[subclassFeaturesKey]; - const abilityResults = await this.actor.createEmbeddedDocuments('Item', values); - for (var i = 0; i < abilityResults.length; i++) { - multiclassSubclass.system[subclassFeaturesKey].abilities[i].uuid = abilityResults[i].uuid; - } - } - - await this.actor.createEmbeddedDocuments('Item', [ - multiclassClass, - ...multiclassFeatures, - multiclassSubclass - ]); - } - - this.close(); - } } + +// import SelectDialog from '../dialogs/selectDialog.mjs'; +// import { getTier } from '../helpers/utils.mjs'; +// import DhpMulticlassDialog from './multiclassDialog.mjs'; + +// const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +// export default class DhpLevelup extends HandlebarsApplicationMixin(ApplicationV2) { +// constructor(actor) { +// super({}); + +// this.actor = actor; +// this.data = foundry.utils.deepClone(actor.system.levelData); +// this.activeLevel = actor.system.levelData.currentLevel + 1; +// } + +// get title() { +// return `${this.actor.name} - Level Up`; +// } + +// static DEFAULT_OPTIONS = { +// classes: ['daggerheart', 'views', 'levelup'], +// position: { width: 1200, height: 'auto' }, +// window: { +// resizable: true +// }, + +// actions: { +// toggleBox: this.toggleBox, +// advanceLevel: this.advanceLevel, +// finishLevelup: this.finishLevelup +// } +// }; + +// static PARTS = { +// form: { +// id: 'levelup', +// template: 'systems/daggerheart/templates/views/levelup.hbs' +// } +// }; + +// async _prepareContext(_options) { +// let selectedChoices = 0, +// multiclassing = {}, +// subclassing = {}; +// const leveledTiers = Object.keys(this.data.levelups).reduce( +// (acc, levelKey) => { +// const levelData = this.data.levelups[levelKey]; +// ['tier1', 'tier2', 'tier3'].forEach(tierKey => { +// let tierUpdate = {}; +// const tierData = levelData[tierKey]; +// if (tierData) { +// tierUpdate = Object.keys(tierData).reduce((acc, propertyKey) => { +// const values = tierData[propertyKey]; +// const level = Number.parseInt(levelKey); + +// acc[propertyKey] = Object.values(values).map(value => { +// if (value && level === this.activeLevel) selectedChoices++; +// if (propertyKey === 'multiclass') multiclassing[levelKey] = true; +// if (propertyKey === 'subclass') subclassing[tierKey] = true; + +// return { level: level, value: value }; +// }); + +// return acc; +// }, {}); +// } + +// Object.keys(tierUpdate).forEach(propertyKey => { +// const property = tierUpdate[propertyKey]; +// const propertyValues = foundry.utils.getProperty(acc, `${tierKey}.${propertyKey}`) ?? []; +// foundry.utils.setProperty(acc, `${tierKey}.${propertyKey}`, [...propertyValues, ...property]); +// }); +// }); + +// return acc; +// }, +// { tier1: {}, tier2: {}, tier3: {} } +// ); + +// const activeTier = getTier(this.activeLevel); +// const data = Object.keys(SYSTEM.ACTOR.levelupData).reduce((acc, tierKey) => { +// const tier = SYSTEM.ACTOR.levelupData[tierKey]; +// acc[tierKey] = { +// label: game.i18n.localize(tier.label), +// info: game.i18n.localize(tier.info), +// pretext: game.i18n.localize(tier.pretext), +// postext: game.i18n.localize(tier.posttext), +// active: tierKey <= activeTier, +// choices: Object.keys(tier.choices).reduce((acc, propertyKey) => { +// const property = tier.choices[propertyKey]; +// acc[propertyKey] = { description: property.description, cost: property.cost ?? 1, values: [] }; +// for (var i = 0; i < property.maxChoices; i++) { +// const leveledValue = leveledTiers[tierKey][propertyKey]?.[i]; +// const subclassLock = +// propertyKey === 'subclass' && +// Object.keys(multiclassing).find(x => getTier(Number.parseInt(x)) === tierKey); +// const subclassMulticlassLock = propertyKey === 'multiclass' && subclassing[tierKey]; +// const multiclassLock = +// propertyKey === 'multiclass' && +// Object.keys(multiclassing).length > 0 && +// !( +// leveledValue && +// Object.keys(multiclassing).find(x => Number.parseInt(x) === leveledValue.level) +// ); +// const locked = +// (leveledValue && leveledValue.level !== this.activeLevel) || +// subclassLock || +// subclassMulticlassLock || +// multiclassLock; +// const disabled = +// tierKey > activeTier || +// (selectedChoices === 2 && !(leveledValue && leveledValue.level === this.activeLevel)) || +// locked; + +// acc[propertyKey].values.push({ +// selected: leveledValue?.value !== undefined, +// path: `levelups.${this.activeLevel}.${tierKey}.${propertyKey}.${i}`, +// description: game.i18n.localize(property.description), +// disabled: disabled, +// locked: locked +// }); +// } + +// return acc; +// }, {}) +// }; + +// return acc; +// }, {}); + +// return { +// data: data, +// activeLevel: this.activeLevel, +// changedLevel: this.actor.system.levelData.changedLevel, +// completedSelection: selectedChoices === 2 +// }; +// } + +// static async toggleBox(_, button) { +// const path = button.dataset.path; +// if (foundry.utils.getProperty(this.data, path)) { +// const pathParts = path.split('.'); +// const arrayPart = pathParts.slice(0, pathParts.length - 1).join('.'); +// let array = foundry.utils.getProperty(this.data, arrayPart); +// if (button.dataset.levelAttribute === 'multiclass') { +// array = []; +// } else { +// delete array[Number.parseInt(pathParts[pathParts.length - 1])]; +// } +// foundry.utils.setProperty(this.data, arrayPart, array); +// } else { +// const updates = [{ path: path, value: { level: this.activeLevel } }]; +// const levelChoices = SYSTEM.ACTOR.levelChoices[button.dataset.levelAttribute]; +// if (button.dataset.levelAttribute === 'subclass') { +// if (!this.actor.system.multiclassSubclass) { +// updates[0].value.value = { +// multiclass: false, +// feature: this.actor.system.subclass.system.specializationFeature.unlocked +// ? 'mastery' +// : 'specialization' +// }; +// } else { +// const choices = [ +// { name: this.actor.system.subclass.name, value: this.actor.system.subclass.uuid }, +// { +// name: this.actor.system.multiclassSubclass.name, +// value: this.actor.system.multiclassSubclass.uuid +// } +// ]; +// const indexes = await SelectDialog.selectItem({ +// actor: this.actor, +// choices: choices, +// title: levelChoices.title, +// nrChoices: 1 +// }); +// if (indexes.length === 0) { +// this.render(); +// return; +// } +// const multiclassSubclass = choices[indexes[0]].name === this.actor.system.multiclassSubclass.name; +// updates[0].value.value = { +// multiclass: multiclassSubclass, +// feature: this.actor.system.multiclassSubclass.system.specializationFeature.unlocked +// ? 'mastery' +// : 'specialization' +// }; +// } +// } else if (button.dataset.levelAttribute === 'multiclass') { +// const multiclassAwait = new Promise(resolve => { +// new DhpMulticlassDialog(this.actor.name, this.actor.system.class, resolve).render(true); +// }); +// const multiclassData = await multiclassAwait; +// if (!multiclassData) { +// this.render(); +// return; +// } + +// const pathParts = path.split('.'); +// const arrayPart = pathParts.slice(0, pathParts.length - 1).join('.'); +// updates[0] = { +// path: [arrayPart, '0'].join('.'), +// value: { +// level: this.activeLevel, +// value: { +// class: multiclassData.class, +// subclass: multiclassData.subclass, +// domain: multiclassData.domain, +// level: this.activeLevel +// } +// } +// }; +// updates[1] = { +// path: [arrayPart, '1'].join('.'), +// value: { +// level: this.activeLevel, +// value: { +// class: multiclassData.class, +// subclass: multiclassData.subclass, +// domain: multiclassData.domain, +// level: this.activeLevel +// } +// } +// }; +// } else { +// if (levelChoices.choices.length > 0) { +// if (typeof levelChoices.choices === 'string') { +// const choices = foundry.utils +// .getProperty(this.actor, levelChoices.choices) +// .map(x => ({ name: x.description, value: x.id })); +// const indexes = await SelectDialog.selectItem({ +// actor: this.actor, +// choices: choices, +// title: levelChoices.title, +// nrChoices: levelChoices.nrChoices +// }); +// if (indexes.length === 0) { +// this.render(); +// return; +// } +// updates[0].value.value = choices +// .filter((_, index) => indexes.includes(index)) +// .map(x => x.value); +// } else { +// const indexes = await SelectDialog.selectItem({ +// actor: this.actor, +// choices: levelChoices.choices, +// title: levelChoices.title, +// nrChoices: levelChoices.nrChoices +// }); +// if (indexes.length === 0) { +// this.render(); +// return; +// } +// updates[0].value.value = levelChoices.choices[indexes[0]].path; +// } +// } +// } + +// const update = updates.reduce((acc, x) => { +// acc[x.path] = x.value; + +// return acc; +// }, {}); + +// this.data = foundry.utils.mergeObject(this.data, update); +// } + +// this.render(); +// } + +// static advanceLevel() { +// this.activeLevel += 1; +// this.render(); +// } + +// static async finishLevelup() { +// this.data.currentLevel = this.data.changedLevel; +// let multiclass = null; +// for (var level in this.data.levelups) { +// for (var tier in this.data.levelups[level]) { +// for (var category in this.data.levelups[level][tier]) { +// for (var value in this.data.levelups[level][tier][category]) { +// if (category === 'multiclass') { +// multiclass = this.data.levelups[level][tier][category][value].value; +// this.data.levelups[level][tier][category][value] = true; +// } else { +// this.data.levelups[level][tier][category][value] = +// this.data.levelups[level][tier][category][value].value ?? true; +// } +// } +// } +// } +// } + +// const tiersMoved = +// getTier(this.actor.system.levelData.changedLevel, true) - +// getTier(this.actor.system.levelData.currentLevel, true); +// const experiences = Array.from(Array(tiersMoved), (_, index) => ({ +// id: foundry.utils.randomID(), +// level: this.actor.system.experiences.length + index * 3, +// description: '', +// value: 1 +// })); + +// await this.actor.update( +// { +// system: { +// levelData: this.data, +// experiences: [...this.actor.system.experiences, ...experiences] +// } +// }, +// { diff: false } +// ); + +// if (!this.actor.multiclass && multiclass) { +// const multiclassClass = (await fromUuid(multiclass.class.uuid)).toObject(); +// multiclassClass.system.domains = [multiclass.domain.id]; +// multiclassClass.system.multiclass = multiclass.level; + +// const multiclassFeatures = []; +// for (var i = 0; i < multiclassClass.system.features.length; i++) { +// const feature = (await fromUuid(multiclassClass.system.features[i].uuid)).toObject(); +// feature.system.multiclass = multiclass.level; +// multiclassFeatures.push(feature); +// } + +// const multiclassSubclass = (await fromUuid(multiclass.subclass.uuid)).toObject(); +// multiclassSubclass.system.multiclass = multiclass.level; + +// const multiclassSubclassFeatures = {}; +// const features = [ +// multiclassSubclass.system.foundationFeature, +// multiclassSubclass.system.specializationFeature, +// multiclassSubclass.system.masteryFeature +// ]; +// for (var i = 0; i < features.length; i++) { +// const path = i === 0 ? 'foundationFeature' : i === 1 ? 'specializationFeature' : 'masteryFeature'; +// const feature = features[i]; +// for (var ability of feature.abilities) { +// const data = (await fromUuid(ability.uuid)).toObject(); +// if (i > 0) data.system.disabled = true; +// data.system.multiclass = multiclass.level; +// if (!multiclassSubclassFeatures[path]) multiclassSubclassFeatures[path] = [data]; +// else multiclassSubclassFeatures[path].push(data); +// // data.uuid = feature.uuid; + +// // const abilityData = await this._onDropItemCreate(data); +// // ability.uuid = abilityData[0].uuid; + +// // createdItems.push(abilityData); +// } +// } + +// for (let subclassFeaturesKey in multiclassSubclassFeatures) { +// const values = multiclassSubclassFeatures[subclassFeaturesKey]; +// const abilityResults = await this.actor.createEmbeddedDocuments('Item', values); +// for (var i = 0; i < abilityResults.length; i++) { +// multiclassSubclass.system[subclassFeaturesKey].abilities[i].uuid = abilityResults[i].uuid; +// } +// } + +// await this.actor.createEmbeddedDocuments('Item', [ +// multiclassClass, +// ...multiclassFeatures, +// multiclassSubclass +// ]); +// } + +// this.close(); +// } +// } diff --git a/module/applications/sheets/pc.mjs b/module/applications/sheets/pc.mjs index 4727166e..fba2d335 100644 --- a/module/applications/sheets/pc.mjs +++ b/module/applications/sheets/pc.mjs @@ -1,10 +1,10 @@ import { capitalize } from '../../helpers/utils.mjs'; import DhpDeathMove from '../deathMove.mjs'; import DhpDowntime from '../downtime.mjs'; -import DhpLevelup from '../levelup.mjs'; import AncestrySelectionDialog from '../ancestrySelectionDialog.mjs'; import DaggerheartSheet from './daggerheart-sheet.mjs'; import { abilities } from '../../config/actorConfig.mjs'; +import DhlevelUp from '../levelup.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; @@ -187,7 +187,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { context.storyEditor = this.storyEditor; context.multiclassFeatureSetSelected = this.multiclassFeatureSetSelected; - const selectedAttributes = Object.values(this.document.system.attributes).map(x => x.data.base); + const selectedAttributes = Object.values(this.document.system.traits).map(x => x.data.base); context.abilityScoreArray = JSON.parse( await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray) ).reduce((acc, x) => { @@ -621,7 +621,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { } openLevelUp() { - new DhpLevelup(this.document).render(true); + new DhlevelUp(this.document).render(true); } static domainCardsTab(toVault) { diff --git a/module/data/levelTier.mjs b/module/data/levelTier.mjs index ff27e4e8..7eb316e6 100644 --- a/module/data/levelTier.mjs +++ b/module/data/levelTier.mjs @@ -42,51 +42,17 @@ class DhLevelOption extends foundry.abstract.DataModel { checkboxQuantity: new fields.NumberField({ required: true, integer: true, initial: 1 }), minCost: new fields.NumberField({ required: true, integer: true, initial: 1 }), type: new fields.StringField({ required: true, choices: LevelOptionType }), - choice: new fields.StringField(), value: new fields.NumberField({ integer: true }), amount: new fields.NumberField({ integer: true }) }; } } -// class DhLevelOptionType extends foundry.abstract.DataModel { -// static defineSchema(){ -// return new fields.SchemaField({ -// trait: new fields.SchemaField({ -// id: new fields.StringField({ required: true }), -// label: new fields.StringField({ required: true }), -// }), -// attribute: new fields.SchemaField({ -// id: new fields.StringField({ required: true }), -// label: new fields.StringField({ required: true }), -// choice: new fields.StringField({ required: true, choices: attributeChoices }) -// }), -// experience: new fields.SchemaField({ -// id: new fields.StringField({ required: true }), -// label: new fields.StringField({ required: true }), -// }), -// domainCard: new fields.SchemaField({ -// id: new fields.StringField({ required: true }), -// label: new fields.StringField({ required: true }), -// }), -// subclass: new fields.SchemaField({ -// id: new fields.StringField({ required: true }), -// label: new fields.StringField({ required: true }), -// }), -// }); -// } -// } - -const LevelOptionType = { +export const LevelOptionType = { trait: { id: 'trait', label: 'Character Trait' }, - // attribute: { - // id: 'attribute', - // label: 'Attribute', - // choices: attributeChoices, - // }, hitPoint: { id: 'hitPoint', label: 'Hit Points' @@ -121,25 +87,6 @@ const LevelOptionType = { } }; -// const attributeChoices = { -// hitPoint: { -// id: 'hitPoint', -// label: 'Hit Points', -// }, -// stress: { -// id: 'stress', -// label: 'Stress', -// }, -// evasion: { -// id: 'evasion', -// label: 'Evasion', -// }, -// proficiency: { -// id: 'proficiency', -// label: 'Proficiency', -// }, -// }; - export const defaultLevelTiers = { tiers: { 2: { diff --git a/module/data/levelup.mjs b/module/data/levelup.mjs new file mode 100644 index 00000000..93bc6491 --- /dev/null +++ b/module/data/levelup.mjs @@ -0,0 +1,127 @@ +import { LevelOptionType } from './levelTier.mjs'; + +export class DhLevelup extends foundry.abstract.DataModel { + static initializeData(levelTierData, levelChoices) { + return { + tiers: Object.keys(levelTierData.tiers).reduce((acc, key) => { + acc[key] = DhLevelupTier.initializeData(levelTierData.tiers[key]); + + return acc; + }, {}) + }; + } + + static defineSchema() { + const fields = foundry.data.fields; + + return { + tiers: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupTier)) + }; + } + + get totalSelections() { + return Object.values(this.tiers).reduce((acc, tier) => acc + tier.nrSelections, 0); + } +} + +class DhLevelupTier extends foundry.abstract.DataModel { + static initializeData(levelTier, levelChoices) { + const levels = {}; + const levelEndCap = levelTier.levels.end + 1; + for (var level = levelTier.levels.start; level < levelEndCap; level++) { + levels[level] = DhLevelupLevel.initializeData(levelTier.availableOptions, levelTier.options); + } + + return { + tier: levelTier.tier, + name: levelTier.name, + options: Object.keys(levelTier.options).reduce((acc, key) => { + acc[key] = levelTier.options[key]; + + return acc; + }, {}), + levels: levels, + maxSelections: levelTier.availableOptions * (levelEndCap - levelTier.levels.start) + }; + } + + static defineSchema() { + const fields = foundry.data.fields; + + return { + tier: new fields.NumberField({ required: true, integer: true }), + name: new fields.StringField({ required: true }), + options: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupTierOption)), + levels: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupLevel)), + maxSelections: new fields.NumberField({ required: true, integer: true }) + }; + } + + get nrSelections() { + return Object.values(this.levels).reduce((acc, level) => acc + level.nrSelections, 0); + } + + /* Data to render all options in a Tier from */ + get tierCheckboxGroups() { + return Object.keys(this.options).map(optionKey => { + const option = this.options[optionKey]; + return { + label: game.i18n.localize(option.label), + checkboxes: [...Array(option.checkboxQuantity).keys()].map(checkboxNr => { + const levelId = Object.keys(this.levels).find(levelKey => { + Object.values(this.levels[levelKey].optionSelections).some(nr => nr === checkboxNr); + }); + return { + ...option, + tier: this.tier, + level: levelId, + selected: Boolean(levelId), + optionkey: optionKey, + checkboxNr: checkboxNr + }; + }) + }; + }); + } +} + +class DhLevelupTierOption extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + label: new fields.StringField({ required: true }), + checkboxQuantity: new fields.NumberField({ required: true, integer: true }), + minCost: new fields.NumberField({ required: true, integer: true }), + type: new fields.StringField({ required: true, choices: LevelOptionType }), + value: new fields.NumberField({ integer: true }), + amount: new fields.NumberField({ integer: true }) + }; + } +} + +class DhLevelupLevel extends foundry.abstract.DataModel { + static initializeData(maxSelections, levelOptions, levelChoices) { + return { + maxSelections: maxSelections, + optionSelections: {} // collate levelOption and levelChoices, + }; + } + + static defineSchema() { + const fields = foundry.data.fields; + + return { + maxSelections: new fields.NumberField({ required: true, integer: true }), + optionSelections: new fields.TypedObjectField( + new fields.SchemaField({ + checkboxNr: new fields.NumberField({ required: true, integer: true }) + }) + ) + }; + } + + get nrSelections() { + return this.optionSelections.length; + } +} diff --git a/module/data/pc.mjs b/module/data/pc.mjs index d7b6e4f8..74cff93b 100644 --- a/module/data/pc.mjs +++ b/module/data/pc.mjs @@ -104,7 +104,8 @@ export default class DhpPC extends foundry.abstract.TypeDataModel { get canLevelUp() { // return Object.values(this.levels.data).some(x => !x.completed); - return this.levelData.currentLevel !== this.levelData.changedLevel; + // return this.levelData.currentLevel !== this.levelData.changedLevel; + return true; } get tier() { @@ -334,20 +335,20 @@ export default class DhpPC extends foundry.abstract.TypeDataModel { for (var attributeKey in this.traits) { const attribute = this.traits[attributeKey]; - attribute.levelMark = attribute.levelMarks.find(x => this.isSameTier(x)) ?? null; + // attribute.levelMark = attribute.levelMarks.find(x => this.isSameTier(x)) ?? null; - const actualValue = attribute.data.base + attribute.levelMarks.length + attribute.data.bonus; - attribute.data.actualValue = actualValue; - attribute.data.value = attribute.data.overrideValue - ? attribute.data.overrideValue - : attribute.data.actualValue; + // const actualValue = attribute.data.base + attribute.levelMarks.length + attribute.data.bonus; + // attribute.data.actualValue = actualValue; + // attribute.data.value = attribute.data.overrideValue + // ? attribute.data.overrideValue + // : attribute.data.actualValue; } this.evasion = this.class?.system?.evasion ?? 0; // this.armor.value = this.activeArmor?.baseScore ?? 0; - this.damageThresholds = this.computeDamageThresholds(); + // this.damageThresholds = this.computeDamageThresholds(); - this.applyLevels(); + // this.applyLevels(); this.applyEffects(); } diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 0ecfcbc4..74d3b2f4 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -2784,6 +2784,32 @@ div.daggerheart.views.multiclass { .item-button .item-icon.checked { opacity: 1; } +.theme-light { + /* Add specifics*/ +} +.daggerheart.levelup .tiers-container { + display: flex; + gap: 16px; +} +.daggerheart.levelup .tiers-container .tier-container { + display: flex; + flex-direction: column; + gap: 8px; + background-image: url('../assets/parchments/dh-parchment-dark.png'); +} +.daggerheart.levelup .tiers-container .tier-container legend { + margin-left: auto; + margin-right: auto; +} +.daggerheart.levelup .tiers-container .tier-container .checkbox-group-container { + display: grid; + grid-template-columns: 3fr 1fr; + gap: 4px; +} +.daggerheart.levelup .tiers-container .tier-container .checkbox-group-container .checkboxes-container { + display: flex; + gap: 4px; +} .application.sheet.daggerheart.dh-style.feature .item-sheet-header { display: flex; } diff --git a/styles/daggerheart.less b/styles/daggerheart.less index d55a4aec..c8877e80 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -9,6 +9,7 @@ @import './sheets//sheets.less'; @import './components.less'; @import './dialog.less'; +@import './levelup.less'; @import '../node_modules/@yaireo/tagify/dist/tagify.css'; // new styles imports diff --git a/styles/levelup.less b/styles/levelup.less new file mode 100644 index 00000000..0d3e07db --- /dev/null +++ b/styles/levelup.less @@ -0,0 +1,36 @@ +.theme-light { + /* Add specifics*/ +} + +.daggerheart.levelup { + .tiers-container { + display: flex; + gap: 16px; + + .tier-container { + display: flex; + flex-direction: column; + gap: 8px; + background-image: url('../assets/parchments/dh-parchment-dark.png'); + + legend { + margin-left: auto; + margin-right: auto; + } + + .checkbox-group-container { + display: grid; + grid-template-columns: 3fr 1fr; + gap: 4px; + + .checkbox-group-container-title { + } + + .checkboxes-container { + display: flex; + gap: 4px; + } + } + } + } +} diff --git a/templates/sheets/parts/attributes.hbs b/templates/sheets/parts/attributes.hbs index 46a15317..2416c763 100644 --- a/templates/sheets/parts/attributes.hbs +++ b/templates/sheets/parts/attributes.hbs @@ -10,9 +10,9 @@
{{key}}
-
+ {{!--
-
+
--}}
{{#if ../editAttributes}} + {{/each}} +
+ + {{/each}} + + {{/each}} + + + +{{!--
Level {{activeLevel}}
{{#each data}} {{> "systems/daggerheart/templates/views/parts/level.hbs" data=this }} {{/each}} - {{!-- {{#each levelupConfig as |configData key|}} - {{> "systems/daggerheart/templates/views/parts/level.hbs" configData=configData levelData=(lookup ../levelData key) completedSelection=../completedSelection activeTier=../activeTier activeLevel=../activeLevel category=key }} - {{/each}} --}}
-
\ No newline at end of file + --}} \ No newline at end of file