import { chunkify } from '../helpers/utils.mjs'; import { LevelOptionType } from './levelTier.mjs'; export class DhLevelup extends foundry.abstract.DataModel { static initializeData(levelTierData, pcLevelData) { const startLevel = pcLevelData.level.current + 1; const currentLevel = pcLevelData.level.current + 1; const endLevel = pcLevelData.level.changed; const tiers = {}; const levels = {}; const tierKeys = Object.keys(levelTierData.tiers); tierKeys.forEach(key => { const tier = levelTierData.tiers[key]; const belongingLevels = []; for (var i = tier.levels.start; i <= tier.levels.end; i++) { if (i <= endLevel) { const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {}; const experiences = initialAchievements.experience ? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => { acc[foundry.utils.randomID()] = { name: '', modifier: initialAchievements.experience.modifier }; return acc; }, {}) : {}; const domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => { const id = foundry.utils.randomID(); acc[id] = { uuid: null, itemUuid: null, level: i }; return acc; }, {}); levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], tier.availableOptions, { ...initialAchievements, experiences, domainCards }); } belongingLevels.push(i); } tiers[key] = { name: tier.name, belongingLevels: belongingLevels, options: Object.keys(tier.options).reduce((acc, key) => { acc[key] = tier.options[key].toObject(); return acc; }, {}) }; }); return { tiers, levels, startLevel, currentLevel, endLevel }; } static defineSchema() { const fields = foundry.data.fields; return { tiers: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField({ required: true }), belongingLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })), options: new fields.TypedObjectField( new fields.SchemaField({ label: new fields.StringField({ required: true }), checkboxSelections: 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 }) }) ) }) ), levels: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupLevel)), startLevel: new fields.NumberField({ required: true, integer: true }), currentLevel: new fields.NumberField({ required: true, integer: true }), endLevel: new fields.NumberField({ required: true, integer: true }), multiclass: new fields.SchemaField({ class: new fields.StringField({ required: true }), domain: new fields.StringField(), level: new fields.NumberField({ required: true, integer: true }) }) }; } #levelFinished(levelKey) { const allSelectionsMade = this.levels[levelKey].nrSelections.available === 0; const allChoicesMade = Object.keys(this.levels[levelKey].choices).every(choiceKey => { const choice = this.levels[levelKey].choices[choiceKey]; return Object.values(choice).every(checkbox => { switch (choiceKey) { case 'trait': case 'experience': case 'domainCard': case 'subclass': return checkbox.amount ? checkbox.data.length === checkbox.amount : checkbox.data.length === 1; case 'multiclass': const classSelected = checkbox.data.length === 1; const domainSelected = checkbox.secondaryData; return classSelected && domainSelected; default: return true; } }); }); const experiencesSelected = !this.levels[levelKey].achievements.experiences ? true : Object.values(this.levels[levelKey].achievements.experiences).every(exp => exp.name); const domainCardsSelected = Object.values(this.levels[levelKey].achievements.domainCards) .filter(x => x.level <= this.endLevel) .every(card => card.uuid); const allAchievementsSelected = experiencesSelected && domainCardsSelected; return allSelectionsMade && allChoicesMade && allAchievementsSelected; } get currentLevelFinished() { return this.#levelFinished(this.currentLevel); } get allLevelsFinished() { return Object.keys(this.levels) .filter(level => Number(level) >= this.startLevel) .every(this.#levelFinished.bind(this)); } get tiersForRendering() { const tierKeys = Object.keys(this.tiers); const selections = Object.keys(this.levels).reduce( (acc, key) => { const level = this.levels[key]; Object.keys(level.choices).forEach(optionKey => { const choice = level.choices[optionKey]; Object.keys(choice).forEach(checkboxNr => { const checkbox = choice[checkboxNr]; if (!acc[checkbox.tier][optionKey]) acc[checkbox.tier][optionKey] = {}; Object.keys(choice).forEach(checkboxNr => { acc[checkbox.tier][optionKey][checkboxNr] = { ...checkbox, level: Number(key) }; }); }); }); return acc; }, tierKeys.reduce((acc, key) => { acc[key] = {}; return acc; }, {}) ); return tierKeys.map(tierKey => { const tier = this.tiers[tierKey]; return { name: tier.name, active: this.currentLevel >= Math.min(...tier.belongingLevels), groups: Object.keys(tier.options).map(optionKey => { const option = tier.options[optionKey]; const checkboxes = [...Array(option.checkboxSelections).keys()].flatMap(index => { const checkboxNr = index + 1; const checkboxData = selections[tierKey]?.[optionKey]?.[checkboxNr]; const checkbox = { ...option, checkboxNr, tier: tierKey }; if (checkboxData) { checkbox.level = checkboxData.level; checkbox.selected = true; checkbox.disabled = checkbox.level !== this.currentLevel; } return checkbox; }); return { label: game.i18n.localize(option.label), checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => { const anySelected = chunkedBoxes.some(x => x.selected); const anyDisabled = chunkedBoxes.some(x => x.disabled); return { multi: option.minCost > 1, checkboxes: chunkedBoxes.map(x => ({ ...x, selected: anySelected, disabled: anyDisabled })) }; }) }; }) }; }); } } export class DhLevelupLevel extends foundry.abstract.DataModel { static initializeData(levelData = { selections: [] }, maxSelections, achievements) { return { maxSelections: maxSelections, achievements: { experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {}, domainCards: levelData.achievements?.domainCards ? levelData.achievements.domainCards.reduce((acc, card, index) => { acc[index] = { ...card }; return acc; }, {}) : (achievements.domainCards ?? {}), proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null }, choices: levelData.selections.reduce((acc, data) => { if (!acc[data.optionKey]) acc[data.optionKey] = {}; acc[data.optionKey][data.checkboxNr] = { ...data }; return acc; }, {}) }; } static defineSchema() { const fields = foundry.data.fields; return { maxSelections: new fields.NumberField({ required: true, integer: true }), achievements: new fields.SchemaField({ experiences: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField({ required: true }), modifier: new fields.NumberField({ required: true, integer: true }) }) ), domainCards: new fields.TypedObjectField( new fields.SchemaField({ uuid: new fields.StringField({ required: true, nullable: true, initial: null }), itemUuid: new fields.StringField({ required: true }), level: new fields.NumberField({ required: true, integer: true }) }) ), proficiency: new fields.NumberField({ integer: true }) }), choices: new fields.TypedObjectField( new fields.TypedObjectField( new fields.SchemaField({ tier: new fields.NumberField({ required: true, integer: true }), minCost: new fields.NumberField({ required: true, integer: true }), amount: new fields.NumberField({ integer: true }), value: new fields.StringField(), data: new fields.ArrayField(new fields.StringField()), secondaryData: new fields.StringField(), type: new fields.StringField({ required: true }) }) ) ) }; } get nrSelections() { const selections = Object.keys(this.choices).reduce((acc, choiceKey) => { const choice = this.choices[choiceKey]; acc += Object.values(choice).reduce((acc, x) => acc + x.minCost, 0); return acc; }, 0); return { selections: selections, available: this.maxSelections - selections }; } }