[Feature] 1383 - Companion Bonus Levelups (#1565)

* Fixed so that companions can get bonus levelupchoices from their partner

* Fixed collection prep order

* Added ActiveEffects to Beastbound features

* Corrected styling

* Added migration for overleveled companions

* Raised version

* Moved migration to 1.6.0. Sillyness
This commit is contained in:
WBHarry 2026-01-25 15:21:06 +01:00 committed by GitHub
parent c42f876d4f
commit ce96ffa0a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 551 additions and 18 deletions

View file

@ -3,6 +3,7 @@ import * as applications from './module/applications/_module.mjs';
import * as data from './module/data/_module.mjs'; import * as data from './module/data/_module.mjs';
import * as models from './module/data/_module.mjs'; import * as models from './module/data/_module.mjs';
import * as documents from './module/documents/_module.mjs'; import * as documents from './module/documents/_module.mjs';
import * as collections from './module/documents/collections/_module.mjs';
import * as dice from './module/dice/_module.mjs'; import * as dice from './module/dice/_module.mjs';
import * as fields from './module/data/fields/_module.mjs'; import * as fields from './module/data/fields/_module.mjs';
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
@ -36,6 +37,7 @@ CONFIG.Dice.daggerheart = {
CONFIG.Actor.documentClass = documents.DhpActor; CONFIG.Actor.documentClass = documents.DhpActor;
CONFIG.Actor.dataModels = models.actors.config; CONFIG.Actor.dataModels = models.actors.config;
CONFIG.Actor.collection = collections.DhActorCollection;
CONFIG.Item.documentClass = documents.DHItem; CONFIG.Item.documentClass = documents.DHItem;
CONFIG.Item.dataModels = models.items.config; CONFIG.Item.dataModels = models.items.config;

View file

@ -1,6 +1,6 @@
import BaseLevelUp from './levelup.mjs'; import BaseLevelUp from './levelup.mjs';
import { defaultCompanionTier, LevelOptionType } from '../../data/levelTier.mjs'; import { defaultCompanionTier, LevelOptionType } from '../../data/levelTier.mjs';
import { DhLevelup } from '../../data/levelup.mjs'; import { DhCompanionLevelup as DhLevelup } from '../../data/companionLevelup.mjs';
import { diceTypes, range } from '../../config/generalConfig.mjs'; import { diceTypes, range } from '../../config/generalConfig.mjs';
export default class DhCompanionLevelUp extends BaseLevelUp { export default class DhCompanionLevelUp extends BaseLevelUp {
@ -9,7 +9,9 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
this.levelTiers = this.addBonusChoices(defaultCompanionTier); this.levelTiers = this.addBonusChoices(defaultCompanionTier);
const playerLevelupData = actor.system.levelData; const playerLevelupData = actor.system.levelData;
this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData)); this.levelup = new DhLevelup(
DhLevelup.initializeData(this.levelTiers, playerLevelupData, actor.system.levelupChoicesLeft)
);
} }
async _preparePartContext(partId, context) { async _preparePartContext(partId, context) {

View file

@ -38,15 +38,6 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
} }
}; };
/** @inheritDoc */
async _onRender(context, options) {
await super._onRender(context, options);
this.element
.querySelector('.level-value')
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Application Clicks Actions */ /* Application Clicks Actions */
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -662,6 +662,11 @@ export default class DhCharacter extends BaseDataActor {
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope; const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
this.resources.hope.max = globalHopeMax - this.scars; this.resources.hope.max = globalHopeMax - this.scars;
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0; this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
/* Companion Related Data */
this.companionData = {
levelupChoices: this.levelData.level.current - 1
};
} }
prepareDerivedData() { prepareDerivedData() {
@ -733,6 +738,16 @@ export default class DhCharacter extends BaseDataActor {
}; };
} }
} }
/* Force companion data prep */
if (this.companion) {
if (
changes.system?.levelData?.level?.current !== undefined &&
changes.system.levelData.level.current !== this._source.levelData.level.current
) {
this.companion.update(this.companion.toObject(), { diff: false, recursive: false });
}
}
} }
async _preDelete() { async _preDelete() {

View file

@ -109,6 +109,10 @@ export default class DhCompanion extends BaseDataActor {
return this.partner?.system?.proficiency ?? 1; return this.partner?.system?.proficiency ?? 1;
} }
get canLevelUp() {
return this.levelupChoicesLeft > 0;
}
isItemValid() { isItemValid() {
return false; return false;
} }
@ -147,6 +151,17 @@ export default class DhCompanion extends BaseDataActor {
} }
} }
prepareDerivedData() {
/* Partner Related Setup */
if (this.partner) {
this.levelData.level.changed = this.partner.system.levelData.level.current;
this.levelupChoicesLeft = Object.values(this.levelData.levelups).reduce((acc, curr) => {
acc = Math.max(acc - curr.selections.length, 0);
return acc;
}, this.partner.system.companionData.levelupChoices);
}
}
async _preUpdate(changes, options, userId) { async _preUpdate(changes, options, userId) {
const allowed = await super._preUpdate(changes, options, userId); const allowed = await super._preUpdate(changes, options, userId);
if (allowed === false) return; if (allowed === false) return;
@ -162,6 +177,16 @@ export default class DhCompanion extends BaseDataActor {
changes.system.experiences[experience].core = true; changes.system.experiences[experience].core = true;
} }
} }
/* Force partner data prep */
if (this.partner) {
if (
changes.system?.levelData?.level?.current !== undefined &&
changes.system.levelData.level.current !== this._source.levelData.level.current
) {
this.partner.update(this.partner.toObject(), { diff: false, recursive: false });
}
}
} }
async _preDelete() { async _preDelete() {

View file

@ -0,0 +1,370 @@
import { abilities } from '../config/actorConfig.mjs';
import { chunkify } from '../helpers/utils.mjs';
import { LevelOptionType } from './levelTier.mjs';
export class DhCompanionLevelup extends foundry.abstract.DataModel {
static initializeData(levelTierData, pcLevelData, origChoicesLeft) {
let choicesLeft = origChoicesLeft;
const { current, changed } = pcLevelData.level;
const bonusChoicesOnly = current === changed;
const startLevel = bonusChoicesOnly ? current : current + 1;
const endLevel = bonusChoicesOnly ? startLevel : changed;
const tiers = {};
const levels = {};
const tierKeys = Object.keys(levelTierData.tiers);
tierKeys.forEach(key => {
const tier = levelTierData.tiers[key];
const belongingLevels = [];
for (var i = tier.levels.start; i <= tier.levels.end; i++) {
if (i <= endLevel) {
const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {};
const experiences = initialAchievements.experience
? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => {
acc[foundry.utils.randomID()] = {
name: '',
modifier: initialAchievements.experience.modifier
};
return acc;
}, {})
: {};
const currentChoices = pcLevelData.levelups[i]?.selections?.length;
const maxSelections =
i === endLevel
? choicesLeft + (currentChoices ?? 0)
: (currentChoices ?? tier.maxSelections[i]);
if (!pcLevelData.levelups[i]) choicesLeft -= maxSelections;
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], maxSelections, {
...initialAchievements,
experiences,
domainCards: {}
});
}
belongingLevels.push(i);
}
/* Improve. Temporary handling for Companion new experiences */
Object.keys(tier.extraAchievements ?? {}).forEach(key => {
const level = Number(key);
if (level >= startLevel && level <= endLevel) {
const levelExtras = tier.extraAchievements[level];
if (levelExtras.experience) {
levels[level].achievements.experiences[foundry.utils.randomID()] = {
name: '',
modifier: levelExtras.experience.modifier
};
}
}
});
tiers[key] = {
name: tier.name,
belongingLevels: belongingLevels,
options: Object.keys(tier.options).reduce((acc, key) => {
acc[key] = tier.options[key].toObject?.() ?? tier.options[key];
return acc;
}, {})
};
});
return {
tiers,
levels,
startLevel,
currentLevel: startLevel,
endLevel
};
}
static defineSchema() {
const fields = foundry.data.fields;
return {
tiers: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
belongingLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })),
options: new fields.TypedObjectField(
new fields.SchemaField({
label: new fields.StringField({ required: true }),
checkboxSelections: new fields.NumberField({ required: true, integer: true }),
minCost: new fields.NumberField({ required: true, integer: true }),
type: new fields.StringField({ required: true, choices: LevelOptionType }),
value: new fields.NumberField({ integer: true }),
amount: new fields.NumberField({ integer: true })
})
)
})
),
levels: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupLevel)),
startLevel: new fields.NumberField({ required: true, integer: true }),
currentLevel: new fields.NumberField({ required: true, integer: true }),
endLevel: new fields.NumberField({ required: true, integer: true })
};
}
#levelFinished(levelKey) {
const allSelectionsMade = this.levels[levelKey].nrSelections.available === 0;
const allChoicesMade = Object.keys(this.levels[levelKey].choices).every(choiceKey => {
const choice = this.levels[levelKey].choices[choiceKey];
return Object.values(choice).every(checkbox => {
switch (choiceKey) {
case 'trait':
case 'experience':
case 'domainCard':
case 'subclass':
case 'vicious':
return checkbox.data.length === (checkbox.amount ?? 1);
case 'multiclass':
const classSelected = checkbox.data.length === 1;
const domainSelected = checkbox.secondaryData.domain;
const subclassSelected = checkbox.secondaryData.subclass;
return classSelected && domainSelected && subclassSelected;
default:
return true;
}
});
});
const experiencesSelected = !this.levels[levelKey].achievements.experiences
? true
: Object.values(this.levels[levelKey].achievements.experiences).every(exp => exp.name);
const domainCardsSelected = Object.values(this.levels[levelKey].achievements.domainCards)
.filter(x => x.level <= this.endLevel)
.every(card => card.uuid);
const allAchievementsSelected = experiencesSelected && domainCardsSelected;
return allSelectionsMade && allChoicesMade && allAchievementsSelected;
}
get currentLevelFinished() {
return this.#levelFinished(this.currentLevel);
}
get allLevelsFinished() {
return Object.keys(this.levels)
.filter(level => Number(level) >= this.startLevel)
.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() {
let subclasses = [];
let multiclass = null;
Object.keys(this.levels).forEach(levelKey => {
const level = this.levels[levelKey];
Object.values(level.choices).forEach(choice => {
Object.values(choice).forEach(checkbox => {
if (checkbox.type === 'multiclass') {
multiclass = {
class: checkbox.data.length > 0 ? checkbox.data[0] : null,
domain: checkbox.secondaryData.domain ?? null,
subclass: checkbox.secondaryData.subclass ?? null,
tier: checkbox.tier,
level: levelKey
};
}
if (checkbox.type === 'subclass') {
subclasses.push({
tier: checkbox.tier,
level: levelKey
});
}
});
});
});
return { subclasses, multiclass };
}
get tiersForRendering() {
const tierKeys = Object.keys(this.tiers);
const selections = Object.keys(this.levels).reduce(
(acc, key) => {
const level = this.levels[key];
Object.keys(level.choices).forEach(optionKey => {
const choice = level.choices[optionKey];
Object.keys(choice).forEach(checkboxNr => {
const checkbox = choice[checkboxNr];
if (!acc[checkbox.tier][optionKey]) acc[checkbox.tier][optionKey] = {};
Object.keys(choice).forEach(checkboxNr => {
acc[checkbox.tier][optionKey][checkboxNr] = { ...checkbox, level: Number(key) };
});
});
});
return acc;
},
tierKeys.reduce((acc, key) => {
acc[key] = {};
return acc;
}, {})
);
const { multiclass, subclasses } = this.classUpgradeChoices;
return tierKeys.map((tierKey, tierIndex) => {
const tier = this.tiers[tierKey];
const multiclassInTier = multiclass?.tier === Number(tierKey);
const subclassInTier = subclasses.some(x => x.tier === Number(tierKey));
return {
name: game.i18n.localize(tier.name),
active: this.currentLevel >= Math.min(...tier.belongingLevels),
groups: Object.keys(tier.options).map(optionKey => {
const option = tier.options[optionKey];
const checkboxes = [...Array(option.checkboxSelections).keys()].flatMap(index => {
const checkboxNr = index + 1;
const checkboxData = selections[tierKey]?.[optionKey]?.[checkboxNr];
const checkbox = { ...option, checkboxNr, tier: tierKey };
if (checkboxData) {
checkbox.level = checkboxData.level;
checkbox.selected = true;
checkbox.disabled = checkbox.level !== this.currentLevel;
}
if (optionKey === 'multiclass') {
if ((multiclass && !multiclassInTier) || subclassInTier) {
checkbox.disabled = true;
}
}
if (optionKey === 'subclass' && multiclassInTier) {
checkbox.disabled = true;
}
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 {
label: label,
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
const anySelected = chunkedBoxes.some(x => x.selected);
const anyDisabled = chunkedBoxes.some(x => x.disabled);
return {
multi: option.minCost > 1,
checkboxes: chunkedBoxes.map(x => ({
...x,
selected: anySelected,
disabled: anyDisabled
}))
};
})
};
})
};
});
}
}
export class DhLevelupLevel extends foundry.abstract.DataModel {
static initializeData(levelData = { selections: [] }, maxSelections, achievements) {
return {
maxSelections: maxSelections,
achievements: {
experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {},
domainCards: levelData.achievements?.domainCards
? levelData.achievements.domainCards.reduce((acc, card, index) => {
acc[index] = { ...card };
return acc;
}, {})
: (achievements.domainCards ?? {}),
proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null
},
choices: levelData.selections.reduce((acc, data) => {
if (!acc[data.optionKey]) acc[data.optionKey] = {};
acc[data.optionKey][data.checkboxNr] = { ...data };
return acc;
}, {})
};
}
static defineSchema() {
const fields = foundry.data.fields;
return {
maxSelections: new fields.NumberField({ required: true, integer: true }),
achievements: new fields.SchemaField({
experiences: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
modifier: new fields.NumberField({ required: true, integer: true })
})
),
domainCards: new fields.TypedObjectField(
new fields.SchemaField({
uuid: new fields.StringField({ required: true, nullable: true, initial: null }),
itemUuid: new fields.StringField({ required: true }),
level: new fields.NumberField({ required: true, integer: true })
})
),
proficiency: new fields.NumberField({ integer: true })
}),
choices: new fields.TypedObjectField(
new fields.TypedObjectField(
new fields.SchemaField({
tier: new fields.NumberField({ required: true, integer: true }),
minCost: new fields.NumberField({ required: true, integer: true }),
amount: new fields.NumberField({ integer: true }),
value: new fields.StringField(),
data: new fields.ArrayField(new fields.StringField()),
secondaryData: new fields.TypedObjectField(new fields.StringField()),
type: new fields.StringField({ required: true })
})
)
)
};
}
get nrSelections() {
const selections = Object.keys(this.choices).reduce((acc, choiceKey) => {
const choice = this.choices[choiceKey];
acc += Object.values(choice).reduce((acc, x) => acc + x.minCost, 0);
return acc;
}, 0);
return {
selections: selections,
available: this.maxSelections - selections
};
}
}

View file

@ -241,6 +241,11 @@ export default class DhpActor extends Actor {
} }
} }
}); });
if (this.system.companion) {
this.system.companion.updateLevel(usedLevel);
}
this.sheet.render(); this.sheet.render();
} }
} }

View file

@ -0,0 +1 @@
export { default as DhActorCollection } from './actorCollection.mjs';

View file

@ -0,0 +1,14 @@
export default class DhActorCollection extends foundry.documents.collections.Actors {
/** Ensure companions are initialized after all other subtypes. */
_initialize() {
super._initialize();
const companions = [];
for (const actor of this.values()) {
if (actor.type === 'companion') companions.push(actor);
}
for (const actor of companions) {
this.delete(actor.id);
this.set(actor.id, actor);
}
}
}

View file

@ -212,6 +212,7 @@ export async function runMigrations() {
} }
if (foundry.utils.isNewerVersion('1.5.5', lastMigrationVersion)) { if (foundry.utils.isNewerVersion('1.5.5', lastMigrationVersion)) {
/* Clear out Environments that were added directly from compendium */
for (const scene of game.scenes) { for (const scene of game.scenes) {
if (!scene.flags.daggerheart) continue; if (!scene.flags.daggerheart) continue;
const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart); const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
@ -226,6 +227,25 @@ export async function runMigrations() {
lastMigrationVersion = '1.5.5'; lastMigrationVersion = '1.5.5';
} }
if (foundry.utils.isNewerVersion('1.6.0', lastMigrationVersion)) {
/* Delevel any companions that are higher level than their partner character */
for (const companion of game.actors.filter(x => x.type === 'companion')) {
if (companion.system.levelData.level.current <= 1) continue;
if (!companion.system.partner) {
await companion.updateLevel(1);
} else {
const endLevel = companion.system.partner.system.levelData.level.current;
if (endLevel < companion.system.levelData.level.current) {
companion.system.levelData.level.changed = companion.system.levelData.level.current;
await companion.updateLevel(endLevel);
}
}
}
lastMigrationVersion = '1.6.0';
}
//#endregion //#endregion
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);

View file

@ -16,7 +16,51 @@
"artist": "" "artist": ""
} }
}, },
"effects": [], "effects": [
{
"name": "Advanced Training",
"type": "base",
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"_id": "bKOuMxhB2Jth3j2T",
"img": "icons/creatures/mammals/wolf-howl-moon-gray.webp",
"changes": [
{
"key": "system.companionData.levelupChoices",
"mode": 2,
"value": "2",
"priority": null
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Choose two additional level-up options for your companion.</span></p>",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"_key": "!items.effects!uGcs785h94RMtueH.bKOuMxhB2Jth3j2T"
}
],
"sort": 0, "sort": 0,
"ownership": { "ownership": {
"default": 0, "default": 0,

View file

@ -16,7 +16,51 @@
"artist": "" "artist": ""
} }
}, },
"effects": [], "effects": [
{
"name": "Expert Training",
"type": "base",
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"_id": "rknTONvaUDZ2Yz1W",
"img": "icons/creatures/mammals/dog-husky-white-blue.webp",
"changes": [
{
"key": "system.companionData.levelupChoices",
"mode": 2,
"value": "1",
"priority": null
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Choose an additional level-up option for your companion.</span></p>",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"_key": "!items.effects!iCXtOWBKv1FdKdWz.rknTONvaUDZ2Yz1W"
}
],
"sort": 0, "sort": 0,
"ownership": { "ownership": {
"default": 0, "default": 0,

View file

@ -207,7 +207,7 @@
.input-section { .input-section {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; gap: 8px;
} }
} }

View file

@ -2,7 +2,7 @@
"id": "daggerheart", "id": "daggerheart",
"title": "Daggerheart", "title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system", "description": "An unofficial implementation of the Daggerheart system",
"version": "1.5.5", "version": "1.6.0",
"compatibility": { "compatibility": {
"minimum": "13.346", "minimum": "13.346",
"verified": "13.351", "verified": "13.351",

View file

@ -31,7 +31,7 @@
<h3 class='label'> <h3 class='label'>
{{localize 'DAGGERHEART.GENERAL.level'}} {{localize 'DAGGERHEART.GENERAL.level'}}
<div class="input-section"> <div class="input-section">
{{#if document.system.levelData.canLevelUp}} {{#if document.system.canLevelUp}}
<button <button
type="button" type="button"
class="level-button glow" data-tooltip="{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}" class="level-button glow" data-tooltip="{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}"
@ -41,7 +41,7 @@
</button> </button>
{{/if}} {{/if}}
<span {{#unless document.system.partner}}data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.companionPartnerLevelBlock"}}"{{/unless}}> <span {{#unless document.system.partner}}data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.companionPartnerLevelBlock"}}"{{/unless}}>
<input type="text" data-dtype="Number" class="level-value" value={{document.system.levelData.level.changed}} {{#if document.system.needsCharacterSetup}}disabled{{/if}} {{disabled (not document.system.partner)}} /> {{document.system.levelData.level.changed}}
</span> </span>
</div> </div>
</h3> </h3>