diff --git a/daggerheart.mjs b/daggerheart.mjs
index 2399ccdf..9885b691 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -285,6 +285,7 @@ const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/sheets/pc/parts/advancementCard.hbs',
'systems/daggerheart/templates/views/parts/level.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'
]);
};
diff --git a/lang/en.json b/lang/en.json
index a18c02dd..0c7c0649 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -770,7 +770,11 @@
"damageThresholdSevereIncrease": "Severe: {threshold}",
"newExperiences": "New Experiences",
"experiencePlaceholder": "A new experience..",
- "domainCards": "Domain Cards"
+ "domainCards": "Domain Cards",
+ "subclass": "Subclass",
+ "multiclass": "Multiclass",
+ "traits": "Increased Traits",
+ "experienceIncreases": "Experience Increases"
},
"notifications": {
"info": {
@@ -781,7 +785,9 @@
},
"error": {
"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",
"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": {
"Description": "Description",
diff --git a/module/applications/levelup.mjs b/module/applications/levelup.mjs
index 2aec01a6..06780fcb 100644
--- a/module/applications/levelup.mjs
+++ b/module/applications/levelup.mjs
@@ -1,4 +1,7 @@
+import { abilities } from '../config/actorConfig.mjs';
+import { domains } from '../config/domainConfig.mjs';
import { DhLevelup } from '../data/levelup.mjs';
+import { tagifyElement } from '../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@@ -27,7 +30,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
resizable: true
},
actions: {
- save: this.save
+ save: this.save,
+ viewCompendium: this.viewCompendium,
+ selectPreview: this.selectPreview
},
form: {
handler: this.updateForm,
@@ -93,7 +98,27 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
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 = {
...context.advancementChoices.domainCard,
...this.levelup.domainCards
@@ -102,7 +127,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.domainCards = [];
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 };
context.domainCards.push({
...(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;
case 'summary':
const actorArmor = this.actor.system.armor;
@@ -169,9 +242,53 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
htmlElement
.querySelectorAll('.selection-checkbox')
.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));
}
+ 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) {
const { levelup } = foundry.utils.expandObject(formData.object);
await this.levelup.updateSource(levelup);
@@ -181,7 +298,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
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 (!this.actor.system.class.system.domains.includes(item.system.domain)) {
// Also needs to check for multiclass adding a new domain
@@ -191,15 +308,25 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
return;
}
- if (item.system.level > Number(event.currentTarget.dataset.limit)) {
+ if (item.system.level > Number(event.target.dataset.limit)) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel')
);
return;
}
- const achievementCard = event.currentTarget.dataset.path.startsWith('domainCards');
- await this.levelup.updateSource({ [event.currentTarget.dataset.path]: item.uuid });
+ await this.levelup.updateSource({ [event.target.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();
}
}
@@ -260,9 +387,27 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
this.render();
}
- static async save() {
- await this.actor.levelUp(this.levelup.selectionData);
+ static async viewCompendium(_, button) {
+ (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();
}
}
diff --git a/module/applications/sheets/adversary.mjs b/module/applications/sheets/adversary.mjs
index 2087ea0a..b9180bf3 100644
--- a/module/applications/sheets/adversary.mjs
+++ b/module/applications/sheets/adversary.mjs
@@ -362,7 +362,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
name: x.actor.name,
img: x.actor.img,
difficulty: x.actor.system.difficulty,
- evasion: x.actor.system.evasion
+ evasion: x.actor.system.evasion.value
}));
const cls = getDocumentClass('ChatMessage');
diff --git a/module/applications/sheets/pc.mjs b/module/applications/sheets/pc.mjs
index 2d16b470..31c98b6e 100644
--- a/module/applications/sheets/pc.mjs
+++ b/module/applications/sheets/pc.mjs
@@ -604,7 +604,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
name: x.actor.name,
img: x.actor.img,
difficulty: x.actor.system.difficulty,
- evasion: x.actor.system.evasion
+ evasion: x.actor.system.evasion.value
}));
const systemData = {
@@ -645,6 +645,11 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
}
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);
}
diff --git a/module/data/levelup.mjs b/module/data/levelup.mjs
index ff2f8295..b7d8f8df 100644
--- a/module/data/levelup.mjs
+++ b/module/data/levelup.mjs
@@ -12,35 +12,36 @@ export class DhLevelup extends foundry.abstract.DataModel {
totalLevelProgression.push(level);
}
+ const pcSelections = Object.values(pcLevelData.levelups).flatMap(x => x.selections);
const tiers = tierKeys.reduce((acc, key) => {
acc[key] = DhLevelupTier.initializeData(
levelTierData.tiers[key],
maxLevel,
- pcLevelData.selections.filter(x => x.tier === Number(key)),
+ pcSelections.filter(x => x.tier === Number(key)),
pcLevelData.level.changed
);
return acc;
}, {});
- const allInitialAchievements = Object.values(tiers).reduce(
- (acc, tier) => {
- const levelThreshold = Math.min(...tier.belongingLevels);
+ const allInitialAchievements = Object.values(tiers).reduce((acc, tier) => {
+ const levelThreshold = Math.min(...tier.belongingLevels);
- if (totalLevelProgression.includes(levelThreshold)) {
- acc.proficiency += tier.initialAchievements.proficiency;
- [...Array(tier.initialAchievements.experience.nr).keys()].forEach(_ => {
- acc.newExperiences[foundry.utils.randomID()] = {
- name: '',
- modifier: tier.initialAchievements.experience.modifier
- };
- });
- }
+ if (totalLevelProgression.includes(levelThreshold)) {
+ acc[levelThreshold] = {
+ newExperiences: {},
+ proficiency: tier.initialAchievements.proficiency
+ };
+ [...Array(tier.initialAchievements.experience.nr).keys()].forEach(_ => {
+ acc[levelThreshold].newExperiences[foundry.utils.randomID()] = {
+ name: '',
+ modifier: tier.initialAchievements.experience.modifier
+ };
+ });
+ }
- return acc;
- },
- { newExperiences: {}, proficiency: 0 }
- );
+ return acc;
+ }, {});
const domainCards = Object.keys(tiers).reduce((acc, tierKey) => {
const tier = tiers[tierKey];
@@ -73,11 +74,9 @@ export class DhLevelup extends foundry.abstract.DataModel {
return acc;
}, 0),
- allInitialAchievements: {
- newExperiences: allInitialAchievements.newExperiences,
- proficiency: allInitialAchievements.proficiency
- },
- domainCards: domainCards
+ allInitialAchievements: allInitialAchievements,
+ domainCards: domainCards,
+ progressionLevels: totalLevelProgression
};
}
@@ -87,15 +86,17 @@ export class DhLevelup extends foundry.abstract.DataModel {
return {
tiers: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupTier)),
maxSelections: new fields.NumberField({ required: true, integer: true }),
- allInitialAchievements: new fields.SchemaField({
- newExperiences: new fields.TypedObjectField(
- new fields.SchemaField({
- name: new fields.StringField({ required: true }),
- modifier: new fields.NumberField({ required: true, integer: true })
- })
- ),
- proficiency: new fields.NumberField({ required: true, integer: true })
- }),
+ allInitialAchievements: new fields.TypedObjectField(
+ new fields.SchemaField({
+ newExperiences: new fields.TypedObjectField(
+ new fields.SchemaField({
+ name: new fields.StringField({ required: true }),
+ modifier: new fields.NumberField({ required: true, integer: true })
+ })
+ ),
+ proficiency: new fields.NumberField({ required: true, integer: true })
+ })
+ ),
domainCards: new fields.TypedObjectField(
new fields.SchemaField({
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 }),
path: new fields.StringField({ required: true })
})
- )
- // advancementSelections: new fields.SchemaField({
- // experiences: new fields.SetField(new fields.StringField()),
- // }),
+ ),
+ progressionLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }))
};
}
+ 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() {
return Object.values(this.tiers).reduce(
(acc, tier) => {
@@ -148,13 +180,35 @@ export class DhLevelup extends foundry.abstract.DataModel {
checkboxNr: Number(checkboxNr),
value: optionSelect.value,
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 {
@@ -295,6 +349,7 @@ class DhLevelupLevel extends foundry.abstract.DataModel {
if (!acc[data.optionKey]) acc[data.optionKey] = {};
acc[data.optionKey][data.checkboxNr] = {
minCost: optionSelections[data.optionKey].minCost,
+ minCost: optionSelections[data.optionKey].amount,
locked: locked
};
@@ -313,8 +368,10 @@ class DhLevelupLevel extends foundry.abstract.DataModel {
new fields.SchemaField({
selected: new fields.BooleanField({ required: true, initial: true }),
minCost: new fields.NumberField({ required: true, integer: true }),
+ amount: new fields.NumberField({ integer: true }),
locked: new fields.BooleanField({ required: true, initial: false }),
- data: new fields.StringField()
+ data: new fields.ArrayField(new fields.StringField()),
+ secondaryData: new fields.StringField()
})
)
)
diff --git a/module/data/pc.mjs b/module/data/pc.mjs
index 08763123..29c23777 100644
--- a/module/data/pc.mjs
+++ b/module/data/pc.mjs
@@ -50,7 +50,9 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
knowledge: attributeField()
}),
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(
new fields.SchemaField({
id: new fields.StringField({ required: true }),
@@ -338,7 +340,7 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
// : 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;
const armor = this.armor;
this.damageThresholds = {
@@ -429,15 +431,37 @@ class DhPCLevelData extends foundry.abstract.DataModel {
current: 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({
- 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 })
+ 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 })
+ })
+ ),
+ 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 }))
+ })
+ )
})
)
};
diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs
index ff193d93..012377c7 100644
--- a/module/documents/actor.mjs
+++ b/module/documents/actor.mjs
@@ -27,29 +27,178 @@ export default class DhpActor extends Actor {
if (newLevel > this.system.levelData.level.current) {
await this.update({ 'system.levelData.level.changed': newLevel });
} 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 = {
level: {
current: newLevel,
changed: newLevel
},
- selections: Object.keys(this.system.levelData.selections).reduce((acc, key) => {
- const level = this.system.levelData.selections[key];
- if (level.level <= newLevel) {
- acc[key] = level;
- }
+ levelups: Object.keys(this.system.levelData.levelups).reduce((acc, levelKey) => {
+ const level = Number(levelKey);
+ if (level > newLevel) acc[`-=${level}`] = null;
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) {
- await this.actor.update({
- 'system.levelData': {
- 'level.current': this.system.levelData.level.changed,
- 'selections': levelupData
+ const changes = this.getLevelChangedFeatures(
+ this.system.levelData.level.current,
+ this.system.levelData.level.changed,
+ 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
+ }
}
});
}
diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs
index 7a0743f0..8eaa4325 100644
--- a/module/helpers/utils.mjs
+++ b/module/helpers/utils.mjs
@@ -1,4 +1,5 @@
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
+import Tagify from '@yaireo/tagify';
export const loadCompendiumOptions = async compendiums => {
const compendiumValues = [];
@@ -145,3 +146,62 @@ export const chunkify = (array, chunkSize, mappingFunc) => {
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 `` : ''}
+