Fix backpack slot bonus, class features display, and skills layout

- Fix itemSlots.bonus being overwritten by prepareDerivedData (now adds
  to AE value instead of replacing)
- Add _prepareClassFeatures() to extract and display class features from
  class items on the abilities tab
- Change skills grid to vertical column flow (grid-auto-flow: column)
- Fix backpack.json effect _key to reference correct item ID

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-12-22 14:41:52 -06:00
parent 5e743b8f7e
commit 07426484bd
5 changed files with 64 additions and 5 deletions

View File

@ -634,9 +634,10 @@ export default class CharacterData extends VagabondActorBase {
// Calculate Item Slots: 8 + Might - Fatigue + bonus
const baseSlots = CONFIG.VAGABOND?.baseItemSlots || 8;
// Sum up all bonus sources
// Sum up all bonus sources from the bonuses array and ADD to existing bonus
// (existing bonus may have been set by Active Effects before prepareDerivedData runs)
const totalBonus = this.itemSlots.bonuses.reduce((sum, b) => sum + b.value, 0);
this.itemSlots.bonus = totalBonus;
this.itemSlots.bonus += totalBonus;
this.itemSlots.max =
baseSlots + stats.might.value - this.resources.fatigue.value + this.itemSlots.bonus;
// Check if overburdened

View File

@ -119,6 +119,45 @@ export default class VagabondCharacterSheet extends VagabondActorSheet {
context.classes = context.items.classes;
context.className = context.items.classes[0]?.name || "None";
context.ancestryName = context.items.ancestry?.name || "None";
// Prepare class features from class items for display
context.classFeatures = this._prepareClassFeatures();
}
/**
* Prepare class features for display on the abilities tab.
* Extracts features from class items at or below the character's current level.
* @returns {Object[]}
* @private
*/
_prepareClassFeatures() {
const level = this.actor.system.level || 1;
const classFeatures = [];
for (const classItem of this.actor.items.filter((i) => i.type === "class")) {
const features = classItem.system.features || [];
for (const feature of features) {
// Only include features at or below current level
if (feature.level <= level) {
classFeatures.push({
name: feature.name,
description: feature.description,
passive: feature.passive,
level: feature.level,
sourceClass: classItem.name,
img: classItem.img || "icons/svg/book.svg",
});
}
}
}
// Sort by level, then alphabetically
classFeatures.sort((a, b) => {
if (a.level !== b.level) return a.level - b.level;
return a.name.localeCompare(b.name);
});
return classFeatures;
}
/**

View File

@ -38,7 +38,7 @@
"effects": [
{
"_id": "backpackSlotBonus",
"_key": "!items.effects!vagabondEquipBackpack.backpackSlotBonus",
"_key": "!items.effects!vgbdEqpbackpack0.backpackSlotBonus",
"name": "Backpack Slot Bonus",
"icon": "icons/svg/item-bag.svg",
"changes": [

View File

@ -609,7 +609,10 @@
.skills-section {
.skills-grid {
display: grid;
// 2 columns with vertical flow (fills columns top-to-bottom, then left-to-right)
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(6, auto);
grid-auto-flow: column;
gap: $spacing-2;
}

View File

@ -30,6 +30,18 @@
<div class="abilities-section features">
<h2 class="section-header">{{localize "VAGABOND.Features"}}</h2>
<ul class="ability-list">
{{!-- Display class features extracted from class items --}}
{{#each classFeatures}}
<li class="ability-item class-feature">
<img class="ability-img" src="{{this.img}}" alt="{{this.name}}" />
<div class="ability-info">
<span class="ability-name">{{this.name}}</span>
<span class="ability-source">{{this.sourceClass}} Lv{{this.level}}</span>
</div>
<div class="ability-description">{{{this.description}}}</div>
</li>
{{/each}}
{{!-- Display standalone feature items --}}
{{#each items.features}}
<li class="ability-item" data-item-id="{{this.id}}">
<img class="ability-img" src="{{this.img}}" alt="{{this.name}}" />
@ -45,9 +57,13 @@
<i class="fa-solid fa-trash"></i>
</button>
</li>
{{else}}
<li class="ability-item empty">{{localize "VAGABOND.NoFeatures"}}</li>
{{/each}}
{{!-- Show empty message only if both are empty --}}
{{#unless classFeatures.length}}
{{#unless items.features.length}}
<li class="ability-item empty">{{localize "VAGABOND.NoFeatures"}}</li>
{{/unless}}
{{/unless}}
</ul>
</div>