Added PC level/delevel benefits of leveling up

This commit is contained in:
WBHarry 2025-05-31 21:27:24 +02:00
parent 264a79f6e8
commit 81e9bd8c19
19 changed files with 790 additions and 154 deletions

View file

@ -285,6 +285,7 @@ const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/sheets/pc/parts/advancementCard.hbs', 'systems/daggerheart/templates/sheets/pc/parts/advancementCard.hbs',
'systems/daggerheart/templates/views/parts/level.hbs', 'systems/daggerheart/templates/views/parts/level.hbs',
'systems/daggerheart/templates/components/card-preview.hbs', 'systems/daggerheart/templates/components/card-preview.hbs',
'systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs',
'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs'
]); ]);
}; };

View file

@ -770,7 +770,11 @@
"damageThresholdSevereIncrease": "Severe: {threshold}", "damageThresholdSevereIncrease": "Severe: {threshold}",
"newExperiences": "New Experiences", "newExperiences": "New Experiences",
"experiencePlaceholder": "A new experience..", "experiencePlaceholder": "A new experience..",
"domainCards": "Domain Cards" "domainCards": "Domain Cards",
"subclass": "Subclass",
"multiclass": "Multiclass",
"traits": "Increased Traits",
"experienceIncreases": "Experience Increases"
}, },
"notifications": { "notifications": {
"info": { "info": {
@ -781,7 +785,9 @@
}, },
"error": { "error": {
"domainCardWrongDomain": "You don't have access to that Domain", "domainCardWrongDomain": "You don't have access to that Domain",
"domainCardToHighLevel": "The Domain Card is too high level to be selected" "domainCardToHighLevel": "The Domain Card is too high level to be selected",
"noSelectionsLeft": "Nothing more to select!",
"alreadySelectedClass": "You already have that class!"
} }
} }
}, },
@ -944,7 +950,10 @@
}, },
"NewItem": "New Item", "NewItem": "New Item",
"NewScar": "New Scar", "NewScar": "New Scar",
"DeleteConfirmation": "Are you sure you want to delete the item - {item}?" "DeleteConfirmation": "Are you sure you want to delete the item - {item}?",
"Errors": {
"missingClassOrSubclass": "The character doesn't have a class and subclass"
}
}, },
"Adversary": { "Adversary": {
"Description": "Description", "Description": "Description",

View file

@ -1,4 +1,7 @@
import { abilities } from '../config/actorConfig.mjs';
import { domains } from '../config/domainConfig.mjs';
import { DhLevelup } from '../data/levelup.mjs'; import { DhLevelup } from '../data/levelup.mjs';
import { tagifyElement } from '../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -27,7 +30,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
resizable: true resizable: true
}, },
actions: { actions: {
save: this.save save: this.save,
viewCompendium: this.viewCompendium,
selectPreview: this.selectPreview
}, },
form: { form: {
handler: this.updateForm, handler: this.updateForm,
@ -93,7 +98,27 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
return acc; return acc;
}, {}); }, {});
context.newExperiences = this.levelup.allInitialAchievements.newExperiences; const traits = Object.values(context.advancementChoices.trait ?? {});
context.traits = {
values: traits.filter(trait => trait.data.length > 0).flatMap(trait => trait.data),
active: traits.length > 0
};
const experienceIncreases = Object.values(context.advancementChoices.experience ?? {});
context.experienceIncreases = {
values: experienceIncreases.filter(trait => trait.data.length > 0).flatMap(trait => trait.data),
active: experienceIncreases.length > 0
};
context.newExperiences = Object.keys(this.levelup.allInitialAchievements).flatMap(level => {
const achievement = this.levelup.allInitialAchievements[level];
return Object.keys(achievement.newExperiences).map(key => ({
...achievement.newExperiences[key],
level: level,
key: key
}));
});
const allDomainCards = { const allDomainCards = {
...context.advancementChoices.domainCard, ...context.advancementChoices.domainCard,
...this.levelup.domainCards ...this.levelup.domainCards
@ -102,7 +127,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.domainCards = []; context.domainCards = [];
for (var domainCard of allDomainCardValues) { for (var domainCard of allDomainCardValues) {
const uuid = domainCard.data ?? domainCard.uuid; const uuid = domainCard.data?.length > 0 ? domainCard.data[0] : domainCard.uuid;
const card = uuid ? await foundry.utils.fromUuid(uuid) : { path: domainCard.path }; const card = uuid ? await foundry.utils.fromUuid(uuid) : { path: domainCard.path };
context.domainCards.push({ context.domainCards.push({
...(card.toObject?.() ?? card), ...(card.toObject?.() ?? card),
@ -114,6 +139,54 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
}); });
} }
const subclassSelections = context.advancementChoices.subclass?.flatMap(x => x.data) ?? [];
const multiclassSubclass = this.actor.system.multiclass?.system?.subclasses?.[0];
const possibleSubclasses = [
this.actor.system.subclass,
...(multiclassSubclass ? [multiclassSubclass] : [])
];
const selectedSubclasses = possibleSubclasses.filter(x => subclassSelections.includes(x.uuid));
context.subclassCards = [];
if (context.advancementChoices.subclass?.length > 0) {
for (var subclass of possibleSubclasses) {
const data = await foundry.utils.fromUuid(subclass.uuid);
const selected = selectedSubclasses.some(x => x.uuid === data.uuid);
context.subclassCards.push({
...data.toObject(),
uuid: data.uuid,
disabled:
!selected && subclassSelections.length === context.advancementChoices.subclass.length,
selected: selected
});
}
}
const multiclasses = Object.values(context.advancementChoices.multiclass ?? {});
if (multiclasses?.[0]) {
const data = multiclasses[0];
const path = `tiers.${data.tier}.levels.${data.level}.optionSelections.${data.optionKey}.${data.checkboxNr}.data`;
const multiclass =
data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : { path: path };
context.multiclass = {
...(multiclass.toObject?.() ?? multiclass),
domains:
multiclass?.system?.domains.map(key => {
const domain = domains[key];
const alreadySelected = this.actor.system.class.system.domains.includes(key);
return {
...domain,
selected: key === data.secondaryData,
disabled: (key !== data.secondaryData && data.secondaryData) || alreadySelected
};
}) ?? [],
compendium: 'classes',
limit: 1
};
}
break; break;
case 'summary': case 'summary':
const actorArmor = this.actor.system.armor; const actorArmor = this.actor.system.armor;
@ -169,9 +242,53 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
htmlElement htmlElement
.querySelectorAll('.selection-checkbox') .querySelectorAll('.selection-checkbox')
.forEach(element => element.addEventListener('change', this.selectionClick.bind(this))); .forEach(element => element.addEventListener('change', this.selectionClick.bind(this)));
const traitsTagify = htmlElement.querySelector('.levelup-trait-increases');
if (traitsTagify) {
tagifyElement(traitsTagify, abilities, this.tagifyUpdate('trait').bind(this));
}
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
if (experienceIncreaseTagify) {
tagifyElement(
experienceIncreaseTagify,
this.actor.system.experiences.reduce((acc, experience) => {
acc[experience.id] = { label: experience.description };
return acc;
}, {}),
this.tagifyUpdate('experience').bind(this)
);
}
this._dragDrop.forEach(d => d.bind(htmlElement)); this._dragDrop.forEach(d => d.bind(htmlElement));
} }
tagifyUpdate =
type =>
async (_, { option, removed }) => {
/* Needs to take Amount into account to allow multiple to be stored in the same option. Array structure? */
const updatePath = this.levelup.selectionData.reduce((acc, data) => {
if (data.optionKey === type && removed ? data.data.includes(option) : data.data.length < data.amount) {
return `tiers.${data.tier}.levels.${data.level}.optionSelections.${data.optionKey}.${data.checkboxNr}.data`;
}
return acc;
}, null);
if (!updatePath) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.noSelectionsLeft')
);
return;
}
const currentData = foundry.utils.getProperty(this.levelup, updatePath);
const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option];
await this.levelup.updateSource({ [updatePath]: updatedData });
this.render();
};
static async updateForm(event, _, formData) { static async updateForm(event, _, formData) {
const { levelup } = foundry.utils.expandObject(formData.object); const { levelup } = foundry.utils.expandObject(formData.object);
await this.levelup.updateSource(levelup); await this.levelup.updateSource(levelup);
@ -181,7 +298,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
async _onDrop(event) { async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.getDragEventData(event); const data = foundry.applications.ux.TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid); const item = await fromUuid(data.uuid);
if (event.currentTarget.parentElement?.classList?.contains('domain-cards')) { if (event.target.parentElement?.classList?.contains('domain-cards')) {
if (item.type === 'domainCard') { if (item.type === 'domainCard') {
if (!this.actor.system.class.system.domains.includes(item.system.domain)) { if (!this.actor.system.class.system.domains.includes(item.system.domain)) {
// Also needs to check for multiclass adding a new domain // Also needs to check for multiclass adding a new domain
@ -191,15 +308,25 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
return; return;
} }
if (item.system.level > Number(event.currentTarget.dataset.limit)) { if (item.system.level > Number(event.target.dataset.limit)) {
ui.notifications.error( ui.notifications.error(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel') game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel')
); );
return; return;
} }
const achievementCard = event.currentTarget.dataset.path.startsWith('domainCards'); await this.levelup.updateSource({ [event.target.dataset.path]: item.uuid });
await this.levelup.updateSource({ [event.currentTarget.dataset.path]: item.uuid }); this.render();
}
} else if (event.target.parentElement?.classList?.contains('multiclass-cards')) {
if (item.type === 'class') {
if (item.name === this.actor.system.class.name) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.alreadySelectedClass')
);
return;
}
await this.levelup.updateSource({ [event.target.dataset.path]: item.uuid });
this.render(); this.render();
} }
} }
@ -260,9 +387,27 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
this.render(); this.render();
} }
static async save() { static async viewCompendium(_, button) {
await this.actor.levelUp(this.levelup.selectionData); (await game.packs.get(`daggerheart.${button.dataset.compendium}`))?.render(true);
}
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; // Notification?
}
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 save() {
await this.actor.levelUp(this.levelup.levelupData);
this.close(); this.close();
} }
} }

View file

@ -362,7 +362,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
name: x.actor.name, name: x.actor.name,
img: x.actor.img, img: x.actor.img,
difficulty: x.actor.system.difficulty, difficulty: x.actor.system.difficulty,
evasion: x.actor.system.evasion evasion: x.actor.system.evasion.value
})); }));
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');

View file

@ -604,7 +604,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
name: x.actor.name, name: x.actor.name,
img: x.actor.img, img: x.actor.img,
difficulty: x.actor.system.difficulty, difficulty: x.actor.system.difficulty,
evasion: x.actor.system.evasion evasion: x.actor.system.evasion.value
})); }));
const systemData = { const systemData = {
@ -645,6 +645,11 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
} }
openLevelUp() { openLevelUp() {
if (!this.document.system.class || !this.document.system.subclass) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.missingClassOrSubclass'));
return;
}
new DhlevelUp(this.document).render(true); new DhlevelUp(this.document).render(true);
} }

View file

@ -12,35 +12,36 @@ export class DhLevelup extends foundry.abstract.DataModel {
totalLevelProgression.push(level); totalLevelProgression.push(level);
} }
const pcSelections = Object.values(pcLevelData.levelups).flatMap(x => x.selections);
const tiers = tierKeys.reduce((acc, key) => { const tiers = tierKeys.reduce((acc, key) => {
acc[key] = DhLevelupTier.initializeData( acc[key] = DhLevelupTier.initializeData(
levelTierData.tiers[key], levelTierData.tiers[key],
maxLevel, maxLevel,
pcLevelData.selections.filter(x => x.tier === Number(key)), pcSelections.filter(x => x.tier === Number(key)),
pcLevelData.level.changed pcLevelData.level.changed
); );
return acc; return acc;
}, {}); }, {});
const allInitialAchievements = Object.values(tiers).reduce( const allInitialAchievements = Object.values(tiers).reduce((acc, tier) => {
(acc, tier) => { const levelThreshold = Math.min(...tier.belongingLevels);
const levelThreshold = Math.min(...tier.belongingLevels);
if (totalLevelProgression.includes(levelThreshold)) { if (totalLevelProgression.includes(levelThreshold)) {
acc.proficiency += tier.initialAchievements.proficiency; acc[levelThreshold] = {
[...Array(tier.initialAchievements.experience.nr).keys()].forEach(_ => { newExperiences: {},
acc.newExperiences[foundry.utils.randomID()] = { proficiency: tier.initialAchievements.proficiency
name: '', };
modifier: tier.initialAchievements.experience.modifier [...Array(tier.initialAchievements.experience.nr).keys()].forEach(_ => {
}; acc[levelThreshold].newExperiences[foundry.utils.randomID()] = {
}); name: '',
} modifier: tier.initialAchievements.experience.modifier
};
});
}
return acc; return acc;
}, }, {});
{ newExperiences: {}, proficiency: 0 }
);
const domainCards = Object.keys(tiers).reduce((acc, tierKey) => { const domainCards = Object.keys(tiers).reduce((acc, tierKey) => {
const tier = tiers[tierKey]; const tier = tiers[tierKey];
@ -73,11 +74,9 @@ export class DhLevelup extends foundry.abstract.DataModel {
return acc; return acc;
}, 0), }, 0),
allInitialAchievements: { allInitialAchievements: allInitialAchievements,
newExperiences: allInitialAchievements.newExperiences, domainCards: domainCards,
proficiency: allInitialAchievements.proficiency progressionLevels: totalLevelProgression
},
domainCards: domainCards
}; };
} }
@ -87,15 +86,17 @@ export class DhLevelup extends foundry.abstract.DataModel {
return { return {
tiers: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupTier)), tiers: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupTier)),
maxSelections: new fields.NumberField({ required: true, integer: true }), maxSelections: new fields.NumberField({ required: true, integer: true }),
allInitialAchievements: new fields.SchemaField({ allInitialAchievements: new fields.TypedObjectField(
newExperiences: new fields.TypedObjectField( new fields.SchemaField({
new fields.SchemaField({ newExperiences: new fields.TypedObjectField(
name: new fields.StringField({ required: true }), new fields.SchemaField({
modifier: new fields.NumberField({ required: true, integer: true }) name: new fields.StringField({ required: true }),
}) modifier: new fields.NumberField({ required: true, integer: true })
), })
proficiency: new fields.NumberField({ required: true, integer: true }) ),
}), proficiency: new fields.NumberField({ required: true, integer: true })
})
),
domainCards: new fields.TypedObjectField( domainCards: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
uuid: new fields.StringField({ required: true, nullable: true, initial: null }), uuid: new fields.StringField({ required: true, nullable: true, initial: null }),
@ -104,13 +105,44 @@ export class DhLevelup extends foundry.abstract.DataModel {
domainCardSlot: new fields.NumberField({ required: true, integer: true }), domainCardSlot: new fields.NumberField({ required: true, integer: true }),
path: new fields.StringField({ required: true }) path: new fields.StringField({ required: true })
}) })
) ),
// advancementSelections: new fields.SchemaField({ progressionLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }))
// experiences: new fields.SetField(new fields.StringField()),
// }),
}; };
} }
get canLevelUp() {
if (this.levelSelections.total !== this.maxSelections) return false;
const achievementsDone =
Object.values(this.allInitialAchievements).every(achievement =>
Object.values(achievement.newExperiences).every(experience => experience.name)
) && Object.values(this.domainCards).every(card => card.uuid);
const selectionData = this.selectionData;
let advancementsDone = true;
for (var advancement of selectionData) {
switch (advancement.type) {
case 'trait':
case 'experience':
case 'domainCard':
case 'subclass':
advancementsDone = advancement.amount
? advancement.data.length === advancement.amount
: advancement.data.length === 1;
break;
case 'multiclass':
const classSelected = advancement.data.length === 1;
const domainSelected = advancement.secondaryData;
advancementsDone = classSelected && domainSelected;
break;
}
if (!advancementsDone) break;
}
return achievementsDone && advancementsDone;
}
get levelSelections() { get levelSelections() {
return Object.values(this.tiers).reduce( return Object.values(this.tiers).reduce(
(acc, tier) => { (acc, tier) => {
@ -148,13 +180,35 @@ export class DhLevelup extends foundry.abstract.DataModel {
checkboxNr: Number(checkboxNr), checkboxNr: Number(checkboxNr),
value: optionSelect.value, value: optionSelect.value,
amount: optionSelect.amount, amount: optionSelect.amount,
data: selectionObj.data data: selectionObj.data,
secondaryData: selectionObj.secondaryData
}; };
}); });
}); });
}); });
}); });
} }
get levelupData() {
const leveledSelections = this.selectionData.reduce((acc, data) => {
if (!acc[data.level]) acc[data.level] = [data];
else acc[data.level].push(data);
return acc;
}, {});
return this.progressionLevels.reduce((acc, level) => {
acc[level] = {
achievements: {
experiences: this.allInitialAchievements[level].newExperiences,
proficiency: this.allInitialAchievements[level].proficiency
},
domainCards: Object.values(this.domainCards).map(card => ({ ...card })),
selections: leveledSelections[level]
};
return acc;
}, {});
}
} }
class DhLevelupTier extends foundry.abstract.DataModel { class DhLevelupTier extends foundry.abstract.DataModel {
@ -295,6 +349,7 @@ class DhLevelupLevel extends foundry.abstract.DataModel {
if (!acc[data.optionKey]) acc[data.optionKey] = {}; if (!acc[data.optionKey]) acc[data.optionKey] = {};
acc[data.optionKey][data.checkboxNr] = { acc[data.optionKey][data.checkboxNr] = {
minCost: optionSelections[data.optionKey].minCost, minCost: optionSelections[data.optionKey].minCost,
minCost: optionSelections[data.optionKey].amount,
locked: locked locked: locked
}; };
@ -313,8 +368,10 @@ class DhLevelupLevel extends foundry.abstract.DataModel {
new fields.SchemaField({ new fields.SchemaField({
selected: new fields.BooleanField({ required: true, initial: true }), selected: new fields.BooleanField({ required: true, initial: true }),
minCost: new fields.NumberField({ required: true, integer: true }), minCost: new fields.NumberField({ required: true, integer: true }),
amount: new fields.NumberField({ integer: true }),
locked: new fields.BooleanField({ required: true, initial: false }), locked: new fields.BooleanField({ required: true, initial: false }),
data: new fields.StringField() data: new fields.ArrayField(new fields.StringField()),
secondaryData: new fields.StringField()
}) })
) )
) )

View file

@ -50,7 +50,9 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
knowledge: attributeField() knowledge: attributeField()
}), }),
proficiency: new fields.NumberField({ required: true, initial: 1, integer: true }), proficiency: new fields.NumberField({ required: true, initial: 1, integer: true }),
evasion: new fields.NumberField({ initial: 0, integer: true }), evasion: new fields.SchemaField({
bonuses: new fields.NumberField({ initial: 0, integer: true })
}),
experiences: new fields.ArrayField( experiences: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
id: new fields.StringField({ required: true }), id: new fields.StringField({ required: true }),
@ -338,7 +340,7 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
// : attribute.data.actualValue; // : attribute.data.actualValue;
} }
this.evasion = this.class?.system?.evasion ?? 0; this.evasion.value = (this.class?.system?.evasion ?? 0) + this.evasion.bonuses;
// this.armor.value = this.activeArmor?.baseScore ?? 0; // this.armor.value = this.activeArmor?.baseScore ?? 0;
const armor = this.armor; const armor = this.armor;
this.damageThresholds = { this.damageThresholds = {
@ -429,15 +431,37 @@ class DhPCLevelData extends foundry.abstract.DataModel {
current: new fields.NumberField({ required: true, integer: true, initial: 1 }), current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
changed: new fields.NumberField({ required: true, integer: true, initial: 1 }) changed: new fields.NumberField({ required: true, integer: true, initial: 1 })
}), }),
selections: new fields.ArrayField( levelups: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
tier: new fields.NumberField({ required: true, integer: true }), achievements: new fields.SchemaField(
level: new fields.NumberField({ required: true, integer: true }), {
optionKey: new fields.StringField({ required: true }), experiences: new fields.TypedObjectField(
type: new fields.StringField({ required: true, choices: LevelOptionType }), new fields.SchemaField({
checkboxNr: new fields.NumberField({ required: true, integer: true }), name: new fields.StringField({ required: true }),
value: new fields.NumberField({ integer: true }), modifier: new fields.NumberField({ required: true, integer: true })
amount: new fields.NumberField({ integer: true }) })
),
proficiency: new fields.NumberField({ integer: true })
},
{ nullable: true, initial: null }
),
domainCards: new fields.ArrayField(
new fields.SchemaField({
uuid: new fields.StringField({ required: true })
})
),
selections: new fields.ArrayField(
new fields.SchemaField({
tier: new fields.NumberField({ required: true, integer: true }),
level: new fields.NumberField({ required: true, integer: true }),
optionKey: new fields.StringField({ required: true }),
type: new fields.StringField({ required: true, choices: LevelOptionType }),
checkboxNr: new fields.NumberField({ required: true, integer: true }),
value: new fields.NumberField({ integer: true }),
amount: new fields.NumberField({ integer: true }),
data: new fields.ArrayField(new fields.StringField({ required: true }))
})
)
}) })
) )
}; };

View file

@ -27,29 +27,178 @@ export default class DhpActor extends Actor {
if (newLevel > this.system.levelData.level.current) { if (newLevel > this.system.levelData.level.current) {
await this.update({ 'system.levelData.level.changed': newLevel }); await this.update({ 'system.levelData.level.changed': newLevel });
} else { } else {
const changes = this.getLevelChangedFeatures(
newLevel,
this.system.levelData.level.changed,
this.system.levelData.levelups
);
for (var domainCard of changes.domainCards) {
const uuid = domainCard.uuid ? domainCard.uuid : domainCard.data;
const itemCard = await this.items.find(x => x.uuid === uuid);
itemCard.delete();
}
var traitsUpdate = changes.traits.reduce((acc, trait) => {
acc[`${trait}.data.value`] = this.system.traits[trait].data.value - 1;
return acc;
}, {});
const newExperienceKeys = Object.keys(changes.experiences);
const experienceUpdate = this.system.experiences.filter(x => !newExperienceKeys.includes(x.id));
for (var experience of changes.experienceIncreases) {
for (var id of experience.data) {
const existingExperience = experienceUpdate.find(x => x.id === id);
existingExperience.value -= experience.value;
}
}
const newLevelData = { const newLevelData = {
level: { level: {
current: newLevel, current: newLevel,
changed: newLevel changed: newLevel
}, },
selections: Object.keys(this.system.levelData.selections).reduce((acc, key) => { levelups: Object.keys(this.system.levelData.levelups).reduce((acc, levelKey) => {
const level = this.system.levelData.selections[key]; const level = Number(levelKey);
if (level.level <= newLevel) { if (level > newLevel) acc[`-=${level}`] = null;
acc[key] = level;
}
return acc; return acc;
}, {}) }, {})
}; };
await this.update({ 'system.levelData': newLevelData }); await this.update({
system: {
'traits': traitsUpdate,
'experiences': experienceUpdate,
'resources': {
health: {
max: this.system.resources.health.max - changes.hitPoint
},
stress: {
max: this.system.resources.stress.max - changes.stress
}
},
'evasion.bonuses': this.system.evasion.bonuses - changes.evasion,
'proficiency': this.system.proficiency - changes.proficiency,
'levelData': newLevelData
}
});
} }
} }
getLevelChangedFeatures(startLevel, endLevel, levelData) {
const changedFeatures = {
hitPoint: 0,
stress: 0,
evasion: 0,
proficiency: 0,
domainCards: [],
multiclass: null,
subclasses: [],
traits: [],
experiences: [],
experienceIncreases: []
};
for (var level = startLevel + 1; level <= endLevel; level++) {
const achievements = levelData[level].achievements;
const selections = levelData[level].selections.reduce((acc, selection) => {
if (!acc[selection.type]) acc[selection.type] = [selection];
else acc[selection.type].push(selection);
return acc;
}, {});
changedFeatures.hitPoint += selections.hitPoint
? selections.hitPoint.reduce((acc, hp) => acc + Number(hp.value), 0)
: 0;
changedFeatures.stress += selections.stress
? selections.stress.reduce((acc, stress) => acc + Number(stress.value), 0)
: 0;
changedFeatures.evasion += selections.evasion
? selections.evasion.reduce((acc, evasion) => acc + Number(evasion.value), 0)
: 0;
changedFeatures.proficiency +=
(achievements?.proficiency ?? 0) +
(selections.evasion ? selections.evasion.reduce((acc, evasion) => acc + Number(evasion.value), 0) : 0);
changedFeatures.domainCards = [
...levelData[level].domainCards,
...(selections.domainCard.flatMap(x => x.data.map(data => ({ ...x, data: data }))) ?? [])
];
changedFeatures.traits = selections.trait ? selections.trait.flatMap(x => x.data) : [];
changedFeatures.experiences = achievements?.experiences ? achievements.experiences : {};
changedFeatures.experienceIncreases = selections.experience ?? [];
changedFeatures.subclasses = selections.subclasses ? [] : [];
changedFeatures.multiclass = selections.multiclass ? [] : [];
}
return changedFeatures;
}
async levelUp(levelupData) { async levelUp(levelupData) {
await this.actor.update({ const changes = this.getLevelChangedFeatures(
'system.levelData': { this.system.levelData.level.current,
'level.current': this.system.levelData.level.changed, this.system.levelData.level.changed,
'selections': levelupData levelupData
);
for (var card of changes.domainCards) {
const fromAchievement = Boolean(card.uuid);
const domainCard = await foundry.utils.fromUuid(fromAchievement ? card.uuid : card.data);
const newCard = (await this.createEmbeddedDocuments('Item', [domainCard]))[0];
if (fromAchievement) {
const levelupCard = levelupData[card.level].domainCards.find(
x => x.tier === card.tier && x.level === card.level
);
if (levelupCard) levelupCard.uuid = newCard.uuid;
} else {
const levelupCard = levelupData[card.level].selections.find(
x =>
x.tier === card.tier &&
x.level === card.level &&
x.optionKey === card.optionKey &&
x.checkboxNr === card.checkboxNr
);
if (levelupCard) levelupCard.data.findSplice(x => x === card.data, newCard.uuid);
}
}
var traitsUpdate = changes.traits.reduce((acc, trait) => {
acc[`${trait}.data.value`] = this.system.traits[trait].data.value + 1;
return acc;
}, {});
const experienceUpdate = this.system.experiences;
const newExperienceKeys = Object.keys(changes.experiences);
for (var key of newExperienceKeys) {
const experience = changes.experiences[key];
experienceUpdate.push({ id: key, description: experience.name, value: experience.modifier });
}
for (var experience of changes.experienceIncreases) {
for (var id of experience.data) {
const existingExperience = experienceUpdate.find(x => x.id === id);
existingExperience.value += experience.value;
}
}
await this.update({
system: {
'traits': traitsUpdate,
'experiences': experienceUpdate,
'resources': {
health: {
max: this.system.resources.health.max + changes.hitPoint
},
stress: {
max: this.system.resources.stress.max + changes.stress
}
},
'evasion.bonuses': this.system.evasion.bonuses + changes.evasion,
'proficiency': this.system.proficiency + changes.proficiency,
'levelData': {
'level.current': this.system.levelData.level.changed,
'levelups': levelupData
}
} }
}); });
} }

View file

@ -1,4 +1,5 @@
import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
import Tagify from '@yaireo/tagify';
export const loadCompendiumOptions = async compendiums => { export const loadCompendiumOptions = async compendiums => {
const compendiumValues = []; const compendiumValues = [];
@ -145,3 +146,62 @@ export const chunkify = (array, chunkSize, mappingFunc) => {
return chunkifiedArray; return chunkifiedArray;
}; };
export const tagifyElement = (element, options, onChange, tagifyOptions = {}) => {
const { maxTags } = tagifyOptions;
const tagifyElement = new Tagify(element, {
tagTextProp: 'name',
enforceWhitelist: true,
whitelist: Object.keys(options).map(key => {
const option = options[key];
return {
value: key,
name: game.i18n.localize(option.label),
src: option.src
};
}),
maxTags: maxTags,
// callbacks: { invalid: this.onAddTag },
dropdown: {
mapValueTo: 'name',
searchKeys: ['name'],
enabled: 0,
maxItems: 20,
closeOnSelect: true,
highlightFirst: false
},
templates: {
tag(tagData) {
return `<tag title="${tagData.title || tagData.value}"
contenteditable='false'
spellcheck='false'
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
${this.getAttributes(tagData)}>
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
<div>
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
${tagData.src ? `<img src="${tagData.src}"></i>` : ''}
</div>
</tag>`;
}
}
});
const onSelect = async event => {
const inputElement = event.detail.tagify.DOM.originalInput;
const selectedOptions = event.detail?.value ? JSON.parse(event.detail.value) : [];
const unusedDropDownItems = event.detail.tagify.suggestedListItems;
const missingOptions = Object.keys(options).filter(x => !unusedDropDownItems.find(item => item.value === x));
const removedItem = missingOptions.find(x => !selectedOptions.find(item => item.value === x));
const addedItem = removedItem
? null
: selectedOptions.find(x => !missingOptions.find(item => item === x.value));
const changedItem = { option: removedItem ?? addedItem.value, removed: Boolean(removedItem) };
onChange(selectedOptions, changedItem, inputElement);
};
tagifyElement.on('change', onSelect);
};

View file

@ -2714,8 +2714,12 @@ div.daggerheart.views.multiclass {
.item-button .item-icon.checked { .item-button .item-icon.checked {
opacity: 1; opacity: 1;
} }
.theme-light { .theme-light .daggerheart.levelup .tiers-container .tier-container {
/* Add specifics*/ background-image: url('../assets/parchments/dh-parchment-light.png');
}
.daggerheart.levelup .window-content {
max-height: 960px;
overflow: auto;
} }
.daggerheart.levelup div[data-application-part='form'] { .daggerheart.levelup div[data-application-part='form'] {
display: flex; display: flex;
@ -2805,10 +2809,40 @@ div.daggerheart.views.multiclass {
font-size: 12px; font-size: 12px;
} }
.daggerheart.levelup .levelup-selections-container .levelup-card-selection { .daggerheart.levelup .levelup-selections-container .levelup-card-selection {
display: grid; display: flex;
grid-template-columns: 1fr 1fr 1fr 1fr; flex-wrap: wrap;
gap: 40px; gap: 40px;
padding-right: 120px; }
.daggerheart.levelup .levelup-selections-container .levelup-card-selection .card-preview-container {
width: calc(100% * (1 / 5));
}
.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container {
display: flex;
flex-direction: column;
gap: 8px;
}
.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
position: relative;
cursor: pointer;
}
.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container.disabled {
pointer-events: none;
opacity: 0.4;
}
.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container div {
position: absolute;
text-align: center;
top: 4px;
background: grey;
padding: 0 12px;
border-radius: 6px;
}
.daggerheart.levelup .levelup-selections-container .levelup-card-selection .levelup-domains-selection-container .levelup-domain-selection-container img {
height: 124px;
} }
.daggerheart.levelup .levelup-summary-container .level-achievements-container { .daggerheart.levelup .levelup-summary-container .level-achievements-container {
display: flex; display: flex;
@ -3250,13 +3284,18 @@ div.daggerheart.views.multiclass {
.system-daggerheart.theme-light .tagify__dropdown .tagify__dropdown__item--active { .system-daggerheart.theme-light .tagify__dropdown .tagify__dropdown__item--active {
color: #efe6d8; color: #efe6d8;
} }
.theme-light .application .component.dh-style.card.card-preview-container { .theme-light .application .component.dh-style.card-preview-container {
background-image: url('../assets/parchments/dh-parchment-light.png'); background-image: url('../assets/parchments/dh-parchment-light.png');
} }
.theme-light .application .component.dh-style.card.card-preview-container .preview-text-container { .theme-light .application .component.dh-style.card-preview-container .preview-text-container {
background-image: url(../assets/parchments/dh-parchment-dark.png); background-image: url(../assets/parchments/dh-parchment-dark.png);
} }
.theme-light .application .component.dh-style.card-preview-container .preview-selected-icon-container {
background-image: url(../assets/parchments/dh-parchment-dark.png);
color: var(--color-light-5);
}
.application .component.dh-style.card-preview-container { .application .component.dh-style.card-preview-container {
position: relative;
border-radius: 6px; border-radius: 6px;
border: 2px solid var(--color-tabs-border); border: 2px solid var(--color-tabs-border);
display: flex; display: flex;
@ -3264,12 +3303,22 @@ div.daggerheart.views.multiclass {
aspect-ratio: 0.75; aspect-ratio: 0.75;
background-image: url('../assets/parchments/dh-parchment-dark.png'); background-image: url('../assets/parchments/dh-parchment-dark.png');
} }
.application .component.dh-style.card-preview-container.empty { .application .component.dh-style.card-preview-container.selectable {
cursor: pointer; cursor: pointer;
} }
.application .component.dh-style.card-preview-container.disabled {
pointer-events: none;
opacity: 0.4;
}
.application .component.dh-style.card-preview-container .preview-image-outer-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.application .component.dh-style.card-preview-container .preview-image-container { .application .component.dh-style.card-preview-container .preview-image-container {
flex: 1; flex: 1;
border-radius: 4px 4px 0; border-radius: 4px 4px 0 0;
} }
.application .component.dh-style.card-preview-container .preview-text-container { .application .component.dh-style.card-preview-container .preview-text-container {
flex: 1; flex: 1;
@ -3283,13 +3332,14 @@ div.daggerheart.views.multiclass {
background-image: url(../assets/parchments/dh-parchment-light.png); background-image: url(../assets/parchments/dh-parchment-light.png);
} }
.application .component.dh-style.card-preview-container .preview-empty-container { .application .component.dh-style.card-preview-container .preview-empty-container {
pointer-events: none;
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex: 1; flex: 1;
} }
.application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container { .application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container {
position: relative;
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -3299,11 +3349,28 @@ div.daggerheart.views.multiclass {
} }
.application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container .preview-empty-subtext { .application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container .preview-empty-subtext {
position: absolute; position: absolute;
bottom: -48px; top: 10%;
font-size: 18px; font-size: 18px;
font-variant: small-caps; font-variant: small-caps;
text-align: center; text-align: center;
} }
.application .component.dh-style.card-preview-container .preview-selected-icon-container {
position: absolute;
height: 54px;
width: 54px;
border-radius: 50%;
border: 2px solid;
font-size: 48px;
display: flex;
align-items: center;
justify-content: center;
background-image: url(../assets/parchments/dh-parchment-light.png);
color: var(--color-dark-5);
}
.application .component.dh-style.card-preview-container .preview-selected-icon-container i {
position: relative;
right: 2px;
}
.sheet.daggerheart.dh-style .tab-navigation { .sheet.daggerheart.dh-style .tab-navigation {
margin: 5px 0; margin: 5px 0;
height: 40px; height: 40px;

View file

@ -275,13 +275,18 @@
.theme-light { .theme-light {
.application { .application {
.component.dh-style.card { .component.dh-style {
&.card-preview-container { &.card-preview-container {
background-image: url('../assets/parchments/dh-parchment-light.png'); background-image: url('../assets/parchments/dh-parchment-light.png');
.preview-text-container { .preview-text-container {
background-image: url(../assets/parchments/dh-parchment-dark.png); background-image: url(../assets/parchments/dh-parchment-dark.png);
} }
.preview-selected-icon-container {
background-image: url(../assets/parchments/dh-parchment-dark.png);
color: var(--color-light-5);
}
} }
} }
} }
@ -290,6 +295,7 @@
.application { .application {
.component.dh-style { .component.dh-style {
&.card-preview-container { &.card-preview-container {
position: relative;
border-radius: 6px; border-radius: 6px;
border: 2px solid var(--color-tabs-border); border: 2px solid var(--color-tabs-border);
display: flex; display: flex;
@ -297,13 +303,25 @@
aspect-ratio: 0.75; aspect-ratio: 0.75;
background-image: url('../assets/parchments/dh-parchment-dark.png'); background-image: url('../assets/parchments/dh-parchment-dark.png');
&.empty { &.selectable {
cursor: pointer; cursor: pointer;
} }
&.disabled {
pointer-events: none;
opacity: 0.4;
}
.preview-image-outer-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.preview-image-container { .preview-image-container {
flex: 1; flex: 1;
border-radius: 4px 4px 0; border-radius: 4px 4px 0 0;
} }
.preview-text-container { .preview-text-container {
@ -319,13 +337,14 @@
} }
.preview-empty-container { .preview-empty-container {
pointer-events: none;
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex: 1; flex: 1;
.preview-empty-inner-container { .preview-empty-inner-container {
position: relative;
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -336,13 +355,32 @@
.preview-empty-subtext { .preview-empty-subtext {
position: absolute; position: absolute;
bottom: -48px; top: 10%;
font-size: 18px; font-size: 18px;
font-variant: small-caps; font-variant: small-caps;
text-align: center; text-align: center;
} }
} }
} }
.preview-selected-icon-container {
position: absolute;
height: 54px;
width: 54px;
border-radius: 50%;
border: 2px solid;
font-size: 48px;
display: flex;
align-items: center;
justify-content: center;
background-image: url(../assets/parchments/dh-parchment-light.png);
color: var(--color-dark-5);
i {
position: relative;
right: 2px;
}
}
} }
} }
} }

View file

@ -1,8 +1,19 @@
.theme-light { .theme-light {
/* Add specifics*/ .daggerheart.levelup {
.tiers-container {
.tier-container {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
}
}
} }
.daggerheart.levelup { .daggerheart.levelup {
.window-content {
max-height: 960px;
overflow: auto;
}
div[data-application-part='form'] { div[data-application-part='form'] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -110,10 +121,46 @@
} }
.levelup-card-selection { .levelup-card-selection {
display: grid; display: flex;
grid-template-columns: 1fr 1fr 1fr 1fr; flex-wrap: wrap;
gap: 40px; gap: 40px;
padding-right: 120px;
.card-preview-container {
width: calc(100% * (1 / 5));
}
.levelup-domains-selection-container {
display: flex;
flex-direction: column;
gap: 8px;
.levelup-domain-selection-container {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
position: relative;
cursor: pointer;
&.disabled {
pointer-events: none;
opacity: 0.4;
}
div {
position: absolute;
text-align: center;
top: 4px;
background: grey;
padding: 0 12px;
border-radius: 6px;
}
img {
height: 124px; // Can it be dynamically sized? Won't follow any window resizing like this.
}
}
}
} }
} }

View file

@ -1,4 +1,4 @@
<div class="component dh-style card-preview-container" data-path="{{this.path}}" data-limit="{{this.limit}}"> <div class="component dh-style card-preview-container {{#if (and this.compendium (not this.img))}}selectable{{/if}}" data-action="viewCompendium" data-compendium="{{this.compendium}}" data-path="{{this.path}}" data-limit="{{this.limit}}">
{{#if this.img}} {{#if this.img}}
<img class="preview-image-container" src="{{this.img}}" /> <img class="preview-image-container" src="{{this.img}}" />
<div class="preview-text-container">{{this.name}}</div> <div class="preview-text-container">{{this.name}}</div>

View file

@ -4,7 +4,7 @@
<div class="defense-row"> <div class="defense-row">
<div class="defense-section"> <div class="defense-section">
<div class="defense-container"> <div class="defense-container">
<div class="defense-value">{{document.system.evasion}}</div> <div class="defense-value">{{document.system.evasion.value}}</div>
<img src="systems/daggerheart/assets/AttributeShield.svg" /> <img src="systems/daggerheart/assets/AttributeShield.svg" />
<div class="defense-banner">{{localize "DAGGERHEART.Sheets.PC.Defense.Evasion"}}</div> <div class="defense-banner">{{localize "DAGGERHEART.Sheets.PC.Defense.Evasion"}}</div>
</div> </div>

View file

@ -1,29 +0,0 @@
{{#switch}}
{{#case 'trait'}}
{{/case}}
{{/switch}}
trait: {
id: 'trait',
label: 'Character Trait',
dataPath: ''
},
experience: {
id: 'experience',
label: 'Experience'
},
domainCard: {
id: 'domainCard',
label: 'Domain Card'
},
subclass: {
id: 'subclass',
label: 'Subclass'
},
multiclass: {
id: 'multiclass',
label: 'Multiclass'
}

View file

@ -0,0 +1,11 @@
<div class="component dh-style card-preview-container selectable {{#if this.disabled}}disabled{{/if}}" data-action="selectPreview" data-path="{{this.path}}" data-uuid="{{this.uuid}}" {{#if selected}}data-selected="true"{{/if}}>
<div class="preview-image-outer-container">
<img class="preview-image-container" src="{{this.img}}" />
{{#if this.selected}}
<div class="preview-selected-icon-container">
<i class="fa-solid fa-check"></i>
</div>
{{/if}}
</div>
<div class="preview-text-container">{{this.name}}</div>
</div>

View file

@ -33,5 +33,15 @@
</fieldset> </fieldset>
{{/each}} {{/each}}
</div> </div>
<footer class="levelup-footer">
<div class="advancement-information-container">
{{localize "DAGGERHEART.Application.LevelUp.notifications.info.remainingAdvancementInfo" choices=this.levelup.levelSelections.totalAvailable}}
{{#each this.levelup.tiers as |tier key|}}
<div class="advancement-tier-stats">Tier {{tier.tier}}: {{tier.selections.totalAvailable}}</div>
{{/each}}
<i class="advancement-tier-info fa-solid fa-circle-question" data-tooltip="{{localize "DAGGERHEART.Application.LevelUp.notifications.info.tierAdvancementInfo"}}"></i>
</div>
</footer>
</div> </div>
</section> </section>

View file

@ -4,31 +4,81 @@
data-group='{{tabs.selections.group}}' data-group='{{tabs.selections.group}}'
> >
<div class="section-container levelup-selections-container"> <div class="section-container levelup-selections-container">
<div> {{#if (gt this.newExperiences.length 0)}}
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.newExperiences"}}</h3> <div>
<div class="achievement-experience-cards"> <h3>{{localize "DAGGERHEART.Application.LevelUp.summary.newExperiences"}}</h3>
{{#each this.newExperiences as |experience key|}} <div class="achievement-experience-cards">
<div class="achievement-experience-card"> {{#each this.newExperiences}}
<div class="flexrow"> <div class="achievement-experience-card">
<input type="text" name="{{concat "levelup.allInitialAchievements.newExperiences." key ".name"}}" value="{{experience.name}}" placeholder="{{localize "DAGGERHEART.Application.LevelUp.summary.experiencePlaceholder"}}" /> <div class="flexrow">
<div class="flex0">{{signedNumber experience.modifier}}</div> <input type="text" name="{{concat "levelup.allInitialAchievements." this.level ".newExperiences." this.key ".name"}}" value="{{this.name}}" placeholder="{{localize "DAGGERHEART.Application.LevelUp.summary.experiencePlaceholder"}}" />
</div> <div class="flex0">{{signedNumber this.modifier}}</div>
<div class="achievement-experience-marker"> </div>
{{#if experience.name}}<i class="fa-solid fa-check"></i>{{/if}} <div class="achievement-experience-marker">
{{#if this.name}}<i class="fa-solid fa-check"></i>{{/if}}
</div>
</div> </div>
{{/each}}
</div>
</div>
{{/if}}
{{#if this.traits.active}}
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.traits"}}</h3>
<input type="text" class="levelup-trait-increases" value="{{this.traits.values}}" />
</div>
{{/if}}
{{#if this.experienceIncreases.active}}
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.experienceIncreases"}}</h3>
<input type="text" class="levelup-experience-increases" value="{{this.experienceIncreases.values}}" />
</div>
{{/if}}
{{#if (gt this.domainCards.length 0)}}
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.domainCards"}}</h3>
<div class="levelup-card-selection domain-cards">
{{#each this.domainCards}}
{{> "systems/daggerheart/templates/components/card-preview.hbs" img=this.img name=this.name path=this.path }}
{{/each}}
</div>
</div>
{{/if}}
{{#if (gt this.subclassCards.length 0)}}
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.subclass"}}</h3>
<div class="levelup-card-selection subclass-cards">
{{#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 }}
{{/each}}
</div>
</div>
{{/if}}
{{#if this.multiclass}}
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.multiclass"}}</h3>
<div class="levelup-card-selection multiclass-cards">
{{> "systems/daggerheart/templates/components/card-preview.hbs" img=this.multiclass.img name=this.multiclass.name path=this.multiclass.path compendium=this.multiclass.compendium }}
<div class="levelup-domains-selection-container">
{{#each this.multiclass.domains}}
<div class="levelup-domain-selection-container {{#if this.disabled}}disabled{{/if}}">
<div>{{localize this.label}}</div>
<img src="{{this.src}}" />
</div>
{{/each}}
</div> </div>
{{/each}} </div>
</div> </div>
</div> {{/if}}
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.domainCards"}}</h3>
<div class="levelup-card-selection domain-cards">
{{#each this.domainCards}}
{{> "systems/daggerheart/templates/components/card-preview.hbs" img=this.img name=this.name path=this.path }}
{{/each}}
</div>
</div>
</div> </div>
</section> </section>

View file

@ -46,15 +46,7 @@
</fieldset> </fieldset>
<footer class="levelup-footer"> <footer class="levelup-footer">
<button data-action="save">{{localize "Finish Levelup"}}</button> <button data-action="save" {{#if (not this.levelup.canLevelUp)}}disabled{{/if}}>{{localize "Finish Levelup"}}</button>
<div class="advancement-information-container">
{{localize "DAGGERHEART.Application.LevelUp.notifications.info.remainingAdvancementInfo" choices=this.levelup.levelSelections.totalAvailable}}
{{#each this.levelup.tiers as |tier key|}}
<div class="advancement-tier-stats">Tier {{tier.tier}}: {{tier.selections.totalAvailable}}</div>
{{/each}}
<i class="advancement-tier-info fa-solid fa-circle-question" data-tooltip="{{localize "DAGGERHEART.Application.LevelUp.notifications.info.tierAdvancementInfo"}}"></i>
</div>
</footer> </footer>
</div> </div>
</section> </section>