mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-15 13:11:08 +01:00
Levelup Followup (#126)
* Levelup applies bonuses to character * Added visualisation of domain card levels * Fixed domaincard level max for selections in a tier * A trait can now only be level up once within the same tier
This commit is contained in:
parent
f840dc2553
commit
ec3aecfe99
23 changed files with 566 additions and 202 deletions
11
lang/en.json
11
lang/en.json
|
|
@ -352,30 +352,39 @@
|
||||||
},
|
},
|
||||||
"Domains": {
|
"Domains": {
|
||||||
"Arcana": {
|
"Arcana": {
|
||||||
|
"label": "Arcana",
|
||||||
"Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled."
|
"Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled."
|
||||||
},
|
},
|
||||||
"Blade": {
|
"Blade": {
|
||||||
|
"label": "Blade",
|
||||||
"Description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death."
|
"Description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death."
|
||||||
},
|
},
|
||||||
"Bone": {
|
"Bone": {
|
||||||
|
"label": "Bone",
|
||||||
"Description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training."
|
"Description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training."
|
||||||
},
|
},
|
||||||
"Codex": {
|
"Codex": {
|
||||||
|
"label": "Codex",
|
||||||
"Description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge."
|
"Description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge."
|
||||||
},
|
},
|
||||||
"Grace": {
|
"Grace": {
|
||||||
|
"label": "Grace",
|
||||||
"Description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language."
|
"Description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language."
|
||||||
},
|
},
|
||||||
"Midnight": {
|
"Midnight": {
|
||||||
|
"label": "Midnight",
|
||||||
"Description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas."
|
"Description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas."
|
||||||
},
|
},
|
||||||
"Sage": {
|
"Sage": {
|
||||||
|
"label": "Sage",
|
||||||
"Description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator."
|
"Description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator."
|
||||||
},
|
},
|
||||||
"Splendor": {
|
"Splendor": {
|
||||||
|
"label": "Splendor",
|
||||||
"Description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life."
|
"Description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life."
|
||||||
},
|
},
|
||||||
"Valor": {
|
"Valor": {
|
||||||
|
"label": "Valor",
|
||||||
"Description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others."
|
"Description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -834,7 +843,7 @@
|
||||||
"content": "Returning to the previous level selection will remove all selections made for this level. Do you want to proceed?"
|
"content": "Returning to the previous level selection will remove all selections made for this level. Do you want to proceed?"
|
||||||
},
|
},
|
||||||
"Selections": {
|
"Selections": {
|
||||||
"emptyDomainCardHint": "Domain Card Level {level} or below"
|
"emptyDomainCardHint": "{domain} level {level} or below"
|
||||||
},
|
},
|
||||||
"summary": {
|
"summary": {
|
||||||
"levelAchievements": "Level Achievements",
|
"levelAchievements": "Level Achievements",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { abilities } from '../config/actorConfig.mjs';
|
import { abilities, subclassFeatureLabels } from '../config/actorConfig.mjs';
|
||||||
import { domains } from '../config/domainConfig.mjs';
|
import { domains } from '../config/domainConfig.mjs';
|
||||||
import { DhLevelup } from '../data/levelup.mjs';
|
import { DhLevelup } from '../data/levelup.mjs';
|
||||||
import { getDeleteKeys, tagifyElement } from '../helpers/utils.mjs';
|
import { getDeleteKeys, tagifyElement } from '../helpers/utils.mjs';
|
||||||
|
|
@ -35,6 +35,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
viewCompendium: this.viewCompendium,
|
viewCompendium: this.viewCompendium,
|
||||||
selectPreview: this.selectPreview,
|
selectPreview: this.selectPreview,
|
||||||
selectDomain: this.selectDomain,
|
selectDomain: this.selectDomain,
|
||||||
|
selectSubclass: this.selectSubclass,
|
||||||
updateCurrentLevel: this.updateCurrentLevel,
|
updateCurrentLevel: this.updateCurrentLevel,
|
||||||
activatePart: this.activatePart
|
activatePart: this.activatePart
|
||||||
},
|
},
|
||||||
|
|
@ -178,6 +179,20 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
};
|
};
|
||||||
const allDomainCardKeys = Object.keys(allDomainCards);
|
const allDomainCardKeys = Object.keys(allDomainCards);
|
||||||
|
|
||||||
|
const classDomainsData = this.actor.system.class.value.system.domains.map(domain => ({
|
||||||
|
domain,
|
||||||
|
multiclass: false
|
||||||
|
}));
|
||||||
|
const multiclassDomainsData = (this.actor.system.multiclass?.value?.system?.domains ?? []).map(
|
||||||
|
domain => ({ domain, multiclass: true })
|
||||||
|
);
|
||||||
|
const domainsData = [...classDomainsData, ...multiclassDomainsData];
|
||||||
|
const multiclassDomain = this.levelup.classUpgradeChoices?.multiclass?.domain;
|
||||||
|
if (multiclassDomain) {
|
||||||
|
if (!domainsData.some(x => x.domain === multiclassDomain))
|
||||||
|
domainsData.push({ domain: multiclassDomain, multiclass: true });
|
||||||
|
}
|
||||||
|
|
||||||
context.domainCards = [];
|
context.domainCards = [];
|
||||||
for (var key of allDomainCardKeys) {
|
for (var key of allDomainCardKeys) {
|
||||||
const domainCard = allDomainCards[key];
|
const domainCard = allDomainCards[key];
|
||||||
|
|
@ -188,35 +203,56 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
context.domainCards.push({
|
context.domainCards.push({
|
||||||
...(card.toObject?.() ?? card),
|
...(card.toObject?.() ?? card),
|
||||||
emptySubtext: game.i18n.format(
|
emptySubtexts: domainsData.map(domain => {
|
||||||
'DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint',
|
const levelBase = domain.multiclass
|
||||||
{ level: domainCard.level }
|
? Math.ceil(this.levelup.currentLevel / 2)
|
||||||
),
|
: this.levelup.currentLevel;
|
||||||
|
const levelMax = domainCard.secondaryData?.limit
|
||||||
|
? Math.min(domainCard.secondaryData.limit, levelBase)
|
||||||
|
: levelBase;
|
||||||
|
|
||||||
|
return game.i18n.format('DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', {
|
||||||
|
domain: game.i18n.localize(domains[domain.domain].label),
|
||||||
|
level: levelMax
|
||||||
|
});
|
||||||
|
}),
|
||||||
path: domainCard.data
|
path: domainCard.data
|
||||||
? `${domainCard.path}.data`
|
? `${domainCard.path}.data`
|
||||||
: `levels.${domainCard.level}.achievements.domainCards.${key}.uuid`,
|
: `levels.${domainCard.level}.achievements.domainCards.${key}.uuid`,
|
||||||
limit: domainCard.level,
|
limit: domainCard.secondaryData?.limit ?? null,
|
||||||
compendium: 'domains'
|
compendium: 'domains'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const subclassSelections = advancementChoices.subclass?.flatMap(x => x.data) ?? [];
|
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));
|
|
||||||
context.subclassCards = [];
|
context.subclassCards = [];
|
||||||
if (advancementChoices.subclass?.length > 0) {
|
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) {
|
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 data = await foundry.utils.fromUuid(subclass.uuid);
|
||||||
const selected = selectedSubclasses.some(x => x.uuid === data.uuid);
|
|
||||||
context.subclassCards.push({
|
context.subclassCards.push({
|
||||||
...data.toObject(),
|
...data.toObject(),
|
||||||
|
path: choice?.path,
|
||||||
uuid: data.uuid,
|
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 +273,19 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...domain,
|
...domain,
|
||||||
selected: key === data.secondaryData,
|
selected: key === data.secondaryData.domain,
|
||||||
disabled: (data.secondaryData && key !== data.secondaryData) || alreadySelected
|
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',
|
compendium: 'classes',
|
||||||
limit: 1
|
limit: 1
|
||||||
};
|
};
|
||||||
|
|
@ -277,8 +322,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
context.achievements = {
|
context.achievements = {
|
||||||
proficiency: {
|
proficiency: {
|
||||||
old: this.actor.system.proficiency,
|
old: this.actor.system.proficiency.total,
|
||||||
new: this.actor.system.proficiency + achivementProficiency,
|
new: this.actor.system.proficiency.total + achivementProficiency,
|
||||||
shown: achivementProficiency > 0
|
shown: achivementProficiency > 0
|
||||||
},
|
},
|
||||||
damageThresholds: {
|
damageThresholds: {
|
||||||
|
|
@ -322,6 +367,13 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
? advancement[choiceKey] + Number(checkbox.value)
|
? advancement[choiceKey] + Number(checkbox.value)
|
||||||
: Number(checkbox.value);
|
: Number(checkbox.value);
|
||||||
break;
|
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':
|
case 'domainCard':
|
||||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||||
if (checkbox.data.length === 1) {
|
if (checkbox.data.length === 1) {
|
||||||
|
|
@ -339,6 +391,33 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
});
|
});
|
||||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
||||||
break;
|
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 +430,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||||
},
|
},
|
||||||
hitPoints: {
|
hitPoints: {
|
||||||
old: this.actor.system.resources.hitPoints.max,
|
old: this.actor.system.resources.hitPoints.maxTotal,
|
||||||
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
|
||||||
},
|
},
|
||||||
stress: {
|
stress: {
|
||||||
old: this.actor.system.resources.stress.max,
|
old: this.actor.system.resources.stress.maxTotal,
|
||||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||||
},
|
},
|
||||||
evasion: {
|
evasion: {
|
||||||
old: this.actor.system.evasion,
|
old: this.actor.system.evasion.total,
|
||||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
traits:
|
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||||
advancement.trait?.flatMap(x =>
|
if (advancement.trait?.[traitKey]) {
|
||||||
x.data.map(data => game.i18n.localize(abilities[data].label))
|
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 ?? [],
|
domainCards: advancement.domainCard ?? [],
|
||||||
experiences:
|
experiences:
|
||||||
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
|
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
|
||||||
[]
|
[],
|
||||||
|
multiclass: advancement.multiclass,
|
||||||
|
subclass: advancement.subclass
|
||||||
};
|
};
|
||||||
|
|
||||||
context.advancements.statistics.proficiency.shown =
|
context.advancements.statistics.proficiency.shown =
|
||||||
|
|
@ -419,7 +507,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
const traitsTagify = htmlElement.querySelector('.levelup-trait-increases');
|
const traitsTagify = htmlElement.querySelector('.levelup-trait-increases');
|
||||||
if (traitsTagify) {
|
if (traitsTagify) {
|
||||||
tagifyElement(traitsTagify, abilities, this.tagifyUpdate('trait').bind(this));
|
tagifyElement(traitsTagify, this.levelup.unmarkedTraits, this.tagifyUpdate('trait').bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
||||||
|
|
@ -485,8 +573,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
if (event.target.closest('.domain-cards')) {
|
if (event.target.closest('.domain-cards')) {
|
||||||
const target = event.target.closest('.card-preview-container');
|
const target = event.target.closest('.card-preview-container');
|
||||||
if (item.type === 'domainCard') {
|
if (item.type === 'domainCard') {
|
||||||
|
const { multiclass } = this.levelup.classUpgradeChoices;
|
||||||
|
const isMulticlass = !multiclass ? false : item.system.domain === multiclass.domain;
|
||||||
if (
|
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
|
this.levelup.classUpgradeChoices?.multiclass?.domain !== item.system.domain
|
||||||
) {
|
) {
|
||||||
ui.notifications.error(
|
ui.notifications.error(
|
||||||
|
|
@ -495,7 +585,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.system.level > Number(target.dataset.limit)) {
|
const levelBase = isMulticlass ? Math.ceil(this.levelup.currentLevel / 2) : this.levelup.currentLevel;
|
||||||
|
const levelMax = target.dataset.limit ? Math.min(Number(target.dataset.limit), levelBase) : levelBase;
|
||||||
|
if (levelMax < item.system.level) {
|
||||||
ui.notifications.error(
|
ui.notifications.error(
|
||||||
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel')
|
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel')
|
||||||
);
|
);
|
||||||
|
|
@ -547,8 +639,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
amount: target.dataset.amount ? Number(target.dataset.amount) : null,
|
amount: target.dataset.amount ? Number(target.dataset.amount) : null,
|
||||||
value: target.dataset.value,
|
value: target.dataset.value,
|
||||||
type: target.dataset.type,
|
type: target.dataset.type,
|
||||||
data: item.uuid,
|
data: item.uuid
|
||||||
secondaryData: null
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -562,16 +653,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
const update = {};
|
const update = {};
|
||||||
if (!button.checked) {
|
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.
|
// 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 {
|
} else {
|
||||||
update[
|
update[`${basePath}.${button.dataset.option}.-=${button.dataset.checkboxNr}`] = null;
|
||||||
`levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.-=${button.dataset.checkboxNr}`
|
|
||||||
] = null;
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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(
|
ui.notifications.info(
|
||||||
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.info.insufficentAdvancements')
|
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.info.insufficentAdvancements')
|
||||||
);
|
);
|
||||||
|
|
@ -579,15 +670,23 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
update[
|
const updateData = {
|
||||||
`levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.${button.dataset.checkboxNr}`
|
|
||||||
] = {
|
|
||||||
tier: Number(button.dataset.tier),
|
tier: Number(button.dataset.tier),
|
||||||
minCost: Number(button.dataset.cost),
|
minCost: Number(button.dataset.cost),
|
||||||
amount: button.dataset.amount ? Number(button.dataset.amount) : null,
|
amount: button.dataset.amount ? Number(button.dataset.amount) : null,
|
||||||
value: button.dataset.value,
|
value: button.dataset.value,
|
||||||
type: button.dataset.type
|
type: button.dataset.type
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (button.dataset.type === 'domainCard') {
|
||||||
|
updateData.secondaryData = {
|
||||||
|
limit: Math.max(...this.levelup.tiers[button.dataset.tier].belongingLevels)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
update[
|
||||||
|
`levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.${button.dataset.checkboxNr}`
|
||||||
|
] = updateData;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.levelup.updateSource(update);
|
await this.levelup.updateSource(update);
|
||||||
|
|
@ -600,24 +699,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
static async selectPreview(_, button) {
|
static async selectPreview(_, button) {
|
||||||
const remove = button.dataset.selected;
|
const remove = button.dataset.selected;
|
||||||
const selectionData = Object.values(this.levelup.selectionData);
|
await this.levelup.updateSource({
|
||||||
const option = remove
|
[`${button.dataset.path}`]: {
|
||||||
? selectionData.find(x => x.type === 'subclass' && x.data.includes(button.dataset.uuid))
|
data: remove ? [] : [button.dataset.uuid],
|
||||||
: selectionData.find(x => x.type === 'subclass' && x.data.length === 0);
|
secondaryData: {
|
||||||
if (!option) return;
|
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();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async selectDomain(_, button) {
|
static async selectDomain(_, button) {
|
||||||
const option = foundry.utils.getProperty(this.levelup, button.dataset.path);
|
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({
|
await this.levelup.updateSource({
|
||||||
multiclass: { domain },
|
[`${button.dataset.path}.secondaryData.domain`]: domain
|
||||||
[`${button.dataset.path}.secondaryData`]: 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();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { tagifyElement } from '../../../helpers/utils.mjs';
|
||||||
import DaggerheartSheet from '../daggerheart-sheet.mjs';
|
import DaggerheartSheet from '../daggerheart-sheet.mjs';
|
||||||
import Tagify from '@yaireo/tagify';
|
|
||||||
|
|
||||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
const { TextEditor } = foundry.applications.ux;
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
@ -72,55 +72,14 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
const domainInput = htmlElement.querySelector('.domain-input');
|
const domainInput = htmlElement.querySelector('.domain-input');
|
||||||
const domainTagify = new Tagify(domainInput, {
|
tagifyElement(domainInput, SYSTEM.DOMAIN.domains, this.onDomainSelect.bind(this));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.document = this.document;
|
context.document = this.document;
|
||||||
context.tabs = super._getTabs(this.constructor.TABS);
|
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;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
@ -136,8 +95,7 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onDomainSelect(event) {
|
async onDomainSelect(domains) {
|
||||||
const domains = event.detail?.value ? JSON.parse(event.detail.value) : [];
|
|
||||||
await this.document.update({ 'system.domains': domains.map(x => x.value) });
|
await this.document.update({ 'system.domains': domains.map(x => x.value) });
|
||||||
this.render(true);
|
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 = {
|
export const domains = {
|
||||||
arcana: {
|
arcana: {
|
||||||
id: 'arcana',
|
id: 'arcana',
|
||||||
label: 'Arcana',
|
label: 'DAGGERHEART.Domains.Arcana.label',
|
||||||
src: 'icons/magic/symbols/circled-gem-pink.webp',
|
src: 'icons/magic/symbols/circled-gem-pink.webp',
|
||||||
description: 'DAGGERHEART.Domains.Arcana'
|
description: 'DAGGERHEART.Domains.Arcana'
|
||||||
},
|
},
|
||||||
blade: {
|
blade: {
|
||||||
id: 'blade',
|
id: 'blade',
|
||||||
label: 'Blade',
|
label: 'DAGGERHEART.Domains.Blade.label',
|
||||||
src: 'icons/weapons/swords/sword-broad-crystal-paired.webp',
|
src: 'icons/weapons/swords/sword-broad-crystal-paired.webp',
|
||||||
description: 'DAGGERHEART.Domains.Blade'
|
description: 'DAGGERHEART.Domains.Blade'
|
||||||
},
|
},
|
||||||
bone: {
|
bone: {
|
||||||
id: 'bone',
|
id: 'bone',
|
||||||
label: 'Bone',
|
label: 'DAGGERHEART.Domains.Bone.label',
|
||||||
src: 'icons/skills/wounds/bone-broken-marrow-red.webp',
|
src: 'icons/skills/wounds/bone-broken-marrow-red.webp',
|
||||||
description: 'DAGGERHEART.Domains.Bone'
|
description: 'DAGGERHEART.Domains.Bone'
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
id: 'codex',
|
id: 'codex',
|
||||||
label: 'Codex',
|
label: 'DAGGERHEART.Domains.Codex.label',
|
||||||
src: 'icons/sundries/books/book-embossed-jewel-gold-purple.webp',
|
src: 'icons/sundries/books/book-embossed-jewel-gold-purple.webp',
|
||||||
description: 'DAGGERHEART.Domains.Codex'
|
description: 'DAGGERHEART.Domains.Codex'
|
||||||
},
|
},
|
||||||
grace: {
|
grace: {
|
||||||
id: 'grace',
|
id: 'grace',
|
||||||
label: 'Grace',
|
label: 'DAGGERHEART.Domains.Grace.label',
|
||||||
src: 'icons/skills/movement/feet-winged-boots-glowing-yellow.webp',
|
src: 'icons/skills/movement/feet-winged-boots-glowing-yellow.webp',
|
||||||
description: 'DAGGERHEART.Domains.Grace'
|
description: 'DAGGERHEART.Domains.Grace'
|
||||||
},
|
},
|
||||||
midnight: {
|
midnight: {
|
||||||
id: 'midnight',
|
id: 'midnight',
|
||||||
label: 'Midnight',
|
label: 'DAGGERHEART.Domains.Midnight.label',
|
||||||
src: 'icons/environment/settlement/watchtower-castle-night.webp',
|
src: 'icons/environment/settlement/watchtower-castle-night.webp',
|
||||||
background: 'systems/daggerheart/assets/backgrounds/MidnightBackground.webp',
|
background: 'systems/daggerheart/assets/backgrounds/MidnightBackground.webp',
|
||||||
description: 'DAGGERHEART.Domains.Midnight'
|
description: 'DAGGERHEART.Domains.Midnight'
|
||||||
},
|
},
|
||||||
sage: {
|
sage: {
|
||||||
id: 'sage',
|
id: 'sage',
|
||||||
label: 'Sage',
|
label: 'DAGGERHEART.Domains.Sage.label',
|
||||||
src: 'icons/sundries/misc/pipe-wooden-straight-brown.webp',
|
src: 'icons/sundries/misc/pipe-wooden-straight-brown.webp',
|
||||||
description: 'DAGGERHEART.Domains.Sage'
|
description: 'DAGGERHEART.Domains.Sage'
|
||||||
},
|
},
|
||||||
splendor: {
|
splendor: {
|
||||||
id: 'splendor',
|
id: 'splendor',
|
||||||
label: 'Splendor',
|
label: 'DAGGERHEART.Domains.Splendor.label',
|
||||||
src: 'icons/magic/control/control-influence-crown-gold.webp',
|
src: 'icons/magic/control/control-influence-crown-gold.webp',
|
||||||
description: 'DAGGERHEART.Domains.Splendor'
|
description: 'DAGGERHEART.Domains.Splendor'
|
||||||
},
|
},
|
||||||
valor: {
|
valor: {
|
||||||
id: 'valor',
|
id: 'valor',
|
||||||
label: 'Valor',
|
label: 'DAGGERHEART.Domains.Valor.label',
|
||||||
src: 'icons/magic/control/control-influence-rally-purple.webp',
|
src: 'icons/magic/control/control-influence-rally-purple.webp',
|
||||||
description: 'DAGGERHEART.Domains.Valor'
|
description: 'DAGGERHEART.Domains.Valor'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ import BaseDataActor from './base.mjs';
|
||||||
const attributeField = () =>
|
const attributeField = () =>
|
||||||
new foundry.data.fields.SchemaField({
|
new foundry.data.fields.SchemaField({
|
||||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
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 })
|
tierMarked: new foundry.data.fields.BooleanField({ initial: false })
|
||||||
});
|
});
|
||||||
|
|
||||||
const resourceField = max =>
|
const resourceField = max =>
|
||||||
new foundry.data.fields.SchemaField({
|
new foundry.data.fields.SchemaField({
|
||||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
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 })
|
max: new foundry.data.fields.NumberField({ initial: max, integer: true })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -40,12 +42,18 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
presence: attributeField(),
|
presence: attributeField(),
|
||||||
knowledge: attributeField()
|
knowledge: attributeField()
|
||||||
}),
|
}),
|
||||||
proficiency: new fields.NumberField({ initial: 1, integer: true }),
|
proficiency: new fields.SchemaField({
|
||||||
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
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(
|
experiences: new fields.TypedObjectField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
description: new fields.StringField({}),
|
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: {
|
initial: {
|
||||||
|
|
@ -89,8 +97,8 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
get domains() {
|
get domains() {
|
||||||
const classDomains = this.class ? this.class.system.domains : [];
|
const classDomains = this.class.value ? this.class.value.system.domains : [];
|
||||||
const multiclassDomains = this.multiclass ? this.multiclass.system.domains : [];
|
const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : [];
|
||||||
return [...classDomains, ...multiclassDomains];
|
return [...classDomains, ...multiclassDomains];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,9 +171,46 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
for (var attributeKey in this.traits) {
|
const currentLevel = this.levelData.level.current;
|
||||||
const attribute = this.traits[attributeKey];
|
const currentTier =
|
||||||
/* Levleup handling */
|
currentLevel === 1
|
||||||
|
? null
|
||||||
|
: Object.values(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers).tiers).find(
|
||||||
|
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
|
||||||
|
).tier;
|
||||||
|
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;
|
||||||
|
this.traits[data].tierMarked = selection.tier === currentTier;
|
||||||
|
});
|
||||||
|
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;
|
const armor = this.armor;
|
||||||
|
|
@ -182,6 +227,21 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||||
this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
|
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 +285,7 @@ class DhPCLevelData extends foundry.abstract.DataModel {
|
||||||
minCost: new fields.NumberField({ integer: true }),
|
minCost: new fields.NumberField({ integer: true }),
|
||||||
amount: new fields.NumberField({ integer: true }),
|
amount: new fields.NumberField({ integer: true }),
|
||||||
data: new fields.ArrayField(new fields.StringField({ required: 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 })
|
itemUuid: new fields.StringField({ required: true })
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ export default class DHClass extends BaseDataItem {
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
domains: new fields.ArrayField(new fields.StringField(), { max: 2 }),
|
domains: new fields.ArrayField(new fields.StringField(), { max: 2 }),
|
||||||
|
|
||||||
classItems: new fields.ArrayField(new ForeignDocumentUUIDField({ type: 'Item' })),
|
classItems: new fields.ArrayField(new ForeignDocumentUUIDField({ type: 'Item' })),
|
||||||
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
||||||
features: new fields.ArrayField(new ForeignDocumentUUIDField({ type: 'Item' })),
|
features: new fields.ArrayField(new ForeignDocumentUUIDField({ type: 'Item' })),
|
||||||
|
|
|
||||||
|
|
@ -36,17 +36,12 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
return false;
|
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'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.LacksDomain'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.actor.system.domainCards.total.length === 5) {
|
if (this.actor.system.domainCards.total.find(x => x.name === this.parent.name)) {
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.MaxLoadoutReached'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.actor.system.domainCards.total.find(x => x.name === item.name)) {
|
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.DuplicateDomainCard'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.DuplicateDomainCard'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,24 +25,37 @@ export default class DHSubclass extends BaseDataItem {
|
||||||
foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||||
specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||||
masteryFeature: 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 })
|
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) {
|
async _preCreate(data, options, user) {
|
||||||
const allowed = await super._preCreate(data, options, user);
|
const allowed = await super._preCreate(data, options, user);
|
||||||
if (allowed === false) return;
|
if (allowed === false) return;
|
||||||
|
|
||||||
if (this.actor?.type === 'character') {
|
if (this.actor?.type === 'character') {
|
||||||
const path = data.system.isMulticlass ? 'system.multiclass' : 'system.class';
|
const classData = this.actor.items.find(
|
||||||
const classData = foundry.utils.getProperty(this.actor, path);
|
x => x.type === 'class' && x.system.isMulticlass === data.system.isMulticlass
|
||||||
if (!classData.value) {
|
);
|
||||||
|
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'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.MissingClass'));
|
||||||
return false;
|
return false;
|
||||||
} else if (classData.subclass) {
|
} else if (subclassData) {
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassAlreadySelected'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassAlreadySelected'));
|
||||||
return false;
|
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'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassNotInClass'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { abilities } from '../config/actorConfig.mjs';
|
||||||
import { chunkify } from '../helpers/utils.mjs';
|
import { chunkify } from '../helpers/utils.mjs';
|
||||||
import { LevelOptionType } from './levelTier.mjs';
|
import { LevelOptionType } from './levelTier.mjs';
|
||||||
|
|
||||||
|
|
@ -97,11 +98,12 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
||||||
case 'experience':
|
case 'experience':
|
||||||
case 'domainCard':
|
case 'domainCard':
|
||||||
case 'subclass':
|
case 'subclass':
|
||||||
return checkbox.amount ? checkbox.data.length === checkbox.amount : checkbox.data.length === 1;
|
return checkbox.data.length === (checkbox.amount ?? 1);
|
||||||
case 'multiclass':
|
case 'multiclass':
|
||||||
const classSelected = checkbox.data.length === 1;
|
const classSelected = checkbox.data.length === 1;
|
||||||
const domainSelected = checkbox.secondaryData;
|
const domainSelected = checkbox.secondaryData.domain;
|
||||||
return classSelected && domainSelected;
|
const subclassSelected = checkbox.secondaryData.subclass;
|
||||||
|
return classSelected && domainSelected && subclassSelected;
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -128,8 +130,37 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
||||||
.every(this.#levelFinished.bind(this));
|
.every(this.#levelFinished.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get unmarkedTraits() {
|
||||||
|
const possibleLevels = Object.values(this.tiers).reduce((acc, tier) => {
|
||||||
|
if (tier.belongingLevels.includes(this.currentLevel)) acc = tier.belongingLevels;
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return Object.keys(this.levels)
|
||||||
|
.filter(key => possibleLevels.some(x => x === Number(key)))
|
||||||
|
.reduce(
|
||||||
|
(acc, levelKey) => {
|
||||||
|
const level = this.levels[levelKey];
|
||||||
|
Object.values(level.choices).forEach(choice =>
|
||||||
|
Object.values(choice).forEach(checkbox => {
|
||||||
|
if (
|
||||||
|
checkbox.type === 'trait' &&
|
||||||
|
checkbox.data.length > 0 &&
|
||||||
|
Number(levelKey) !== this.currentLevel
|
||||||
|
) {
|
||||||
|
checkbox.data.forEach(data => delete acc[data]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ ...abilities }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get classUpgradeChoices() {
|
get classUpgradeChoices() {
|
||||||
let subclass = null;
|
let subclasses = [];
|
||||||
let multiclass = null;
|
let multiclass = null;
|
||||||
Object.keys(this.levels).forEach(levelKey => {
|
Object.keys(this.levels).forEach(levelKey => {
|
||||||
const level = this.levels[levelKey];
|
const level = this.levels[levelKey];
|
||||||
|
|
@ -138,21 +169,22 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
||||||
if (checkbox.type === 'multiclass') {
|
if (checkbox.type === 'multiclass') {
|
||||||
multiclass = {
|
multiclass = {
|
||||||
class: checkbox.data.length > 0 ? checkbox.data[0] : null,
|
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,
|
tier: checkbox.tier,
|
||||||
level: levelKey
|
level: levelKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (checkbox.type === 'subclass') {
|
if (checkbox.type === 'subclass') {
|
||||||
subclass = {
|
subclasses.push({
|
||||||
tier: checkbox.tier,
|
tier: checkbox.tier,
|
||||||
level: levelKey
|
level: levelKey
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return { subclass, multiclass };
|
return { subclasses, multiclass };
|
||||||
}
|
}
|
||||||
|
|
||||||
get tiersForRendering() {
|
get tiersForRendering() {
|
||||||
|
|
@ -179,11 +211,11 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
||||||
}, {})
|
}, {})
|
||||||
);
|
);
|
||||||
|
|
||||||
const { multiclass, subclass } = this.classUpgradeChoices;
|
const { multiclass, subclasses } = this.classUpgradeChoices;
|
||||||
return tierKeys.map(tierKey => {
|
return tierKeys.map((tierKey, tierIndex) => {
|
||||||
const tier = this.tiers[tierKey];
|
const tier = this.tiers[tierKey];
|
||||||
const multiclassInTier = multiclass?.tier === Number(tierKey);
|
const multiclassInTier = multiclass?.tier === Number(tierKey);
|
||||||
const subclassInTier = subclass?.tier === Number(tierKey);
|
const subclassInTier = subclasses.some(x => x.tier === Number(tierKey));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: tier.name,
|
name: tier.name,
|
||||||
|
|
@ -214,8 +246,15 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
||||||
|
|
||||||
return checkbox;
|
return checkbox;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let label = game.i18n.localize(option.label);
|
||||||
|
if (optionKey === 'domainCard') {
|
||||||
|
const maxLevel = tier.belongingLevels[tier.belongingLevels.length - 1];
|
||||||
|
label = game.i18n.format(option.label, { maxLevel });
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: game.i18n.localize(option.label),
|
label: label,
|
||||||
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
|
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
|
||||||
const anySelected = chunkedBoxes.some(x => x.selected);
|
const anySelected = chunkedBoxes.some(x => x.selected);
|
||||||
const anyDisabled = chunkedBoxes.some(x => x.disabled);
|
const anyDisabled = chunkedBoxes.some(x => x.disabled);
|
||||||
|
|
@ -287,7 +326,7 @@ export class DhLevelupLevel extends foundry.abstract.DataModel {
|
||||||
amount: new fields.NumberField({ integer: true }),
|
amount: new fields.NumberField({ integer: true }),
|
||||||
value: new fields.StringField(),
|
value: new fields.StringField(),
|
||||||
data: new fields.ArrayField(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 })
|
type: new fields.StringField({ required: true })
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -40,17 +40,65 @@ export default class DhpActor extends Actor {
|
||||||
return acc;
|
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)
|
.filter(x => x > newLevel)
|
||||||
.flatMap(levelKey => {
|
.forEach(levelKey => {
|
||||||
const level = this.system.levelData.levelups[levelKey];
|
const level = this.system.levelData.levelups[levelKey];
|
||||||
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
|
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
|
||||||
const advancementCards = level.selections.filter(x => x.type === 'domainCard').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) {
|
if (experiences.length > 0) {
|
||||||
const itemCard = await this.items.find(x => x.uuid === domainCard);
|
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();
|
itemCard.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,6 +120,94 @@ export default class DhpActor extends Actor {
|
||||||
const levelups = {};
|
const levelups = {};
|
||||||
for (var levelKey of Object.keys(levelupData)) {
|
for (var levelKey of Object.keys(levelupData)) {
|
||||||
const level = levelupData[levelKey];
|
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 = [];
|
const achievementDomainCards = [];
|
||||||
for (var card of Object.values(level.achievements.domainCards)) {
|
for (var card of Object.values(level.achievements.domainCards)) {
|
||||||
const item = await foundry.utils.fromUuid(card.uuid);
|
const item = await foundry.utils.fromUuid(card.uuid);
|
||||||
|
|
@ -80,27 +216,14 @@ export default class DhpActor extends Actor {
|
||||||
achievementDomainCards.push(card);
|
achievementDomainCards.push(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
const selections = [];
|
if (subclassFeatureState.class) {
|
||||||
for (var optionKey of Object.keys(level.choices)) {
|
await this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class });
|
||||||
const selection = level.choices[optionKey];
|
}
|
||||||
for (var checkboxNr of Object.keys(selection)) {
|
|
||||||
const checkbox = selection[checkboxNr];
|
|
||||||
let itemUuid = null;
|
|
||||||
|
|
||||||
if (checkbox.type === 'domainCard') {
|
if (subclassFeatureState.multiclass) {
|
||||||
const item = await foundry.utils.fromUuid(checkbox.data[0]);
|
await this.system.multiclass.subclass.update({
|
||||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]);
|
'system.featureState': subclassFeatureState.multiclass
|
||||||
itemUuid = embeddedItem[0].uuid;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
selections.push({
|
|
||||||
...checkbox,
|
|
||||||
level: Number(levelKey),
|
|
||||||
optionKey: optionKey,
|
|
||||||
checkboxNr: Number(checkboxNr),
|
|
||||||
itemUuid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
levelups[levelKey] = {
|
levelups[levelKey] = {
|
||||||
|
|
|
||||||
|
|
@ -174,17 +174,17 @@ export const tagifyElement = (element, options, onChange, tagifyOptions = {}) =>
|
||||||
templates: {
|
templates: {
|
||||||
tag(tagData) {
|
tag(tagData) {
|
||||||
return `<tag title="${tagData.title || tagData.value}"
|
return `<tag title="${tagData.title || tagData.value}"
|
||||||
contenteditable='false'
|
contenteditable='false'
|
||||||
spellcheck='false'
|
spellcheck='false'
|
||||||
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
||||||
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
||||||
${this.getAttributes(tagData)}>
|
${this.getAttributes(tagData)}>
|
||||||
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
||||||
<div>
|
<div>
|
||||||
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
|
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
|
||||||
${tagData.src ? `<img src="${tagData.src}"></i>` : ''}
|
${tagData.src ? `<img src="${tagData.src}"></i>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</tag>`;
|
</tag>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3379,6 +3379,7 @@ div.daggerheart.views.multiclass {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="preview-empty-container">
|
<div class="preview-empty-container">
|
||||||
<div class="preview-empty-inner-container">
|
<div class="preview-empty-inner-container">
|
||||||
<i class="preview-add-icon fa-solid fa-plus"></i>
|
<i class="preview-add-icon fa-solid fa-plus"></i>
|
||||||
<div class="preview-empty-subtext">{{this.emptySubtext}}</div>
|
<div class="preview-empty-subtext">{{> @partial-block }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,19 @@
|
||||||
{{#each this.attributes as |attribute key|}}
|
{{#each this.attributes as |attribute key|}}
|
||||||
<div class="attribute">
|
<div class="attribute">
|
||||||
<div class="attribute-banner">
|
<div class="attribute-banner">
|
||||||
<img class="attribute-roll" data-action="attributeRoll" data-attribute="{{key}}" data-value="{{attribute.value}}" src="icons/svg/d12-grey.svg" />
|
<img class="attribute-roll" data-action="attributeRoll" data-attribute="{{key}}" data-value="{{attribute.total}}" src="icons/svg/d12-grey.svg" />
|
||||||
<div class="attribute-text">{{key}}</div>
|
<div class="attribute-text">{{key}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="attribute-image">
|
<div class="attribute-image">
|
||||||
{{#if ../editAttributes}}
|
{{#if ../editAttributes}}
|
||||||
<select class="attribute-value{{#if (lt attribute.value 0)}} negative{{/if}}{{#if (and (not attribute.value) (not ../abilityScoresFinished))}} unselected{{/if}}" data-attribute="{{key}}">
|
<select class="attribute-value{{#if (lt attribute.total 0)}} negative{{/if}}{{#if (and (not attribute.total) (not ../abilityScoresFinished))}} unselected{{/if}}" data-attribute="{{key}}">
|
||||||
{{#if (not (eq attribute.value 0))}}<option value="">{{attribute.value}}</option>{{/if}}
|
{{#if (not (eq attribute.total 0))}}<option value="">{{attribute.total}}</option>{{/if}}
|
||||||
{{#each ../abilityScoreArray as |option|}}
|
{{#each ../abilityScoreArray as |option|}}
|
||||||
<option value="{{option.value}}">{{option.name}}</option>
|
<option value="{{option.total}}">{{option.name}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="attribute-text {{#if (lt attribute.value 0)}}negative{{/if}}">{{attribute.value}}</div>
|
<div class="attribute-text {{#if (lt attribute.total 0)}}negative{{/if}}">{{attribute.total}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<img src="systems/daggerheart/assets/AttributeShield.svg" />
|
<img src="systems/daggerheart/assets/AttributeShield.svg" />
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -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.total}}</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>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
{{#each document.system.experiences as |experience id|}}
|
{{#each document.system.experiences as |experience id|}}
|
||||||
<div class="experience-row">
|
<div class="experience-row">
|
||||||
<input name="{{concat "system.experiences." id ".description"}}" data-experience={{id}} value="{{experience.description}}" type="text" />
|
<input name="{{concat "system.experiences." id ".description"}}" data-experience={{id}} value="{{experience.description}}" type="text" />
|
||||||
<div name="{{concat "system.experiences." id ".value"}}" class="experience-value">{{experience.value}}</div>
|
<div name="{{concat "system.experiences." id ".value"}}" class="experience-value">{{experience.total}}</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#times (subtract 5 (length document.system.experiences))}}
|
{{#times (subtract 5 (length document.system.experiences))}}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<i class="fa-solid fa-caret-left"></i>
|
<i class="fa-solid fa-caret-left"></i>
|
||||||
<div class="health-category">{{localize "DAGGERHEART.Sheets.PC.Health.Severe"}}</div>
|
<div class="health-category">{{localize "DAGGERHEART.Sheets.PC.Health.Severe"}}</div>
|
||||||
</div>
|
</div>
|
||||||
<i data-action="makeDeathMove" class="fas fa-skull death-save {{#if (lt resources.hitPoints.value document.system.resources.hitPoints.max)}}disabled{{/if}}" title="{{localize "DAGGERHEART.Sheets.PC.Health.DeathMoveTooltip"}}"></i>
|
<i data-action="makeDeathMove" class="fas fa-skull death-save {{#if (lt resources.hitPoints.value document.system.resources.hitPoints.maxTotal)}}disabled{{/if}}" title="{{localize "DAGGERHEART.Sheets.PC.Health.DeathMoveTooltip"}}"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexrow" style="flex-wrap: nowrap; align-items: center;">
|
<div class="flexrow" style="flex-wrap: nowrap; align-items: center;">
|
||||||
<div class="flexcol flex0">
|
<div class="flexcol flex0">
|
||||||
|
|
@ -30,22 +30,22 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flexcol">
|
<div class="flexcol">
|
||||||
<div class="flexrow" style="flex-wrap: nowrap;">
|
<div class="flexrow" style="flex-wrap: nowrap;">
|
||||||
{{#times document.system.resources.hitPoints.max}}
|
{{#times document.system.resources.hitPoints.maxTotal}}
|
||||||
{{#with (add this 1)}}
|
{{#with (add this 1)}}
|
||||||
<input class="resource-box" type="checkbox" data-action="toggleHP" data-value="{{this}}" {{ checked (gte ../../document.system.resources.hitPoints.value this) }} />
|
<input class="resource-box" type="checkbox" data-action="toggleHP" data-value="{{this}}" {{ checked (gte ../../document.system.resources.hitPoints.value this) }} />
|
||||||
{{/with}}
|
{{/with}}
|
||||||
{{/times}}
|
{{/times}}
|
||||||
{{#times (subtract 12 document.system.resources.hitPoints.max)}}
|
{{#times (subtract 12 document.system.resources.hitPoints.maxTotal)}}
|
||||||
<input class="resource-box disabled" type="checkbox" disabled />
|
<input class="resource-box disabled" type="checkbox" disabled />
|
||||||
{{/times}}
|
{{/times}}
|
||||||
</div>
|
</div>
|
||||||
<div class="flexrow">
|
<div class="flexrow">
|
||||||
{{#times document.system.resources.stress.max}}
|
{{#times document.system.resources.stress.maxTotal}}
|
||||||
{{#with (add this 1)}}
|
{{#with (add this 1)}}
|
||||||
<input class="resource-box" type="checkbox" data-action="toggleStress" data-value="{{this}}" {{ checked (gte ../../document.system.resources.stress.value this) }} />
|
<input class="resource-box" type="checkbox" data-action="toggleStress" data-value="{{this}}" {{ checked (gte ../../document.system.resources.stress.value this) }} />
|
||||||
{{/with}}
|
{{/with}}
|
||||||
{{/times}}
|
{{/times}}
|
||||||
{{#times (subtract 12 document.system.resources.stress.max)}}
|
{{#times (subtract 12 document.system.resources.stress.maxTotal)}}
|
||||||
<input class="resource-box disabled" type="checkbox" disabled />
|
<input class="resource-box disabled" type="checkbox" disabled />
|
||||||
{{/times}}
|
{{/times}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="proficiency-container">
|
<div class="proficiency-container">
|
||||||
<span>{{localize "DAGGERHEART.Sheets.PC.Weapons.ProficiencyTitle"}}</span>
|
<span>{{localize "DAGGERHEART.Sheets.PC.Weapons.ProficiencyTitle"}}</span>
|
||||||
{{#times 6}}
|
{{#times 6}}
|
||||||
<i class="fa-solid fa-circle proficiency-dot {{#if (gt ../proficiency this)}}marked{{/if}}"></i>
|
<i class="fa-solid fa-circle proficiency-dot {{#if (gt ../proficiency.total this)}}marked{{/if}}"></i>
|
||||||
{{/times}}
|
{{/times}}
|
||||||
</div>
|
</div>
|
||||||
<div class="proficiency-container-visual-element"></div>
|
<div class="proficiency-container-visual-element"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<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="component dh-style card-preview-container selectable {{#if this.disabled}}disabled{{/if}}" data-action="selectPreview" data-path="{{this.path}}" data-uuid="{{this.uuid}}" data-is-multiclass="{{this.isMulticlass}}" data-feature-state="{{this.featureState}}" {{#if selected}}data-selected="true"{{/if}}>
|
||||||
<div class="preview-image-outer-container">
|
<div class="preview-image-outer-container">
|
||||||
<img class="preview-image-container" src="{{this.img}}" />
|
<img class="preview-image-container" src="{{this.img}}" />
|
||||||
{{#if this.selected}}
|
{{#if this.selected}}
|
||||||
|
|
@ -7,5 +7,8 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-text-container">{{this.name}}</div>
|
<div class="preview-text-container">
|
||||||
|
<div>{{this.name}}</div>
|
||||||
|
<strong>{{this.featureLabel}}</strong>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -51,7 +51,11 @@
|
||||||
|
|
||||||
<div class="levelup-card-selection domain-cards">
|
<div class="levelup-card-selection domain-cards">
|
||||||
{{#each this.domainCards}}
|
{{#each this.domainCards}}
|
||||||
{{> "systems/daggerheart/templates/components/card-preview.hbs" this }}
|
{{#> "systems/daggerheart/templates/components/card-preview.hbs" this }}
|
||||||
|
{{#each this.emptySubtexts}}
|
||||||
|
<div>{{this}}</div>
|
||||||
|
{{/each}}
|
||||||
|
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,7 +67,7 @@
|
||||||
|
|
||||||
<div class="levelup-card-selection subclass-cards">
|
<div class="levelup-card-selection subclass-cards">
|
||||||
{{#each this.subclassCards}}
|
{{#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 }}
|
{{> "systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs" img=this.img header=this.featureLabel name=this.name path=this.path selected=this.selected uuid=this.uuid isMulticlass=this.isMulticlass featureState=this.featureState disabled=this.disabled }}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -74,7 +78,9 @@
|
||||||
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.multiclass"}}</h3>
|
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.multiclass"}}</h3>
|
||||||
|
|
||||||
<div class="levelup-card-selection multiclass-cards" data-path="{{this.multiclass.path}}" data-tier="{{this.multiclass.tier}}" data-min-cost="{{this.multiclass.minCost}}" data-amount="{{this.multiclass.amount}}" data-value="{{this.multiclass.value}}" data-type="{{this.multiclass.type}}">
|
<div class="levelup-card-selection multiclass-cards" data-path="{{this.multiclass.path}}" data-tier="{{this.multiclass.tier}}" data-min-cost="{{this.multiclass.minCost}}" data-amount="{{this.multiclass.amount}}" data-value="{{this.multiclass.value}}" data-type="{{this.multiclass.type}}">
|
||||||
{{> "systems/daggerheart/templates/components/card-preview.hbs" this.multiclass }}
|
{{#> "systems/daggerheart/templates/components/card-preview.hbs" this.multiclass }}
|
||||||
|
{{this.multiclass.emptySubtext}}
|
||||||
|
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
|
||||||
<div class="levelup-domains-selection-container">
|
<div class="levelup-domains-selection-container">
|
||||||
{{#each this.multiclass.domains}}
|
{{#each this.multiclass.domains}}
|
||||||
<div class="levelup-domain-selection-container {{#if this.disabled}}disabled{{/if}}" {{#if (not this.disabled)}}data-action="selectDomain" data-uuid="{{../multiclass.uuid}}" data-domain="{{this.id}}" data-path="{{../multiclass.path}}" {{/if}}>
|
<div class="levelup-domain-selection-container {{#if this.disabled}}disabled{{/if}}" {{#if (not this.disabled)}}data-action="selectDomain" data-uuid="{{../multiclass.uuid}}" data-domain="{{this.id}}" data-path="{{../multiclass.path}}" {{/if}}>
|
||||||
|
|
@ -88,6 +94,20 @@
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="levelup-domains-selection-container">
|
||||||
|
{{#each this.multiclass.subclasses}}
|
||||||
|
<div class="levelup-domain-selection-container {{#if this.disabled}}disabled{{/if}}" {{#if (not this.disabled)}}data-action="selectSubclass" data-uuid="{{../multiclass.uuid}}" data-subclass="{{this.uuid}}" data-path="{{../multiclass.path}}" {{/if}}>
|
||||||
|
<div class="levelup-domain-label">{{localize this.name}}</div>
|
||||||
|
<img src="{{this.img}}" />
|
||||||
|
{{#if this.selected}}
|
||||||
|
<div class="levelup-domain-selected">
|
||||||
|
<i class="fa-solid fa-check"></i>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
||||||
|
|
@ -95,11 +95,14 @@
|
||||||
{{#if this.advancements.traits}}
|
{{#if this.advancements.traits}}
|
||||||
<div>
|
<div>
|
||||||
<h5>{{localize "DAGGERHEART.Application.LevelUp.summary.traits"}}</h5>
|
<h5>{{localize "DAGGERHEART.Application.LevelUp.summary.traits"}}</h5>
|
||||||
<div class="summary-selection-container">
|
|
||||||
{{#each this.advancements.traits}}
|
{{#each this.advancements.traits}}
|
||||||
<div class="summary-selection">{{this}}</div>
|
<div class="increase-container">
|
||||||
|
{{this.label}}: {{this.old}}
|
||||||
|
<i class="fa-solid fa-arrow-right-long"></i>
|
||||||
|
{{this.new}}
|
||||||
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
@ -124,6 +127,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.advancements.subclass}}
|
||||||
|
<div>
|
||||||
|
<h5>{{localize "DAGGERHEART.Application.LevelUp.summary.subclass"}}</h5>
|
||||||
|
<div class="summary-selection-container">
|
||||||
|
{{#each this.advancements.subclass}}
|
||||||
|
<div class="summary-selection">{{this.name}} - {{this.featureLabel}}</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.advancements.multiclass}}
|
||||||
|
{{#with this.advancements.multiclass}}
|
||||||
|
<div>
|
||||||
|
<h5>{{localize "DAGGERHEART.Application.LevelUp.summary.multiclass"}}</h5>
|
||||||
|
<div class="summary-selection-container">
|
||||||
|
<div class="summary-selection">{{this.name}}</div>
|
||||||
|
<div class="summary-selection">{{this.domain}}</div>
|
||||||
|
<div class="summary-selection">{{this.subclass}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/with}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue