daggerheart/module/applications/levelup.mjs
WBHarry b24cdcc9ed
Adding Prettier
* Added prettier with automatic useage on pre-commit to avoid style breakage
* Ran Prettier on the project
2025-05-23 18:57:50 +02:00

372 lines
16 KiB
JavaScript

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 = {
id: 'daggerheart-levelup',
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();
}
}