/** * Equipment Actions Module * Handles all user interactions with the equipment system */ import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js'; import { saveSettings, saveChatData, updateMessageSwipeData } from '../../core/persistence.js'; import { renderEquipment } from '../rendering/equipment.js'; import { renderUserStats } from '../rendering/userStats.js'; import { EQUIPMENT_CATEGORIES, escapeHtml } from '../equipment/constants.js'; import { i18n } from '../../core/i18n.js'; /** * Check if a given slot is currently occupied by any item * @param {string} slotId - Slot to check * @param {Array} items - Equipment items array * @returns {boolean} */ function isSlotOccupied(slotId, items) { return items.some(item => item.slot === slotId); } /** * Find the first available slot for a given equipment type * @param {string} type - Equipment type (helmet, ring, accessory, etc.) * @param {Array} items - Equipment items array * @returns {string|null} Available slot ID or null if all full */ function findAvailableSlot(type, items) { const category = EQUIPMENT_CATEGORIES[type]; if (!category) return null; return category.slots.find(slot => !isSlotOccupied(slot, items)) || null; } /** * Get the slot ID currently assigned to an item * @param {Object} item - Equipment item * @returns {string|null} */ function getItemSlot(item) { return item.slot || null; } /** * Convert old specific slot type (e.g. 'ring3') to generic category (e.g. 'ring') * @param {string} type - Equipment type * @returns {string} Generic type */ function normalizeType(type) { if (!type) return type; if (type.startsWith('ring') && type !== 'ring') return 'ring'; if (type.startsWith('accessory') && type !== 'accessory') return 'accessory'; return type; } /** * Migrate old item types in the entire items array (for v6 → v7 migration) * @param {Array} items - Equipment items array */ function migrateItemTypes(items) { for (const item of items) { if (item.type) { item.type = normalizeType(item.type); } } } /** * Generate a unique ID for equipment items * @returns {string} Unique ID */ function generateItemId() { return 'eq_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6); } /** * Updates lastGeneratedData and committedTrackerData to include current equipment state */ function updateEquipmentData() { const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} }; const currentData = lastGeneratedData.userStats || committedTrackerData.userStats; if (currentData) { const trimmed = currentData.trim(); if (trimmed.startsWith('{') || trimmed.startsWith('[')) { try { const jsonData = JSON.parse(currentData); if (jsonData && typeof jsonData === 'object') { jsonData.equipment = JSON.parse(JSON.stringify(equipment)); const updatedJSON = JSON.stringify(jsonData, null, 2); lastGeneratedData.userStats = updatedJSON; committedTrackerData.userStats = updatedJSON; return; } } catch (e) { console.warn('[RPG Equipment] Failed to parse JSON, falling back to text format:', e); } } } // Fallback: rebuild text format const stats = extensionSettings.userStats; const config = extensionSettings.trackerConfig?.userStats || {}; let text = ''; const enabledStats = config.customStats?.filter(stat => stat && stat.enabled && stat.name && stat.id) || []; for (const stat of enabledStats) { const value = stats[stat.id] !== undefined ? stats[stat.id] : 100; text += `${stat.name}: ${value}%\n`; } if (config.statusSection?.enabled) { if (config.statusSection.showMoodEmoji) { text += `${stats.mood}: `; } text += `${stats.conditions || 'None'}\n`; } // Include equipment data in fallback const equipped = equipment.items.filter(item => item.slot); if (equipped.length > 0) { text += `\nEquipment:\n`; for (const item of equipped) { const bonuses = Object.entries(item.stats || {}) .filter(([_, val]) => val > 0) .map(([key, val]) => `${key.toUpperCase()}+${val}`) .join(' '); text += `- ${item.slot}: ${item.name}${bonuses ? ' (' + bonuses + ')' : ''}\n`; } } lastGeneratedData.userStats = text.trim(); committedTrackerData.userStats = text.trim(); } /** * Shows the equipment creation modal */ export function showCreateModal() { const modalHtml = generateCreateModalHTML(); $('body').append(modalHtml); const $modal = $('#rpg-equipment-modal'); $modal.hide().fadeIn(200); $('#rpg-eq-name').focus(); initStatCheckboxes(); } /** * Shows the equipment edit modal * @param {string} itemId - ID of the item to edit */ export function showEditModal(itemId) { const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} }; const item = equipment.items.find(i => i.id === itemId); if (!item) return; const modalHtml = generateEditModalHTML(item); $('body').append(modalHtml); const $modal = $('#rpg-equipment-modal'); $modal.hide().fadeIn(200); initStatCheckboxes(); } /** * Closes the equipment modal */ export function closeModal() { $('#rpg-equipment-modal').fadeOut(150, function() { $(this).remove(); }); } /** * Generates HTML for the create modal * @returns {string} Modal HTML */ function generateCreateModalHTML() { const attributes = getAvailableAttributes(); const typeOptions = generateTypeOptions(null); const statCheckboxes = generateStatCheckboxes(attributes, null); return `