[Fix] Levelup Fixes (#787)

* Fixed crash on experience selection. Fixed subclass error on multiclassing

* Fixed so multiclasses do not gain the hope feature for the class

* Fixed so Class/Subclass features are properly deleted on delevel

* Removed automatic deletion of features on delevel when not using levelup auto

* Fixed so custom domains can be selected in levelup when multiclassing
This commit is contained in:
WBHarry 2025-08-10 20:19:37 +02:00 committed by GitHub
parent f81d0d250f
commit b470a1dc51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 102 additions and 88 deletions

View file

@ -51,7 +51,7 @@ export default class DhCharacterLevelUp extends LevelUpBase {
.filter(exp => exp.data.length > 0) .filter(exp => exp.data.length > 0)
.flatMap(exp => .flatMap(exp =>
exp.data.map(data => { exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(x => x === data); const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name; return this.actor.system.experiences[experience].name;
}) })
); );

View file

@ -39,7 +39,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
.filter(exp => exp.data.length > 0) .filter(exp => exp.data.length > 0)
.flatMap(exp => .flatMap(exp =>
exp.data.map(data => { exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(x => x === data); const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name; return this.actor.system.experiences[experience].name;
}) })
); );

View file

@ -351,6 +351,17 @@ export default class DhCharacter extends BaseDataActor {
return [...classDomains, ...multiclassDomains]; return [...classDomains, ...multiclassDomains];
} }
get domainData() {
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
return this.domains.map(key => {
const domain = allDomainData[key];
return {
...domain,
label: game.i18n.localize(domain.label)
};
});
}
get domainCards() { get domainCards() {
const domainCards = this.parent.items.filter(x => x.type === 'domainCard'); const domainCards = this.parent.items.filter(x => x.type === 'domainCard');
const loadout = domainCards.filter(x => !x.system.inVault); const loadout = domainCards.filter(x => !x.system.inVault);

View file

@ -60,17 +60,6 @@ export default class DHClass extends BaseDataItem {
/* -------------------------------------------- */ /* -------------------------------------------- */
get domainData() {
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
return this.domains.map(key => {
const domain = allDomainData[key];
return {
...domain,
label: game.i18n.localize(domain.label)
};
});
}
get hopeFeatures() { get hopeFeatures() {
return this.features.filter(x => x.type === CONFIG.DH.ITEM.featureSubTypes.hope).map(x => x.item); return this.features.filter(x => x.type === CONFIG.DH.ITEM.featureSubTypes.hope).map(x => x.item);
} }

View file

@ -21,7 +21,7 @@ export default class DHSubclass extends BaseDataItem {
integer: false, integer: false,
nullable: true, nullable: true,
initial: null, initial: null,
label: "DAGGERHEART.ITEMS.Subclass.spellcastingTrait" label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait'
}), }),
features: new ItemLinkFields(), features: new ItemLinkFields(),
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }), featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
@ -50,7 +50,8 @@ export default class DHSubclass extends BaseDataItem {
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
if (this.actor?.type === 'character') { if (this.actor?.type === 'character') {
const dataUuid = data.uuid ?? data._stats?.compendiumSource ?? `Item.${data._id}`; const dataUuid =
(data.uuid ?? data.folder) ? `Compendium.daggerheart.subclasses.Item.${data._id}` : `Item.${data._id}`;
if (this.actor.system.class.subclass) { if (this.actor.system.class.subclass) {
if (this.actor.system.multiclass.subclass) { if (this.actor.system.multiclass.subclass) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent'));

View file

@ -84,6 +84,8 @@ export default class DhpActor extends Actor {
await this.update({ 'system.levelData.level.changed': Math.min(newLevel, maxLevel) }); await this.update({ 'system.levelData.level.changed': Math.min(newLevel, maxLevel) });
} else { } else {
const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
const usedLevel = Math.max(newLevel, 1); const usedLevel = Math.max(newLevel, 1);
if (newLevel < 1) { if (newLevel < 1) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.tooLowLevel')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.tooLowLevel'));
@ -95,79 +97,90 @@ export default class DhpActor extends Actor {
return acc; return acc;
}, {}); }, {});
const features = []; if (levelupAuto) {
const domainCards = []; const features = [];
const experiences = []; const domainCards = [];
const subclassFeatureState = { class: null, multiclass: null }; const experiences = [];
let multiclass = null; const subclassFeatureState = { class: null, multiclass: null };
Object.keys(this.system.levelData.levelups) let multiclass = null;
.filter(x => x > usedLevel) Object.keys(this.system.levelData.levelups)
.forEach(levelKey => { .filter(x => x > usedLevel)
const level = this.system.levelData.levelups[levelKey]; .forEach(levelKey => {
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid); const level = this.system.levelData.levelups[levelKey];
const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid); const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
domainCards.push(...achievementCards, ...advancementCards); const advancementCards = level.selections
experiences.push(...Object.keys(level.achievements.experiences)); .filter(x => x.type === 'domainCard')
features.push(...level.selections.flatMap(x => x.features)); .map(x => x.itemUuid);
domainCards.push(...achievementCards, ...advancementCards);
experiences.push(...Object.keys(level.achievements.experiences));
features.push(...level.selections.flatMap(x => x.features));
const subclass = level.selections.find(x => x.type === 'subclass'); const subclass = level.selections.find(x => x.type === 'subclass');
if (subclass) { if (subclass) {
const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class'; const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class';
const subclassState = Number(subclass.secondaryData.featureState) - 1; const subclassState = Number(subclass.secondaryData.featureState) - 1;
subclassFeatureState[path] = subclassFeatureState[path] subclassFeatureState[path] = subclassFeatureState[path]
? Math.min(subclassState, subclassFeatureState[path]) ? Math.min(subclassState, subclassFeatureState[path])
: subclassState; : subclassState;
} }
multiclass = level.selections.find(x => x.type === 'multiclass'); multiclass = level.selections.find(x => x.type === 'multiclass');
}); });
for (let feature of features) { for (let feature of features) {
if (feature.onPartner && !this.system.partner) continue; if (feature.onPartner && !this.system.partner) continue;
const document = feature.onPartner ? this.system.partner : this; const document = feature.onPartner ? this.system.partner : this;
document.items.get(feature.id)?.delete(); document.items.get(feature.id)?.delete();
}
if (experiences.length > 0) {
const getUpdate = () => ({
'system.experiences': experiences.reduce((acc, key) => {
acc[`-=${key}`] = null;
return acc;
}, {})
});
this.update(getUpdate());
if (this.system.companion) {
this.system.companion.update(getUpdate());
} }
}
if (subclassFeatureState.class) { if (experiences.length > 0) {
this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class }); const getUpdate = () => ({
} 'system.experiences': experiences.reduce((acc, key) => {
acc[`-=${key}`] = null;
if (subclassFeatureState.multiclass) { return acc;
this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass }); }, {})
} });
this.update(getUpdate());
if (multiclass) { if (this.system.companion) {
const multiclassSubclass = this.items.find(x => x.type === 'subclass' && x.system.isMulticlass); this.system.companion.update(getUpdate());
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) { if (subclassFeatureState.class) {
const itemCard = this.items.find(x => x.uuid === domainCard); this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class });
itemCard.delete(); }
if (subclassFeatureState.multiclass) {
this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass });
}
if (multiclass) {
const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid);
const multiclassFeatures = this.items.filter(
x => x.system.originItemType === 'class' && x.system.identifier === 'multiclass'
);
const subclassFeatures = this.items.filter(
x => x.system.originItemType === 'subclass' && x.system.identifier === 'multiclass'
);
this.deleteEmbeddedDocuments(
'Item',
[multiclassItem, ...multiclassFeatures, ...subclassFeatures].map(x => x.id)
);
this.update({
'system.multiclass': {
value: null,
subclass: null
}
});
}
for (let domainCard of domainCards) {
const itemCard = this.items.find(x => x.uuid === domainCard);
itemCard.delete();
}
} }
await this.update({ await this.update({
@ -315,6 +328,7 @@ export default class DhpActor extends Actor {
...multiclassData, ...multiclassData,
system: { system: {
...multiclassData.system, ...multiclassData.system,
features: multiclassData.system.features.filter(x => x.type !== 'hope'),
domains: [multiclass.secondaryData.domain], domains: [multiclass.secondaryData.domain],
isMulticlass: true isMulticlass: true
} }

View file

@ -85,13 +85,12 @@ export const chunkify = (array, chunkSize, mappingFunc) => {
export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}) => { export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}) => {
const { maxTags } = tagifyOptions; const { maxTags } = tagifyOptions;
const options = const options = Array.isArray(baseOptions)
typeof baseOptions === 'object' ? baseOptions
? Object.keys(baseOptions).map(optionKey => ({ : Object.keys(baseOptions).map(optionKey => ({
...baseOptions[optionKey], ...baseOptions[optionKey],
id: optionKey id: optionKey
})) }));
: baseOptions;
const tagifyElement = new Tagify(element, { const tagifyElement = new Tagify(element, {
tagTextProp: 'name', tagTextProp: 'name',

View file

@ -87,7 +87,7 @@
</div> </div>
{{#if document.system.class.value}} {{#if document.system.class.value}}
<div class="domains-section"> <div class="domains-section">
{{#each document.system.class.value.system.domainData as |data|}} {{#each document.system.domainData as |data|}}
<div class="domain"> <div class="domain">
<img src="{{data.src}}" alt="" data-tooltip="{{data.label}}" /> <img src="{{data.src}}" alt="" data-tooltip="{{data.label}}" />
</div> </div>