feat: add Equipment tab with slot-type validation

Add a new Equipment tab to manage player gear and stat bonuses.

Features:
- 19 equipment slots across 8 categories (helmet, necklace, body armor, gloves, pants, shoes, rings, accessories)
- Type-to-slot validation: each type has max equipped limits (1 helmet, 10 rings, 3 accessories, etc.)
- Auto-slot assignment: equipping a ring fills the first available ring slot
- Stat bonuses from equipped items display on RPG attributes (e.g. STR 10 +2)
- Create/edit modal with stat checkboxes per RPG attribute
- Inventory list for unequipped items

Architecture:
- Shared constants in src/systems/equipment/constants.js
- Category-based types (Ring, Accessory) with auto-slot assignment
- v7 migration converts legacy slot-specific types to generic categories
- Full i18n support for all UI strings

Files:
- New: src/systems/equipment/constants.js
- New: src/systems/interaction/equipmentActions.js
- New: src/systems/rendering/equipment.js
- Modified: state.js, persistence.js, template.html, index.js
- Modified: userStats.js, desktop.js, mobile.js, layout.js, modals.js
- Modified: apiClient.js, sillytavern.js, style.css, en.json
This commit is contained in:
2026-07-03 11:11:23 +02:00
parent 38fb3d8c51
commit 10cfe581ac
16 changed files with 1428 additions and 17 deletions
+17 -2
View File
@@ -21,6 +21,7 @@ import {
$infoBoxContainer,
$thoughtsContainer,
$inventoryContainer,
$equipmentContainer,
$questsContainer,
$musicPlayerContainer,
setExtensionSettings,
@@ -38,6 +39,7 @@ import {
setInfoBoxContainer,
setThoughtsContainer,
setInventoryContainer,
setEquipmentContainer,
setQuestsContainer,
setMusicPlayerContainer,
clearSessionAvatarPrompts
@@ -69,6 +71,7 @@ import {
createThoughtPanel
} from './src/systems/rendering/thoughts.js';
import { renderInventory } from './src/systems/rendering/inventory.js';
import { renderEquipment } from './src/systems/rendering/equipment.js';
import { renderQuests } from './src/systems/rendering/quests.js';
import { renderMusicPlayer } from './src/systems/rendering/musicPlayer.js';
import { toggleSnowflakes, initSnowflakes } from './src/systems/ui/snowflakes.js';
@@ -76,6 +79,7 @@ import { toggleDynamicWeather, initWeatherEffects, updateWeatherEffect } from '.
// Interaction modules
import { initInventoryEventListeners } from './src/systems/interaction/inventoryActions.js';
import { initEquipmentEventListeners } from './src/systems/interaction/equipmentActions.js';
// UI Systems modules
import {
@@ -313,6 +317,7 @@ async function initUI() {
setInfoBoxContainer($('#rpg-info-box'));
setThoughtsContainer($('#rpg-thoughts'));
setInventoryContainer($('#rpg-inventory'));
setEquipmentContainer($('#rpg-equipment'));
setQuestsContainer($('#rpg-quests'));
setMusicPlayerContainer($('#rpg-music-player'));
@@ -389,6 +394,12 @@ async function initUI() {
updateSectionVisibility();
});
$('#rpg-toggle-equipment').on('change', function() {
extensionSettings.showEquipment = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
});
$('#rpg-toggle-quests').on('change', function() {
extensionSettings.showQuests = $(this).prop('checked');
saveSettings();
@@ -403,6 +414,7 @@ async function initUI() {
renderInfoBox();
renderThoughts();
renderInventory();
renderEquipment();
renderQuests();
});
@@ -862,7 +874,7 @@ async function initUI() {
if (lastAssistantIndex !== -1) {
commitTrackerDataFromPriorMessage(lastAssistantIndex);
}
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory, renderEquipment);
});
// Strip widget refresh button - same functionality as main refresh button
@@ -881,7 +893,7 @@ async function initUI() {
if (lastAssistantIndex !== -1) {
commitTrackerDataFromPriorMessage(lastAssistantIndex);
}
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory, renderEquipment);
});
$('#rpg-stat-bar-color-low').on('change', function() {
@@ -1121,6 +1133,7 @@ async function initUI() {
$('#rpg-toggle-thought-based-expressions').prop('checked', extensionSettings.enableThoughtBasedExpressions === true);
$('#rpg-toggle-hide-default-expressions').prop('checked', extensionSettings.hideDefaultExpressionDisplay === true);
$('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory);
$('#rpg-toggle-equipment').prop('checked', extensionSettings.showEquipment);
$('#rpg-toggle-quests').prop('checked', extensionSettings.showQuests);
$('#rpg-toggle-lock-icons').prop('checked', extensionSettings.showLockIcons ?? true);
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
@@ -1271,6 +1284,7 @@ async function initUI() {
renderInfoBox();
renderThoughts();
renderInventory();
renderEquipment();
renderQuests();
renderMusicPlayer($musicPlayerContainer[0]);
updateDiceDisplay();
@@ -1284,6 +1298,7 @@ async function initUI() {
setupMobileKeyboardHandling();
setupContentEditableScrolling();
initInventoryEventListeners();
initEquipmentEventListeners();
// Initialize chapter checkpoint UI
initChapterCheckpointUI();