Added DomainCard selection

This commit is contained in:
WBHarry 2025-05-30 14:16:15 +02:00
parent 07c533c82c
commit 66defbffce
18 changed files with 823 additions and 132 deletions

View file

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

View file

@ -142,6 +142,12 @@
"Or": "Or",
"Description": "Description",
"Features": "Features",
"proficiency": "Proficiency",
"unarmored": "Unarmored",
"Experience": {
"Single": "Experience",
"plural": "Experiences"
},
"RefreshType": {
"Session": "Session",
"Shortrest": "Short Rest",
@ -744,14 +750,38 @@
"TakeDowntime": "Take Downtime"
},
"LevelUp": {
"Title": "{actor} Level Up",
"Tabs": {
"advancement": "Level Advancement",
"selections": "Advancement Choices",
"summary": "Summary"
},
"AdvanceLevel": "Continue To Level {level}",
"TakeLevelUp": "Finish Level Up",
"Selections": {
"emptyDomainCardHint": "Domain Card Level {level} or below"
},
"summary": {
"levelAchievements": "Level Achievements",
"levelAdvancements": "Level Advancements",
"proficiencyIncrease": "Proficiency Increased: {proficiency}",
"statisticIncreases": "Statistic Increases",
"damageThresholdMajorIncrease": "Major: {threshold}",
"damageThresholdSevereIncrease": "Severe: {threshold}",
"newExperiences": "New Experiences",
"experiencePlaceholder": "A new experience..",
"domainCards": "Domain Cards"
},
"notifications": {
"info": {
"tierAdvancementInfo": "Advancements from a higher tier can always be used to select advancements in a lower tier.",
"remainingAdvancementInfo": "Remaining Choices: {choices}",
"insufficentAdvancements": "You don't have enough advancements left.",
"insufficientTierAdvancements": "You have no available advancements for this tier."
},
"error": {
"domainCardWrongDomain": "You don't have access to that Domain",
"domainCardToHighLevel": "The Domain Card is too high level to be selected"
}
}
},

View file

@ -11,13 +11,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
const playerLevelupData = actor.system.levelData;
this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData, actor.system.level));
this._dragDrop = this._createDragDropHandlers();
}
get title() {
return `${this.actor.name} - Level Up`;
return game.i18n.format('DAGGERHEART.Application.LevelUp.Title', { actor: this.actor.name });
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'levelup'],
position: { width: 1000, height: 'auto' },
window: {
@ -25,31 +28,188 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
},
actions: {
save: this.save
}
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [{ dragSelector: null, dropSelector: '.levelup-card-selection .card-preview-container' }]
};
static PARTS = {
form: {
id: 'levelup',
template: 'systems/daggerheart/templates/views/levelup.hbs'
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
advancements: { template: 'systems/daggerheart/templates/views/levelup/tabs/advancements.hbs' },
selections: { template: 'systems/daggerheart/templates/views/levelup/tabs/selections.hbs' },
summary: { template: 'systems/daggerheart/templates/views/levelup/tabs/summary.hbs' }
};
static TABS = {
advancements: {
active: true,
cssClass: '',
group: 'primary',
id: 'advancements',
icon: null,
label: 'DAGGERHEART.Application.LevelUp.Tabs.advancement'
},
selections: {
active: false,
cssClass: '',
group: 'primary',
id: 'selections',
icon: null,
label: 'DAGGERHEART.Application.LevelUp.Tabs.selections'
},
summary: {
active: false,
cssClass: '',
group: 'primary',
id: 'summary',
icon: null,
label: 'DAGGERHEART.Application.LevelUp.Tabs.summary'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.levelup = this.levelup;
context.tabs = this._getTabs(this.constructor.TABS);
return context;
}
async _preparePartContext(partId, context) {
switch (partId) {
case 'selections':
context.advancementChoices = this.levelup.selectionData.reduce((acc, data) => {
const advancementChoice = {
...data,
path: `tiers.${data.tier}.levels.${data.level}.optionSelections.${data.optionKey}.${data.checkboxNr}.data`
};
if (acc[data.type]) acc[data.type].push(advancementChoice);
else acc[data.type] = [advancementChoice];
return acc;
}, {});
context.newExperiences = this.levelup.allInitialAchievements.newExperiences;
const allDomainCards = {
...context.advancementChoices.domainCard,
...this.levelup.domainCards
};
const allDomainCardValues = Object.values(allDomainCards);
context.domainCards = [];
for (var domainCard of allDomainCardValues) {
const uuid = domainCard.data ?? domainCard.uuid;
const card = uuid ? await foundry.utils.fromUuid(uuid) : { path: domainCard.path };
context.domainCards.push({
...(card.toObject?.() ?? card),
emptySubtext: game.i18n.format(
'DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint',
{ level: domainCard.level }
),
limit: domainCard.level
});
}
break;
case 'summary':
const actorArmor = this.actor.system.armor;
const { current: currentLevel, changed: changedLevel } = this.actor.system.levelData.level;
context.levelAchievements = {
statisticIncreases: {
proficiency: {
old: this.actor.system.proficiency,
new: this.actor.system.proficiency + this.levelup.allInitialAchievements.proficiency
},
damageThresholds: {
major: {
old: this.actor.system.damageThresholds.major,
new: this.actor.system.damageThresholds.major + changedLevel - currentLevel
},
severe: {
old: this.actor.system.damageThresholds.severe,
new:
this.actor.system.damageThresholds.severe +
(actorArmor ? changedLevel - currentLevel : (changedLevel - currentLevel) * 2)
},
unarmored: !actorArmor
}
}
};
break;
}
return context;
}
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
return tabs;
}
_createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.callbacks = {
drop: this._onDrop.bind(this)
};
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
$(htmlElement).find('.selection-checkbox').on('change', this.selectionClick.bind(this));
htmlElement
.querySelectorAll('.selection-checkbox')
.forEach(element => element.addEventListener('change', this.selectionClick.bind(this)));
this._dragDrop.forEach(d => d.bind(htmlElement));
}
static async updateForm(event, _, formData) {
const { levelup } = foundry.utils.expandObject(formData.object);
await this.levelup.updateSource(levelup);
this.render();
}
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (event.currentTarget.parentElement?.classList?.contains('domain-cards')) {
if (item.type === 'domainCard') {
if (!this.actor.system.class.system.domains.includes(item.system.domain)) {
// Also needs to check for multiclass adding a new domain
ui.notifications.error(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardWrongDomain')
);
return;
}
if (item.system.level > Number(event.currentTarget.dataset.limit)) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel')
);
return;
}
const achievementCard = event.currentTarget.dataset.path.startsWith('domainCards');
await this.levelup.updateSource({ [event.currentTarget.dataset.path]: item.uuid });
this.render();
}
}
}
async selectionClick(event) {
event.stopPropagation();
const button = event.currentTarget;
// const advancementSelections = this.getAdvancementSelectionUpdates(button);
if (!button.checked) {
await this.levelup.updateSource({
[`tiers.${button.dataset.tier}.levels.${button.dataset.level}.optionSelections.${button.dataset.option}.-=${button.dataset.checkboxNr}`]:
@ -101,12 +261,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
}
static async save() {
await this.actor.update({
'system.levelData': {
'level.current': this.actor.system.levelData.level.changed,
'selections': this.levelup.playerData
}
});
await this.actor.levelUp(this.levelup.selectionData);
this.close();
}

View file

@ -182,9 +182,9 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
htmlElement
.querySelectorAll('.experience-value')
.forEach(element => element.addEventListener('change', this.experienceValueChange.bind(this)));
htmlElement
.querySelectorAll('[data-item]')
.forEach(element => element.addEventListener.on('change', this.itemUpdate.bind(this)));
// htmlElement
// .querySelectorAll('[data-item]')
// .forEach(element => element.addEventListener.on('change', this.itemUpdate.bind(this)));
htmlElement.querySelector('.level-value').addEventListener('change', this.onLevelChange.bind(this));
}

View file

@ -61,19 +61,31 @@ class DhLevelOption extends foundry.abstract.DataModel {
export const LevelOptionType = {
trait: {
id: 'trait',
label: 'Character Trait'
label: 'Character Trait',
dataPath: ''
},
hitPoint: {
id: 'hitPoint',
label: 'Hit Points'
label: 'Hit Points',
dataPath: 'resources.hitPoints',
dataPathData: {
property: 'max',
dependencies: ['value']
}
},
stress: {
id: 'stress',
label: 'Stress'
label: 'Stress',
dataPath: 'resources.stress',
dataPathData: {
property: 'max',
dependencies: ['value']
}
},
evasion: {
id: 'evasion',
label: 'Evasion'
label: 'Evasion',
dataPath: 'evasion'
},
proficiency: {
id: 'proficiency',
@ -108,8 +120,8 @@ export const defaultLevelTiers = {
},
initialAchievements: {
experience: {
nr: 2,
modifier: 1
nr: 1,
modifier: 2
},
proficiency: 1
},
@ -171,8 +183,8 @@ export const defaultLevelTiers = {
},
initialAchievements: {
experience: {
nr: 2,
modifier: 1
nr: 1,
modifier: 2
},
proficiency: 1
},
@ -252,8 +264,8 @@ export const defaultLevelTiers = {
},
initialAchievements: {
experience: {
nr: 2,
modifier: 1
nr: 1,
modifier: 2
},
proficiency: 1
},

View file

@ -7,17 +7,63 @@ export class DhLevelup extends foundry.abstract.DataModel {
const tierKeys = Object.keys(levelTierData.tiers);
const maxLevel = levelTierData.tiers[tierKeys[tierKeys.length - 1]].levels.end;
return {
tiers: tierKeys.reduce((acc, key) => {
acc[key] = DhLevelupTier.initializeData(
levelTierData.tiers[key],
maxLevel,
pcLevelData.selections.filter(x => x.tier === Number(key)),
pcLevelData.level.changed
);
const totalLevelProgression = [];
for (var level = pcLevelData.level.current + 1; level <= pcLevelData.level.changed; level++) {
totalLevelProgression.push(level);
}
const tiers = tierKeys.reduce((acc, key) => {
acc[key] = DhLevelupTier.initializeData(
levelTierData.tiers[key],
maxLevel,
pcLevelData.selections.filter(x => x.tier === Number(key)),
pcLevelData.level.changed
);
return acc;
}, {});
const allInitialAchievements = Object.values(tiers).reduce(
(acc, tier) => {
const levelThreshold = Math.min(...tier.belongingLevels);
if (totalLevelProgression.includes(levelThreshold)) {
acc.proficiency += tier.initialAchievements.proficiency;
[...Array(tier.initialAchievements.experience.nr).keys()].forEach(_ => {
acc.newExperiences[foundry.utils.randomID()] = {
name: '',
modifier: tier.initialAchievements.experience.modifier
};
});
}
return acc;
}, {}),
},
{ newExperiences: {}, proficiency: 0 }
);
const domainCards = Object.keys(tiers).reduce((acc, tierKey) => {
const tier = tiers[tierKey];
for (var level of tier.belongingLevels) {
if (level <= pcLevelData.level.changed) {
for (var domainCardSlot = 1; domainCardSlot <= tier.domainCardByLevel; domainCardSlot++) {
const cardId = foundry.utils.randomID();
acc[cardId] = {
uuid: null,
tier: tierKey,
level: level,
domainCardSlot: domainCardSlot,
path: `domainCards.${cardId}.uuid`
};
}
}
}
return acc;
}, {});
return {
tiers: tiers,
maxSelections: [...Array(pcLevelData.level.changed).keys()].reduce((acc, index) => {
const level = index + 1;
const availableChoices = availableChoicesPerLevel[level];
@ -26,7 +72,12 @@ export class DhLevelup extends foundry.abstract.DataModel {
}
return acc;
}, 0)
}, 0),
allInitialAchievements: {
newExperiences: allInitialAchievements.newExperiences,
proficiency: allInitialAchievements.proficiency
},
domainCards: domainCards
};
}
@ -35,7 +86,28 @@ export class DhLevelup extends foundry.abstract.DataModel {
return {
tiers: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupTier)),
maxSelections: new fields.NumberField({ required: true, integer: true })
maxSelections: new fields.NumberField({ required: true, integer: true }),
allInitialAchievements: new fields.SchemaField({
newExperiences: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
modifier: new fields.NumberField({ required: true, integer: true })
})
),
proficiency: new fields.NumberField({ required: true, integer: true })
}),
domainCards: new fields.TypedObjectField(
new fields.SchemaField({
uuid: new fields.StringField({ required: true, nullable: true, initial: null }),
tier: new fields.NumberField({ required: true, integer: true }),
level: new fields.NumberField({ required: true, integer: true }),
domainCardSlot: new fields.NumberField({ required: true, integer: true }),
path: new fields.StringField({ required: true })
})
)
// advancementSelections: new fields.SchemaField({
// experiences: new fields.SetField(new fields.StringField()),
// }),
};
}
@ -57,7 +129,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
);
}
get playerData() {
get selectionData() {
return Object.keys(this.tiers).flatMap(tierKey => {
const tier = this.tiers[tierKey];
return Object.keys(tier.levels).flatMap(levelKey => {
@ -66,15 +138,19 @@ export class DhLevelup extends foundry.abstract.DataModel {
const selection = level.optionSelections[optionSelectionKey];
const optionSelect = tier.options[optionSelectionKey];
return Object.keys(selection).map(checkboxNr => ({
tier: Number(tierKey),
level: Number(levelKey),
optionKey: optionSelectionKey,
type: optionSelect.type,
checkboxNr: Number(checkboxNr),
value: optionSelect.value,
amount: optionSelect.amount
}));
return Object.keys(selection).map(checkboxNr => {
const selectionObj = selection[checkboxNr];
return {
tier: Number(tierKey),
level: Number(levelKey),
optionKey: optionSelectionKey,
type: optionSelect.type,
checkboxNr: Number(checkboxNr),
value: optionSelect.value,
amount: optionSelect.amount,
data: selectionObj.data
};
});
});
});
});
@ -108,6 +184,8 @@ class DhLevelupTier extends foundry.abstract.DataModel {
return acc;
}, {}),
belongingLevels: belongingLevels,
initialAchievements: levelTier.initialAchievements,
domainCardByLevel: levelTier.domainCardByLevel,
levels: levels
};
}
@ -121,10 +199,22 @@ class DhLevelupTier extends foundry.abstract.DataModel {
active: new fields.BooleanField({ required: true, initial: true }),
options: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupTierOption)),
belongingLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })),
initialAchievements: new fields.SchemaField({
experience: new fields.SchemaField({
nr: new fields.NumberField({ required: true, initial: 1 }),
modifier: new fields.NumberField({ required: true, initial: 2 })
}),
proficiency: new fields.NumberField({ integer: true, initial: 1 })
}),
domainCardByLevel: new fields.NumberField({ required: true, integer: true }),
levels: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupLevel))
};
}
get initialAchievementData() {
return this.active ? this.initialAchievements : null;
}
get selections() {
const allSelections = Object.keys(this.levels).reduce(
(acc, key) => {
@ -223,7 +313,8 @@ class DhLevelupLevel extends foundry.abstract.DataModel {
new fields.SchemaField({
selected: new fields.BooleanField({ required: true, initial: true }),
minCost: new fields.NumberField({ required: true, integer: true }),
locked: new fields.BooleanField({ required: true, initial: false })
locked: new fields.BooleanField({ required: true, initial: false }),
data: new fields.StringField()
})
)
)

View file

@ -7,10 +7,7 @@ const attributeField = () =>
new fields.SchemaField({
data: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
base: new fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
actualValue: new fields.NumberField({ initial: 0, integer: true }),
overrideValue: new fields.NumberField({ initial: 0, integer: true })
bonus: new fields.NumberField({ initial: 0, integer: true })
})
});
@ -52,11 +49,7 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
presence: attributeField(),
knowledge: attributeField()
}),
proficiency: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true }),
min: new fields.NumberField({ initial: 1, integer: true }),
max: new fields.NumberField({ initial: 6, integer: true })
}),
proficiency: new fields.NumberField({ required: true, initial: 1, integer: true }),
evasion: new fields.NumberField({ initial: 0, integer: true }),
experiences: new fields.ArrayField(
new fields.SchemaField({
@ -347,7 +340,15 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
this.evasion = this.class?.system?.evasion ?? 0;
// this.armor.value = this.activeArmor?.baseScore ?? 0;
// this.damageThresholds = this.computeDamageThresholds();
const armor = this.armor;
this.damageThresholds = {
major: armor
? armor.system.baseThresholds.major + this.levelData.level.current
: this.levelData.level.current,
severe: armor
? armor.system.baseThresholds.severe + this.levelData.level.current
: this.levelData.level.current * 2
};
this.applyLevels();
this.applyEffects();

View file

@ -45,6 +45,15 @@ export default class DhpActor extends Actor {
}
}
async levelUp(levelupData) {
await this.actor.update({
'system.levelData': {
'level.current': this.system.levelData.level.changed,
'selections': levelupData
}
});
}
async diceRoll(modifier, shiftKey) {
if (this.type === 'pc') {
return await this.dualityRoll(modifier, shiftKey);

View file

@ -3,22 +3,19 @@ import { getWidthOfText } from './utils.mjs';
export default class RegisterHandlebarsHelpers {
static registerHelpers() {
Handlebars.registerHelper({
looseEq: this.looseEq,
times: this.times,
join: this.join,
add: this.add,
subtract: this.subtract,
objectSelector: this.objectSelector,
includes: this.includes,
simpleEditor: this.simpleEditor,
debug: this.debug
debug: this.debug,
signedNumber: this.signedNumber,
switch: this.switch,
case: this.case
});
}
static looseEq(a, b) {
return a == b;
}
static times(nr, block) {
var accum = '';
for (var i = 0; i < nr; ++i) accum += block.fn(i);
@ -77,33 +74,25 @@ export default class RegisterHandlebarsHelpers {
return new Handlebars.SafeString(html);
}
static rangePicker(options) {
let { name, value, min, max, step } = options.hash;
name = name || 'range';
value = value ?? '';
if (Number.isNaN(value)) value = '';
const html = `<input type="range" name="${name}" value="${value}" min="${min}" max="${max}" step="${step}"/>
<span class="range-value">${value}</span>`;
return new Handlebars.SafeString(html);
}
static includes(list, item) {
return list.includes(item);
}
static simpleEditor(content, options) {
const {
target,
editable = true,
button,
engine = 'tinymce',
collaborate = false,
class: cssClass
} = options.hash;
const config = { name: target, value: content, button, collaborate, editable, engine };
const element = foundry.applications.fields.createEditorInput(config);
if (cssClass) element.querySelector('.editor-content').classList.add(cssClass);
return new Handlebars.SafeString(element.outerHTML);
static signedNumber(number) {
return number >= 0 ? `+${number}` : number;
}
static switch(value, options) {
this.switch_value = value;
this.switch_break = false;
return options.fn(this);
}
static case(value, options) {
if (value == this.switch_value) {
this.switch_break = true;
return options.fn(this);
}
}
static debug(a) {

View file

@ -2791,6 +2791,11 @@ div.daggerheart.views.multiclass {
flex-direction: column;
gap: 8px;
}
.daggerheart.levelup section .section-container {
display: flex;
flex-direction: column;
gap: 8px;
}
.daggerheart.levelup .tiers-container {
display: flex;
gap: 16px;
@ -2844,9 +2849,75 @@ div.daggerheart.views.multiclass {
font-size: 14px;
font-style: italic;
}
.daggerheart.levelup .levelup-selections-container .achievement-experience-cards {
display: flex;
gap: 8px;
}
.daggerheart.levelup .levelup-selections-container .achievement-experience-cards .achievement-experience-card {
border: 1px solid;
border-radius: 4px;
padding-right: 4px;
font-size: 18px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 4px;
}
.daggerheart.levelup .levelup-selections-container .achievement-experience-cards .achievement-experience-card .achievement-experience-marker {
border: 1px solid;
border-radius: 50%;
height: 18px;
width: 18px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.daggerheart.levelup .levelup-selections-container .levelup-card-selection {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 40px;
padding-right: 120px;
}
.daggerheart.levelup .levelup-summary-container .level-achievements-container {
display: flex;
flex-direction: column;
gap: 8px;
}
.daggerheart.levelup .levelup-summary-container .level-achievements-container h2,
.daggerheart.levelup .levelup-summary-container .level-achievements-container h3,
.daggerheart.levelup .levelup-summary-container .level-achievements-container h4,
.daggerheart.levelup .levelup-summary-container .level-achievements-container h5 {
margin: 0;
color: var(--color-text-secondary);
}
.daggerheart.levelup .levelup-summary-container .increase-container {
display: flex;
align-items: center;
gap: 4px;
font-size: 20px;
}
.daggerheart.levelup .levelup-footer {
display: flex;
}
.daggerheart.levelup .levelup-footer .advancement-information-container {
display: flex;
gap: 8px;
border-bottom: 3px solid;
border-radius: 4px;
margin-left: 8px;
align-items: center;
padding: 0 8px;
border-color: #9f8475;
}
.daggerheart.levelup .levelup-footer .advancement-information-container .advancement-tier-stats {
border: 1px solid;
padding: 0 2px;
border-radius: 6px;
}
.daggerheart.levelup .levelup-footer .advancement-information-container .advancement-tier-info {
font-size: 14px;
}
.application.sheet.daggerheart.dh-style.feature .item-sheet-header {
display: flex;
}
@ -2892,14 +2963,14 @@ div.daggerheart.views.multiclass {
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserrat/v29/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew-.ttf) format('truetype');
src: url(https://fonts.gstatic.com/s/montserrat/v30/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew-.ttf) format('truetype');
}
@font-face {
font-family: 'Montserrat';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserrat/v29/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCu170w-.ttf) format('truetype');
src: url(https://fonts.gstatic.com/s/montserrat/v30/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCu170w-.ttf) format('truetype');
}
.application.sheet.daggerheart.dh-style h1 {
font-family: 'Cinzel Decorative', serif;
@ -3167,6 +3238,60 @@ div.daggerheart.views.multiclass {
.application.setting.dh-style footer button {
flex: 1;
}
.theme-light .application .component.dh-style.card.card-preview-container {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
.theme-light .application .component.dh-style.card.card-preview-container .preview-text-container {
background-image: url(../assets/parchments/dh-parchment-dark.png);
}
.application .component.dh-style.card-preview-container {
border-radius: 6px;
border: 2px solid var(--color-tabs-border);
display: flex;
flex-direction: column;
aspect-ratio: 0.75;
background-image: url('../assets/parchments/dh-parchment-dark.png');
}
.application .component.dh-style.card-preview-container.empty {
cursor: pointer;
}
.application .component.dh-style.card-preview-container .preview-image-container {
flex: 1;
border-radius: 4px 4px 0;
}
.application .component.dh-style.card-preview-container .preview-text-container {
flex: 1;
border-radius: 0 0 4px 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
text-align: center;
color: var(--color-text-selection-bg);
background-image: url(../assets/parchments/dh-parchment-light.png);
}
.application .component.dh-style.card-preview-container .preview-empty-container {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
}
.application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container {
position: relative;
width: 100%;
display: flex;
justify-content: center;
}
.application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container .preview-add-icon {
font-size: 48px;
}
.application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container .preview-empty-subtext {
position: absolute;
bottom: -48px;
font-size: 18px;
font-variant: small-caps;
text-align: center;
}
.sheet.daggerheart.dh-style .tab-navigation {
margin: 5px 0;
height: 40px;
@ -3399,6 +3524,12 @@ div.daggerheart.views.multiclass {
/* Flex */
/****/
}
.daggerheart .vertical-separator {
border-left: 2px solid black;
height: 56px;
flex: 0;
align-self: center;
}
.daggerheart .flex-centered {
display: flex;
align-items: center;

View file

@ -212,3 +212,77 @@
}
}
}
.theme-light {
.application {
.component.dh-style.card {
&.card-preview-container {
background-image: url('../assets/parchments/dh-parchment-light.png');
.preview-text-container {
background-image: url(../assets/parchments/dh-parchment-dark.png);
}
}
}
}
}
.application {
.component.dh-style {
&.card-preview-container {
border-radius: 6px;
border: 2px solid var(--color-tabs-border);
display: flex;
flex-direction: column;
aspect-ratio: 0.75;
background-image: url('../assets/parchments/dh-parchment-dark.png');
&.empty {
cursor: pointer;
}
.preview-image-container {
flex: 1;
border-radius: 4px 4px 0;
}
.preview-text-container {
flex: 1;
border-radius: 0 0 4px 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
text-align: center;
color: var(--color-text-selection-bg);
background-image: url(../assets/parchments/dh-parchment-light.png);
}
.preview-empty-container {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
.preview-empty-inner-container {
position: relative;
width: 100%;
display: flex;
justify-content: center;
.preview-add-icon {
font-size: 48px;
}
.preview-empty-subtext {
position: absolute;
bottom: -48px;
font-size: 18px;
font-variant: small-caps;
text-align: center;
}
}
}
}
}
}

View file

@ -9,6 +9,14 @@
gap: 8px;
}
section {
.section-container {
display: flex;
flex-direction: column;
gap: 8px;
}
}
.tiers-container {
display: flex;
gap: 16px;
@ -73,13 +81,72 @@
}
}
.levelup-selections-container {
.achievement-experience-cards {
display: flex;
gap: 8px;
.achievement-experience-card {
border: 1px solid;
border-radius: 4px;
padding-right: 4px;
font-size: 18px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 4px;
.achievement-experience-marker {
border: 1px solid;
border-radius: 50%;
height: 18px;
width: 18px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
}
}
.levelup-card-selection {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 40px;
padding-right: 120px;
}
}
.levelup-summary-container {
.level-achievements-container {
display: flex;
flex-direction: column;
gap: 8px;
h2,
h3,
h4,
h5 {
margin: 0;
color: var(--color-text-secondary);
}
}
.increase-container {
display: flex;
align-items: center;
gap: 4px;
font-size: 20px;
}
}
.levelup-footer {
display: flex;
.advancement-information-container {
display: flex;
gap: 8px;
border: 1px solid;
border-bottom: 3px solid;
border-radius: 4px;
margin-left: 8px;
align-items: center;

View file

@ -0,0 +1,13 @@
<div class="component dh-style card-preview-container" data-path="{{this.path}}" data-limit="{{this.limit}}">
{{#if this.img}}
<img class="preview-image-container" src="{{this.img}}" />
<div class="preview-text-container">{{this.name}}</div>
{{else}}
<div class="preview-empty-container">
<div class="preview-empty-inner-container">
<i class="preview-add-icon fa-solid fa-plus"></i>
<div class="preview-empty-subtext">{{this.emptySubtext}}</div>
</div>
</div>
{{/if}}
</div>

View file

@ -1,42 +0,0 @@
<div>
<div class="tiers-container">
{{#each this.levelup.tiers as |tier key|}}
<fieldset class="tier-container {{#if (not tier.active)}}inactive{{/if}}">
<legend>{{tier.name}}</legend>
{{#each tier.tierCheckboxGroups}}
<div class="checkbox-group-container">
<div class="checkboxes-container">
{{#each this.checkboxGroups}}
<div class="checkbox-grouping-coontainer {{#if this.multi}}multi{{/if}}">
{{#each this.checkboxes}}
<input
type="checkbox" class="selection-checkbox{{#if (gt this.cost 1)}} multi{{/if}}" {{checked this.selected}} {{#if this.disabled}}disabled{{/if}}
data-tier="{{this.tier}}"
data-level="{{this.level}}"
data-option="{{this.optionKey}}"
data-checkbox-nr="{{this.checkboxNr}}"
data-cost="{{this.cost}}"
/>
{{/each}}
</div>
{{/each}}
</div>
<div class="checkbox-group-label">{{this.label}}</div>
</div>
{{/each}}
</fieldset>
{{/each}}
</div>
<footer class="levelup-footer">
<button data-action="save">{{localize "Finish Levelup"}}</button>
<div class="advancement-information-container">
{{localize "DAGGERHEART.Application.LevelUp.notifications.info.remainingAdvancementInfo" choices=this.levelup.levelSelections.totalAvailable}}
{{#each this.levelup.tiers as |tier key|}}
<div class="advancement-tier-stats">Tier {{tier.tier}}: {{tier.selections.totalAvailable}}</div>
{{/each}}
<i class="advancement-tier-info fa-solid fa-circle-question" data-tooltip="{{localize "DAGGERHEART.Application.LevelUp.notifications.info.tierAdvancementInfo"}}"></i>
</div>
</footer>
</div>

View file

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

View file

@ -0,0 +1,37 @@
<section
class='tab {{tabs.advancements.cssClass}} {{tabs.advancements.id}}'
data-tab='{{tabs.advancements.id}}'
data-group='{{tabs.advancements.group}}'
>
<div class="section-container">
<div class="tiers-container">
{{#each this.levelup.tiers as |tier key|}}
<fieldset class="tier-container {{#if (not tier.active)}}inactive{{/if}}">
<legend>{{tier.name}}</legend>
{{#each tier.tierCheckboxGroups}}
<div class="checkbox-group-container">
<div class="checkboxes-container">
{{#each this.checkboxGroups}}
<div class="checkbox-grouping-coontainer {{#if this.multi}}multi{{/if}}">
{{#each this.checkboxes}}
<input
type="checkbox" class="selection-checkbox{{#if (gt this.cost 1)}} multi{{/if}}" {{checked this.selected}} {{#if this.disabled}}disabled{{/if}}
data-tier="{{this.tier}}"
data-level="{{this.level}}"
data-option="{{this.optionKey}}"
data-checkbox-nr="{{this.checkboxNr}}"
data-cost="{{this.cost}}"
/>
{{/each}}
</div>
{{/each}}
</div>
<div class="checkbox-group-label">{{this.label}}</div>
</div>
{{/each}}
</fieldset>
{{/each}}
</div>
</div>
</section>

View file

@ -0,0 +1,34 @@
<section
class='tab {{tabs.selections.cssClass}} {{tabs.selections.id}}'
data-tab='{{tabs.selections.id}}'
data-group='{{tabs.selections.group}}'
>
<div class="section-container levelup-selections-container">
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.newExperiences"}}</h3>
<div class="achievement-experience-cards">
{{#each this.newExperiences as |experience key|}}
<div class="achievement-experience-card">
<div class="flexrow">
<input type="text" name="{{concat "levelup.allInitialAchievements.newExperiences." key ".name"}}" value="{{experience.name}}" placeholder="{{localize "DAGGERHEART.Application.LevelUp.summary.experiencePlaceholder"}}" />
<div class="flex0">{{signedNumber experience.modifier}}</div>
</div>
<div class="achievement-experience-marker">
{{#if experience.name}}<i class="fa-solid fa-check"></i>{{/if}}
</div>
</div>
{{/each}}
</div>
</div>
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.domainCards"}}</h3>
<div class="levelup-card-selection domain-cards">
{{#each this.domainCards}}
{{> "systems/daggerheart/templates/components/card-preview.hbs" img=this.img name=this.name path=this.path }}
{{/each}}
</div>
</div>
</div>
</section>

View file

@ -0,0 +1,60 @@
<section
class='tab {{tabs.summary.cssClass}} {{tabs.summary.id}}'
data-tab='{{tabs.summary.id}}'
data-group='{{tabs.summary.group}}'
>
<div class="section-container levelup-summary-container">
<fieldset>
<legend>{{localize "DAGGERHEART.Application.LevelUp.summary.levelAchievements"}}</legend>
<div class="level-achievements-container">
<div>
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.statisticIncreases"}}</h3>
<div class="increase-container">
{{localize "DAGGERHEART.Application.LevelUp.summary.proficiencyIncrease" proficiency=this.levelAchievements.statisticIncreases.proficiency.old }}
<i class="fa-solid fa-arrow-right-long"></i>
{{this.levelAchievements.statisticIncreases.proficiency.new}}
</div>
<h5>{{localize "Damage Thresholds"}}{{#if this.levelAchievements.statisticIncreases.damageThresholds.unarmored}}({{localize "DAGGERHEART.General.unarmored"}}){{/if}}</h5>
<div class="increase-container">
{{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdMajorIncrease" threshold=this.levelAchievements.statisticIncreases.damageThresholds.major.old }}
<i class="fa-solid fa-arrow-right-long"></i>
{{this.levelAchievements.statisticIncreases.damageThresholds.major.new}}
</div>
<div class="increase-container">
{{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdSevereIncrease" threshold=this.levelAchievements.statisticIncreases.damageThresholds.severe.old }}
<i class="fa-solid fa-arrow-right-long"></i>
{{this.levelAchievements.statisticIncreases.damageThresholds.severe.new}}
</div>
</div>
</div>
</fieldset>
<fieldset>
<legend>{{localize "DAGGERHEART.Application.LevelUp.summary.levelAdvancements"}}</legend>
{{!-- <div>
<h4>{{localize "DAGGERHEART.General.Experience.plural"}}</h4>
<div class="level-experience-cards">
{{#each this.levelAchievements.experience}}
<div class="level-experience-card">
{{this.label}}
</div>
{{/each}}
</div>
</div> --}}
</fieldset>
<footer class="levelup-footer">
<button data-action="save">{{localize "Finish Levelup"}}</button>
<div class="advancement-information-container">
{{localize "DAGGERHEART.Application.LevelUp.notifications.info.remainingAdvancementInfo" choices=this.levelup.levelSelections.totalAvailable}}
{{#each this.levelup.tiers as |tier key|}}
<div class="advancement-tier-stats">Tier {{tier.tier}}: {{tier.selections.totalAvailable}}</div>
{{/each}}
<i class="advancement-tier-info fa-solid fa-circle-question" data-tooltip="{{localize "DAGGERHEART.Application.LevelUp.notifications.info.tierAdvancementInfo"}}"></i>
</div>
</footer>
</div>
</section>