mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 19:51:08 +01:00
Levelup applies bonuses to character
This commit is contained in:
parent
044d0a5b21
commit
3717e2ddd4
20 changed files with 457 additions and 174 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { abilities } from '../config/actorConfig.mjs';
|
||||
import { abilities, subclassFeatureLabels } from '../config/actorConfig.mjs';
|
||||
import { domains } from '../config/domainConfig.mjs';
|
||||
import { DhLevelup } from '../data/levelup.mjs';
|
||||
import { getDeleteKeys, tagifyElement } from '../helpers/utils.mjs';
|
||||
|
|
@ -35,6 +35,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
viewCompendium: this.viewCompendium,
|
||||
selectPreview: this.selectPreview,
|
||||
selectDomain: this.selectDomain,
|
||||
selectSubclass: this.selectSubclass,
|
||||
updateCurrentLevel: this.updateCurrentLevel,
|
||||
activatePart: this.activatePart
|
||||
},
|
||||
|
|
@ -201,22 +202,38 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
}
|
||||
|
||||
const subclassSelections = advancementChoices.subclass?.flatMap(x => x.data) ?? [];
|
||||
const possibleSubclasses = [this.actor.system.class.subclass];
|
||||
if (this.actor.system.multiclass?.subclass) {
|
||||
possibleSubclasses.push(this.actor.system.multiclass.subclass);
|
||||
}
|
||||
|
||||
const multiclassSubclass = this.actor.system.multiclass?.system?.subclasses?.[0];
|
||||
const possibleSubclasses = [
|
||||
this.actor.system.class.subclass,
|
||||
...(multiclassSubclass ? [multiclassSubclass] : [])
|
||||
];
|
||||
const selectedSubclasses = possibleSubclasses.filter(x => subclassSelections.includes(x.uuid));
|
||||
// const featureStateIncrease = advancementChoices.subclass?.reduce((acc, subclass) => {
|
||||
// if(subclass.secondaryData.featureState) acc += 1;
|
||||
// return acc;
|
||||
// }, 0) ?? 0;
|
||||
context.subclassCards = [];
|
||||
if (advancementChoices.subclass?.length > 0) {
|
||||
const featureStateIncrease = Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||
acc += Object.values(level.choices).filter(choice => {
|
||||
return Object.values(choice).every(checkbox => checkbox.type === 'subclass');
|
||||
}).length;
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
for (var subclass of possibleSubclasses) {
|
||||
const choice =
|
||||
advancementChoices.subclass.find(x => x.data[0] === subclass.uuid) ??
|
||||
advancementChoices.subclass.find(x => x.data.length === 0);
|
||||
const featureState = subclass.system.featureState + featureStateIncrease;
|
||||
const data = await foundry.utils.fromUuid(subclass.uuid);
|
||||
const selected = selectedSubclasses.some(x => x.uuid === data.uuid);
|
||||
context.subclassCards.push({
|
||||
...data.toObject(),
|
||||
path: choice?.path,
|
||||
uuid: data.uuid,
|
||||
selected: selected
|
||||
selected: subclassSelections.includes(subclass.uuid),
|
||||
featureState: featureState,
|
||||
featureLabel: game.i18n.localize(subclassFeatureLabels[featureState]),
|
||||
isMulticlass: subclass.system.isMulticlass ? 'true' : 'false'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -237,10 +254,19 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
|
||||
return {
|
||||
...domain,
|
||||
selected: key === data.secondaryData,
|
||||
disabled: (data.secondaryData && key !== data.secondaryData) || alreadySelected
|
||||
selected: key === data.secondaryData.domain,
|
||||
disabled:
|
||||
(data.secondaryData.domain && key !== data.secondaryData.domain) ||
|
||||
alreadySelected
|
||||
};
|
||||
}) ?? [],
|
||||
subclasses:
|
||||
multiclass?.system?.subclasses.map(subclass => ({
|
||||
...subclass,
|
||||
uuid: subclass.uuid,
|
||||
selected: data.secondaryData.subclass === subclass.uuid,
|
||||
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
||||
})) ?? [],
|
||||
compendium: 'classes',
|
||||
limit: 1
|
||||
};
|
||||
|
|
@ -277,8 +303,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
|
||||
context.achievements = {
|
||||
proficiency: {
|
||||
old: this.actor.system.proficiency,
|
||||
new: this.actor.system.proficiency + achivementProficiency,
|
||||
old: this.actor.system.proficiency.total,
|
||||
new: this.actor.system.proficiency.total + achivementProficiency,
|
||||
shown: achivementProficiency > 0
|
||||
},
|
||||
damageThresholds: {
|
||||
|
|
@ -322,6 +348,13 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
? advancement[choiceKey] + Number(checkbox.value)
|
||||
: Number(checkbox.value);
|
||||
break;
|
||||
case 'trait':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = {};
|
||||
for (var traitKey of checkbox.data) {
|
||||
if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0;
|
||||
advancement[choiceKey][traitKey] += 1;
|
||||
}
|
||||
break;
|
||||
case 'domainCard':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
if (checkbox.data.length === 1) {
|
||||
|
|
@ -339,6 +372,33 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
});
|
||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
||||
break;
|
||||
case 'subclass':
|
||||
if (checkbox.data[0]) {
|
||||
const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
advancement[choiceKey].push({
|
||||
...subclassItem.toObject(),
|
||||
featureLabel: game.i18n.localize(
|
||||
subclassFeatureLabels[Number(checkbox.secondaryData.featureState)]
|
||||
)
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'multiclass':
|
||||
const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
const subclass = multiclassItem
|
||||
? await foundry.utils.fromUuid(checkbox.secondaryData.subclass)
|
||||
: null;
|
||||
advancement[choiceKey] = multiclassItem
|
||||
? {
|
||||
...multiclassItem.toObject(),
|
||||
domain: checkbox.secondaryData.domain
|
||||
? game.i18n.localize(domains[checkbox.secondaryData.domain].label)
|
||||
: null,
|
||||
subclass: subclass ? subclass.name : null
|
||||
}
|
||||
: {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -351,26 +411,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||
},
|
||||
hitPoints: {
|
||||
old: this.actor.system.resources.hitPoints.max,
|
||||
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
||||
old: this.actor.system.resources.hitPoints.maxTotal,
|
||||
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
|
||||
},
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.max,
|
||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion,
|
||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
traits:
|
||||
advancement.trait?.flatMap(x =>
|
||||
x.data.map(data => game.i18n.localize(abilities[data].label))
|
||||
) ?? [],
|
||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||
if (advancement.trait?.[traitKey]) {
|
||||
if (!acc) acc = {};
|
||||
acc[traitKey] = {
|
||||
label: game.i18n.localize(abilities[traitKey].label),
|
||||
old: this.actor.system.traits[traitKey].total,
|
||||
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, null),
|
||||
domainCards: advancement.domainCard ?? [],
|
||||
experiences:
|
||||
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
|
||||
[]
|
||||
[],
|
||||
multiclass: advancement.multiclass,
|
||||
subclass: advancement.subclass
|
||||
};
|
||||
|
||||
context.advancements.statistics.proficiency.shown =
|
||||
|
|
@ -486,7 +555,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
const target = event.target.closest('.card-preview-container');
|
||||
if (item.type === 'domainCard') {
|
||||
if (
|
||||
!this.actor.system.class.value.system.domains.includes(item.system.domain) &&
|
||||
!this.actor.system.domains.includes(item.system.domain) &&
|
||||
this.levelup.classUpgradeChoices?.multiclass?.domain !== item.system.domain
|
||||
) {
|
||||
ui.notifications.error(
|
||||
|
|
@ -547,8 +616,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
amount: target.dataset.amount ? Number(target.dataset.amount) : null,
|
||||
value: target.dataset.value,
|
||||
type: target.dataset.type,
|
||||
data: item.uuid,
|
||||
secondaryData: null
|
||||
data: item.uuid
|
||||
}
|
||||
});
|
||||
this.render();
|
||||
|
|
@ -562,16 +630,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
|
||||
const update = {};
|
||||
if (!button.checked) {
|
||||
if (button.dataset.cost > 1) {
|
||||
const basePath = `levels.${this.levelup.currentLevel}.choices`;
|
||||
const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`);
|
||||
if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) {
|
||||
// Simple handling that doesn't cover potential Custom LevelTiers.
|
||||
update[`levels.${this.levelup.currentLevel}.choices.-=${button.dataset.option}`] = null;
|
||||
update[`${basePath}.-=${button.dataset.option}`] = null;
|
||||
} else {
|
||||
update[
|
||||
`levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.-=${button.dataset.checkboxNr}`
|
||||
] = null;
|
||||
update[`${basePath}.${button.dataset.option}.-=${button.dataset.checkboxNr}`] = null;
|
||||
}
|
||||
} else {
|
||||
if (!this.levelup.levels[this.levelup.currentLevel].nrSelections.available) {
|
||||
if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) {
|
||||
ui.notifications.info(
|
||||
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.info.insufficentAdvancements')
|
||||
);
|
||||
|
|
@ -600,24 +668,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
|
||||
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;
|
||||
await this.levelup.updateSource({
|
||||
[`${button.dataset.path}`]: {
|
||||
data: remove ? [] : [button.dataset.uuid],
|
||||
secondaryData: {
|
||||
featureState: button.dataset.featureState,
|
||||
isMulticlass: button.dataset.isMulticlass
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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 selectDomain(_, button) {
|
||||
const option = foundry.utils.getProperty(this.levelup, button.dataset.path);
|
||||
const domain = option.secondaryData ? null : button.dataset.domain;
|
||||
const domain = option.secondaryData.domain ? null : button.dataset.domain;
|
||||
|
||||
await this.levelup.updateSource({
|
||||
multiclass: { domain },
|
||||
[`${button.dataset.path}.secondaryData`]: domain
|
||||
[`${button.dataset.path}.secondaryData.domain`]: domain
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async selectSubclass(_, button) {
|
||||
const option = foundry.utils.getProperty(this.levelup, button.dataset.path);
|
||||
const subclass = option.secondaryData.subclass ? null : button.dataset.subclass;
|
||||
|
||||
await this.levelup.updateSource({
|
||||
[`${button.dataset.path}.secondaryData.subclass`]: subclass
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { tagifyElement } from '../../../helpers/utils.mjs';
|
||||
import DaggerheartSheet from '../daggerheart-sheet.mjs';
|
||||
import Tagify from '@yaireo/tagify';
|
||||
|
||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
|
@ -72,55 +72,14 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
|
|||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
const domainInput = htmlElement.querySelector('.domain-input');
|
||||
const domainTagify = new Tagify(domainInput, {
|
||||
tagTextProp: 'name',
|
||||
enforceWhitelist: true,
|
||||
whitelist: Object.keys(SYSTEM.DOMAIN.domains).map(key => {
|
||||
const domain = SYSTEM.DOMAIN.domains[key];
|
||||
return {
|
||||
value: key,
|
||||
name: game.i18n.localize(domain.label),
|
||||
src: domain.src,
|
||||
background: domain.background
|
||||
};
|
||||
}),
|
||||
maxTags: 2,
|
||||
callbacks: { invalid: this.onAddTag },
|
||||
dropdown: {
|
||||
mapValueTo: 'name',
|
||||
searchKeys: ['name'],
|
||||
enabled: 0,
|
||||
maxItems: 20,
|
||||
closeOnSelect: true,
|
||||
highlightFirst: false
|
||||
},
|
||||
templates: {
|
||||
tag(tagData) {
|
||||
//z-index: unset; background-image: ${tagData.background}; Maybe a domain specific background for the chips?
|
||||
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>
|
||||
<img src="${tagData.src}"></i>
|
||||
</div>
|
||||
</tag>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
domainTagify.on('change', this.onDomainSelect.bind(this));
|
||||
tagifyElement(domainInput, SYSTEM.DOMAIN.domains, this.onDomainSelect.bind(this));
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.document = this.document;
|
||||
context.tabs = super._getTabs(this.constructor.TABS);
|
||||
context.domains = this.document.system.domains.map(x => SYSTEM.DOMAIN.domains[x].label);
|
||||
context.domains = this.document.system.domains;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
|
@ -136,8 +95,7 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
|
|||
}
|
||||
}
|
||||
|
||||
async onDomainSelect(event) {
|
||||
const domains = event.detail?.value ? JSON.parse(event.detail.value) : [];
|
||||
async onDomainSelect(domains) {
|
||||
await this.document.update({ 'system.domains': domains.map(x => x.value) });
|
||||
this.render(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -409,3 +409,9 @@ export const levelupData = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const subclassFeatureLabels = {
|
||||
1: 'DAGGERHEART.Sheets.PC.DomainCard.FoundationTitle',
|
||||
2: 'DAGGERHEART.Sheets.PC.DomainCard.SpecializationTitle',
|
||||
3: 'DAGGERHEART.Sheets.PC.DomainCard.MasteryTitle'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,56 +1,56 @@
|
|||
export const domains = {
|
||||
arcana: {
|
||||
id: 'arcana',
|
||||
label: 'Arcana',
|
||||
label: 'DAGGERHEART.Domains.Arcana.label',
|
||||
src: 'icons/magic/symbols/circled-gem-pink.webp',
|
||||
description: 'DAGGERHEART.Domains.Arcana'
|
||||
},
|
||||
blade: {
|
||||
id: 'blade',
|
||||
label: 'Blade',
|
||||
label: 'DAGGERHEART.Domains.Blade.label',
|
||||
src: 'icons/weapons/swords/sword-broad-crystal-paired.webp',
|
||||
description: 'DAGGERHEART.Domains.Blade'
|
||||
},
|
||||
bone: {
|
||||
id: 'bone',
|
||||
label: 'Bone',
|
||||
label: 'DAGGERHEART.Domains.Bone.label',
|
||||
src: 'icons/skills/wounds/bone-broken-marrow-red.webp',
|
||||
description: 'DAGGERHEART.Domains.Bone'
|
||||
},
|
||||
codex: {
|
||||
id: 'codex',
|
||||
label: 'Codex',
|
||||
label: 'DAGGERHEART.Domains.Codex.label',
|
||||
src: 'icons/sundries/books/book-embossed-jewel-gold-purple.webp',
|
||||
description: 'DAGGERHEART.Domains.Codex'
|
||||
},
|
||||
grace: {
|
||||
id: 'grace',
|
||||
label: 'Grace',
|
||||
label: 'DAGGERHEART.Domains.Grace.label',
|
||||
src: 'icons/skills/movement/feet-winged-boots-glowing-yellow.webp',
|
||||
description: 'DAGGERHEART.Domains.Grace'
|
||||
},
|
||||
midnight: {
|
||||
id: 'midnight',
|
||||
label: 'Midnight',
|
||||
label: 'DAGGERHEART.Domains.Midnight.label',
|
||||
src: 'icons/environment/settlement/watchtower-castle-night.webp',
|
||||
background: 'systems/daggerheart/assets/backgrounds/MidnightBackground.webp',
|
||||
description: 'DAGGERHEART.Domains.Midnight'
|
||||
},
|
||||
sage: {
|
||||
id: 'sage',
|
||||
label: 'Sage',
|
||||
label: 'DAGGERHEART.Domains.Sage.label',
|
||||
src: 'icons/sundries/misc/pipe-wooden-straight-brown.webp',
|
||||
description: 'DAGGERHEART.Domains.Sage'
|
||||
},
|
||||
splendor: {
|
||||
id: 'splendor',
|
||||
label: 'Splendor',
|
||||
label: 'DAGGERHEART.Domains.Splendor.label',
|
||||
src: 'icons/magic/control/control-influence-crown-gold.webp',
|
||||
description: 'DAGGERHEART.Domains.Splendor'
|
||||
},
|
||||
valor: {
|
||||
id: 'valor',
|
||||
label: 'Valor',
|
||||
label: 'DAGGERHEART.Domains.Valor.label',
|
||||
src: 'icons/magic/control/control-influence-rally-purple.webp',
|
||||
description: 'DAGGERHEART.Domains.Valor'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import BaseDataActor from './base.mjs';
|
|||
const attributeField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
tierMarked: new foundry.data.fields.BooleanField({ initial: false })
|
||||
});
|
||||
|
||||
const resourceField = max =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new foundry.data.fields.NumberField({ initial: max, integer: true })
|
||||
});
|
||||
|
||||
|
|
@ -40,12 +42,18 @@ export default class DhCharacter extends BaseDataActor {
|
|||
presence: attributeField(),
|
||||
knowledge: attributeField()
|
||||
}),
|
||||
proficiency: new fields.NumberField({ initial: 1, integer: true }),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
||||
proficiency: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 1, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
description: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
bonus: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
{
|
||||
initial: {
|
||||
|
|
@ -89,8 +97,8 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
get domains() {
|
||||
const classDomains = this.class ? this.class.system.domains : [];
|
||||
const multiclassDomains = this.multiclass ? this.multiclass.system.domains : [];
|
||||
const classDomains = this.class.value ? this.class.value.system.domains : [];
|
||||
const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : [];
|
||||
return [...classDomains, ...multiclassDomains];
|
||||
}
|
||||
|
||||
|
|
@ -163,9 +171,38 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
prepareBaseData() {
|
||||
for (var attributeKey in this.traits) {
|
||||
const attribute = this.traits[attributeKey];
|
||||
/* Levleup handling */
|
||||
for (let levelKey in this.levelData.levelups) {
|
||||
const level = this.levelData.levelups[levelKey];
|
||||
|
||||
this.proficiency.bonus += level.achievements.proficiency;
|
||||
|
||||
for (let selection of level.selections) {
|
||||
switch (selection.type) {
|
||||
case 'trait':
|
||||
selection.data.forEach(data => {
|
||||
this.traits[data].bonus += 1;
|
||||
});
|
||||
break;
|
||||
case 'hitPoint':
|
||||
this.resources.hitPoints.bonus += selection.value;
|
||||
break;
|
||||
case 'stress':
|
||||
this.resources.stress.bonus += selection.value;
|
||||
break;
|
||||
case 'evasion':
|
||||
this.evasion.bonus += selection.value;
|
||||
break;
|
||||
case 'proficiency':
|
||||
this.proficiency.bonus = selection.value;
|
||||
break;
|
||||
case 'experience':
|
||||
Object.keys(this.experiences).forEach(key => {
|
||||
const experience = this.experiences[key];
|
||||
experience.bonus += selection.value;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const armor = this.armor;
|
||||
|
|
@ -182,6 +219,21 @@ export default class DhCharacter extends BaseDataActor {
|
|||
prepareDerivedData() {
|
||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||
this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
|
||||
|
||||
for (var traitKey in this.traits) {
|
||||
var trait = this.traits[traitKey];
|
||||
trait.total = trait.value + trait.bonus;
|
||||
}
|
||||
|
||||
for (var experienceKey in this.experiences) {
|
||||
var experience = this.experiences[experienceKey];
|
||||
experience.total = experience.value + experience.bonus;
|
||||
}
|
||||
|
||||
this.resources.hitPoints.maxTotal = this.resources.hitPoints.max + this.resources.hitPoints.bonus;
|
||||
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
|
||||
this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus;
|
||||
this.proficiency.total = this.proficiency.value + this.proficiency.bonus;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +277,7 @@ class DhPCLevelData extends foundry.abstract.DataModel {
|
|||
minCost: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true }),
|
||||
data: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
secondaryData: new fields.StringField(),
|
||||
secondaryData: new fields.TypedObjectField(new fields.StringField({ required: true })),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,17 +36,12 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!this.actor.system.domains.find(x => x === item.system.domain)) {
|
||||
if (!this.actor.system.domains.find(x => x === this.domain)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.LacksDomain'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.actor.system.domainCards.total.length === 5) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.MaxLoadoutReached'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.actor.system.domainCards.total.find(x => x.name === item.name)) {
|
||||
if (this.actor.system.domainCards.total.find(x => x.name === this.parent.name)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.DuplicateDomainCard'));
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,24 +25,37 @@ export default class DHSubclass extends BaseDataItem {
|
|||
foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
|
||||
isMulticlass: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
|
||||
get features() {
|
||||
return {
|
||||
foundation: this.foundationFeature,
|
||||
specialization: this.featureState >= 2 ? this.specializationFeature : null,
|
||||
mastery: this.featureState === 3 ? this.masteryFeature : null
|
||||
};
|
||||
}
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
const allowed = await super._preCreate(data, options, user);
|
||||
if (allowed === false) return;
|
||||
|
||||
if (this.actor?.type === 'character') {
|
||||
const path = data.system.isMulticlass ? 'system.multiclass' : 'system.class';
|
||||
const classData = foundry.utils.getProperty(this.actor, path);
|
||||
if (!classData.value) {
|
||||
const classData = this.actor.items.find(
|
||||
x => x.type === 'class' && x.system.isMulticlass === data.system.isMulticlass
|
||||
);
|
||||
const subclassData = this.actor.items.find(
|
||||
x => x.type === 'subclass' && x.system.isMulticlass === data.system.isMulticlass
|
||||
);
|
||||
if (!classData) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.MissingClass'));
|
||||
return false;
|
||||
} else if (classData.subclass) {
|
||||
} else if (subclassData) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassAlreadySelected'));
|
||||
return false;
|
||||
} else if (classData.value.system.subclasses.every(x => x.uuid !== `Item.${data._id}`)) {
|
||||
} else if (classData.system.subclasses.every(x => x.uuid !== `Item.${data._id}`)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassNotInClass'));
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,11 +97,12 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
case 'experience':
|
||||
case 'domainCard':
|
||||
case 'subclass':
|
||||
return checkbox.amount ? checkbox.data.length === checkbox.amount : checkbox.data.length === 1;
|
||||
return checkbox.data.length === (checkbox.amount ?? 1);
|
||||
case 'multiclass':
|
||||
const classSelected = checkbox.data.length === 1;
|
||||
const domainSelected = checkbox.secondaryData;
|
||||
return classSelected && domainSelected;
|
||||
const domainSelected = checkbox.secondaryData.domain;
|
||||
const subclassSelected = checkbox.secondaryData.subclass;
|
||||
return classSelected && domainSelected && subclassSelected;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
|
@ -129,7 +130,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
get classUpgradeChoices() {
|
||||
let subclass = null;
|
||||
let subclasses = [];
|
||||
let multiclass = null;
|
||||
Object.keys(this.levels).forEach(levelKey => {
|
||||
const level = this.levels[levelKey];
|
||||
|
|
@ -138,21 +139,22 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
if (checkbox.type === 'multiclass') {
|
||||
multiclass = {
|
||||
class: checkbox.data.length > 0 ? checkbox.data[0] : null,
|
||||
domain: checkbox.secondaryData ?? null,
|
||||
domain: checkbox.secondaryData.domain ?? null,
|
||||
subclass: checkbox.secondaryData.subclass ?? null,
|
||||
tier: checkbox.tier,
|
||||
level: levelKey
|
||||
};
|
||||
}
|
||||
if (checkbox.type === 'subclass') {
|
||||
subclass = {
|
||||
subclasses.push({
|
||||
tier: checkbox.tier,
|
||||
level: levelKey
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return { subclass, multiclass };
|
||||
return { subclasses, multiclass };
|
||||
}
|
||||
|
||||
get tiersForRendering() {
|
||||
|
|
@ -179,11 +181,11 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
}, {})
|
||||
);
|
||||
|
||||
const { multiclass, subclass } = this.classUpgradeChoices;
|
||||
const { multiclass, subclasses } = this.classUpgradeChoices;
|
||||
return tierKeys.map(tierKey => {
|
||||
const tier = this.tiers[tierKey];
|
||||
const multiclassInTier = multiclass?.tier === Number(tierKey);
|
||||
const subclassInTier = subclass?.tier === Number(tierKey);
|
||||
const subclassInTier = subclasses.some(x => x.tier === Number(tierKey));
|
||||
|
||||
return {
|
||||
name: tier.name,
|
||||
|
|
@ -287,7 +289,7 @@ export class DhLevelupLevel extends foundry.abstract.DataModel {
|
|||
amount: new fields.NumberField({ integer: true }),
|
||||
value: new fields.StringField(),
|
||||
data: new fields.ArrayField(new fields.StringField()),
|
||||
secondaryData: new fields.StringField(),
|
||||
secondaryData: new fields.TypedObjectField(new fields.StringField()),
|
||||
type: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,17 +39,65 @@ export default class DhpActor extends Actor {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const domainCards = Object.keys(this.system.levelData.levelups)
|
||||
const domainCards = [];
|
||||
const experiences = [];
|
||||
const subclassFeatureState = { class: null, multiclass: null };
|
||||
let multiclass = null;
|
||||
Object.keys(this.system.levelData.levelups)
|
||||
.filter(x => x > newLevel)
|
||||
.flatMap(levelKey => {
|
||||
.forEach(levelKey => {
|
||||
const level = this.system.levelData.levelups[levelKey];
|
||||
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
|
||||
const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid);
|
||||
return [...achievementCards, ...advancementCards];
|
||||
domainCards.push(...achievementCards, ...advancementCards);
|
||||
experiences.push(...Object.keys(level.achievements.experiences));
|
||||
|
||||
const subclass = level.selections.find(x => x.type === 'subclass');
|
||||
if (subclass) {
|
||||
const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class';
|
||||
const subclassState = Number(subclass.secondaryData.featureState) - 1;
|
||||
subclassFeatureState[path] = subclassFeatureState[path]
|
||||
? Math.min(subclassState, subclassFeatureState[path])
|
||||
: subclassState;
|
||||
}
|
||||
|
||||
multiclass = level.selections.find(x => x.type === 'multiclass');
|
||||
});
|
||||
|
||||
for (var domainCard of domainCards) {
|
||||
const itemCard = await this.items.find(x => x.uuid === domainCard);
|
||||
if (experiences.length > 0) {
|
||||
this.update({
|
||||
'system.experiences': experiences.reduce((acc, key) => {
|
||||
acc[`-=${key}`] = null;
|
||||
return acc;
|
||||
}, {})
|
||||
});
|
||||
}
|
||||
|
||||
if (subclassFeatureState.class) {
|
||||
this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class });
|
||||
}
|
||||
|
||||
if (subclassFeatureState.multiclass) {
|
||||
this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass });
|
||||
}
|
||||
|
||||
if (multiclass) {
|
||||
const multiclassSubclass = this.items.find(x => x.type === 'subclass' && x.system.isMulticlass);
|
||||
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) {
|
||||
const itemCard = this.items.find(x => x.uuid === domainCard);
|
||||
itemCard.delete();
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +119,94 @@ export default class DhpActor extends Actor {
|
|||
const levelups = {};
|
||||
for (var levelKey of Object.keys(levelupData)) {
|
||||
const level = levelupData[levelKey];
|
||||
|
||||
for (var experienceKey in level.achievements.experiences) {
|
||||
const experience = level.achievements.experiences[experienceKey];
|
||||
await this.update({
|
||||
[`system.experiences.${experienceKey}`]: {
|
||||
description: experience.name,
|
||||
value: experience.modifier
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let multiclass = null;
|
||||
const domainCards = [];
|
||||
const subclassFeatureState = { class: null, multiclass: null };
|
||||
const selections = [];
|
||||
for (var optionKey of Object.keys(level.choices)) {
|
||||
const selection = level.choices[optionKey];
|
||||
for (var checkboxNr of Object.keys(selection)) {
|
||||
const checkbox = selection[checkboxNr];
|
||||
|
||||
if (checkbox.type === 'multiclass') {
|
||||
multiclass = {
|
||||
...checkbox,
|
||||
level: Number(levelKey),
|
||||
optionKey: optionKey,
|
||||
checkboxNr: Number(checkboxNr)
|
||||
};
|
||||
} else if (checkbox.type === 'domainCard') {
|
||||
domainCards.push({
|
||||
...checkbox,
|
||||
level: Number(levelKey),
|
||||
optionKey: optionKey,
|
||||
checkboxNr: Number(checkboxNr)
|
||||
});
|
||||
} else {
|
||||
if (checkbox.type === 'subclass') {
|
||||
const path = checkbox.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class';
|
||||
subclassFeatureState[path] = Math.max(
|
||||
Number(checkbox.secondaryData.featureState),
|
||||
subclassFeatureState[path]
|
||||
);
|
||||
}
|
||||
|
||||
selections.push({
|
||||
...checkbox,
|
||||
level: Number(levelKey),
|
||||
optionKey: optionKey,
|
||||
checkboxNr: Number(checkboxNr)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (multiclass) {
|
||||
const subclassItem = await foundry.utils.fromUuid(multiclass.secondaryData.subclass);
|
||||
const subclassData = subclassItem.toObject();
|
||||
const multiclassItem = await foundry.utils.fromUuid(multiclass.data[0]);
|
||||
const multiclassData = multiclassItem.toObject();
|
||||
|
||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...multiclassData,
|
||||
system: {
|
||||
...multiclassData.system,
|
||||
domains: [multiclass.secondaryData.domain],
|
||||
isMulticlass: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...subclassData,
|
||||
system: {
|
||||
...subclassData.system,
|
||||
isMulticlass: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
selections.push({ ...multiclass, itemUuid: embeddedItem[0].uuid });
|
||||
}
|
||||
|
||||
for (var domainCard of domainCards) {
|
||||
const item = await foundry.utils.fromUuid(domainCard.data[0]);
|
||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
selections.push({ ...domainCard, itemUuid: embeddedItem[0].uuid });
|
||||
}
|
||||
|
||||
const achievementDomainCards = [];
|
||||
for (var card of Object.values(level.achievements.domainCards)) {
|
||||
const item = await foundry.utils.fromUuid(card.uuid);
|
||||
|
|
@ -79,27 +215,14 @@ export default class DhpActor extends Actor {
|
|||
achievementDomainCards.push(card);
|
||||
}
|
||||
|
||||
const selections = [];
|
||||
for (var optionKey of Object.keys(level.choices)) {
|
||||
const selection = level.choices[optionKey];
|
||||
for (var checkboxNr of Object.keys(selection)) {
|
||||
const checkbox = selection[checkboxNr];
|
||||
let itemUuid = null;
|
||||
if (subclassFeatureState.class) {
|
||||
await this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class });
|
||||
}
|
||||
|
||||
if (checkbox.type === 'domainCard') {
|
||||
const item = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
itemUuid = embeddedItem[0].uuid;
|
||||
}
|
||||
|
||||
selections.push({
|
||||
...checkbox,
|
||||
level: Number(levelKey),
|
||||
optionKey: optionKey,
|
||||
checkboxNr: Number(checkboxNr),
|
||||
itemUuid
|
||||
});
|
||||
}
|
||||
if (subclassFeatureState.multiclass) {
|
||||
await this.system.multiclass.subclass.update({
|
||||
'system.featureState': subclassFeatureState.multiclass
|
||||
});
|
||||
}
|
||||
|
||||
levelups[levelKey] = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue