/** * Character State Rendering Module * Displays character state information in the UI */ import { getCharacterState } from '../../core/characterState.js'; /** * Renders the character's emotional state section * @param {Object} $container - jQuery container element */ export function renderEmotionalState($container) { if (!$container || !$container.length) return; const charState = getCharacterState(); const charName = charState.characterName || 'Character'; let html = `
`; html += `

${charName}'s Emotional State

`; // Get active emotional states (>10 intensity) const activeEmotions = Object.entries(charState.secondaryStates) .filter(([key, value]) => value > 10) .sort((a, b) => b[1] - a[1]) // Sort by intensity .slice(0, 8); // Show top 8 if (activeEmotions.length > 0) { html += `
`; for (const [emotion, value] of activeEmotions) { const emotionLabel = formatEmotionName(emotion); const emotionColor = getEmotionColor(emotion, value); const barWidth = value; html += `
`; html += `${emotionLabel}`; html += `
`; html += `
`; html += `
`; html += `${value}`; html += `
`; } html += `
`; } else { html += `

Emotionally neutral

`; } html += `
`; $container.html(html); } /** * Renders the character's physical condition section * @param {Object} $container - jQuery container element */ export function renderPhysicalCondition($container) { if (!$container || !$container.length) return; const charState = getCharacterState(); const stats = charState.physicalStats; let html = `
`; html += `

Physical Condition

`; html += `
`; const displayStats = [ { key: 'health', label: 'Health', icon: '❤️' }, { key: 'energy', label: 'Energy', icon: '⚡' }, { key: 'hunger', label: 'Hunger', icon: '🍽️' }, { key: 'arousal', label: 'Arousal', icon: '🔥' } ]; for (const stat of displayStats) { const value = stats[stat.key] !== undefined ? stats[stat.key] : 50; const color = getStatColor(stat.key, value); html += `
`; html += `${stat.icon}`; html += `${stat.label}`; html += `
`; html += `
`; html += `
`; html += `${value}%`; html += `
`; } html += `
`; html += `
`; $container.html(html); } /** * Renders the character's relationships section * @param {Object} $container - jQuery container element */ export function renderRelationships($container) { if (!$container || !$container.length) return; const charState = getCharacterState(); const charName = charState.characterName || 'Character'; const relationships = charState.relationships; let html = `
`; html += `

${charName}'s Relationships

`; const relationshipEntries = Object.entries(relationships); if (relationshipEntries.length > 0) { html += `
`; for (const [npcName, relData] of relationshipEntries) { // Only show relationships with some significance if (relData.trust < 20 && relData.love < 10 && relData.attraction < 10) { continue; } html += `
`; html += `
`; html += `${npcName}`; html += `${relData.relationshipStatus || 'Acquaintance'}`; html += `
`; // Show key stats html += `
`; if (relData.trust > 20) { html += `Trust: ${relData.trust}`; } if (relData.love > 10) { html += `Love: ${relData.love}❤️`; } if (relData.attraction > 10) { html += `Attraction: ${relData.attraction}✨`; } html += `
`; // Show current thoughts if (relData.currentThoughts) { html += `
`; html += `"${relData.currentThoughts}"`; html += `
`; } html += `
`; } html += `
`; } else { html += `

No significant relationships yet

`; } html += `
`; $container.html(html); } /** * Renders the character's internal thoughts section * @param {Object} $container - jQuery container element */ export function renderInternalThoughts($container) { if (!$container || !$container.length) return; const charState = getCharacterState(); const charName = charState.characterName || 'Character'; const thoughts = charState.thoughts; let html = `
`; html += `

${charName}'s Thoughts

`; if (thoughts.internalMonologue) { html += `
`; html += `

"${thoughts.internalMonologue}"

`; html += `
`; } else { html += `

No current thoughts

`; } html += `
`; $container.html(html); } /** * Renders the character's current context (location, time, etc.) * @param {Object} $container - jQuery container element */ export function renderContext($container) { if (!$container || !$container.length) return; const charState = getCharacterState(); const context = charState.contextInfo; let html = `
`; html += `

Current Scene

`; html += `
`; if (context.location) { html += `
`; html += `📍`; html += `Location:`; html += `${context.location}`; html += `
`; } if (context.timeOfDay) { html += `
`; html += `🕐`; html += `Time:`; html += `${context.timeOfDay}`; html += `
`; } if (context.presentCharacters && context.presentCharacters.length > 0) { html += `
`; html += `👥`; html += `Present:`; html += `${context.presentCharacters.join(', ')}`; html += `
`; } html += `
`; html += `
`; $container.html(html); } /** * Renders a comprehensive character state overview * @param {Object} $container - jQuery container element */ export function renderCharacterStateOverview($container) { if (!$container || !$container.length) return; const charState = getCharacterState(); const charName = charState.characterName || 'Character'; let html = `
`; html += `

📊 ${charName}'s State

`; // Create tabbed sections html += `
`; html += ``; html += ``; html += ``; html += ``; html += ``; html += `
`; // Tab contents html += `
`; html += `
`; html += `
`; html += `
`; html += `
`; html += `
`; html += `
`; html += `
`; $container.html(html); // Render individual sections renderEmotionalState($('#rpg-tab-emotions')); renderPhysicalCondition($('#rpg-tab-physical')); renderRelationships($('#rpg-tab-relationships')); renderInternalThoughts($('#rpg-tab-thoughts')); renderContext($('#rpg-tab-context')); // Set up tab switching setupTabs(); } /** * Sets up tab switching functionality */ function setupTabs() { $('.rpg-tab-btn').off('click').on('click', function() { const tabName = $(this).data('tab'); // Update active button $('.rpg-tab-btn').removeClass('active'); $(this).addClass('active'); // Update active pane $('.rpg-tab-pane').removeClass('active'); $(`#rpg-tab-${tabName}`).addClass('active'); }); } /** * Helper function to format emotion names for display * @param {string} emotion - Raw emotion key * @returns {string} Formatted emotion name */ function formatEmotionName(emotion) { // Convert camelCase to Title Case return emotion .replace(/([A-Z])/g, ' $1') .replace(/^./, str => str.toUpperCase()) .trim(); } /** * Helper function to get color for an emotion based on its type and intensity * @param {string} emotion - Emotion type * @param {number} value - Emotion intensity (0-100) * @returns {string} CSS color */ function getEmotionColor(emotion, value) { const intensity = value / 100; // Color mappings for different emotions const emotionColors = { happy: `rgba(76, 175, 80, ${0.5 + intensity * 0.5})`, // Green sad: `rgba(96, 125, 139, ${0.5 + intensity * 0.5})`, // Blue-grey angry: `rgba(244, 67, 54, ${0.5 + intensity * 0.5})`, // Red anxious: `rgba(255, 152, 0, ${0.5 + intensity * 0.5})`, // Orange horny: `rgba(233, 30, 99, ${0.5 + intensity * 0.5})`, // Pink confident: `rgba(63, 81, 181, ${0.5 + intensity * 0.5})`, // Indigo scared: `rgba(121, 85, 72, ${0.5 + intensity * 0.5})`, // Brown playful: `rgba(255, 193, 7, ${0.5 + intensity * 0.5})` // Amber }; return emotionColors[emotion] || `rgba(158, 158, 158, ${0.5 + intensity * 0.5})`; } /** * Helper function to get color for a physical stat * @param {string} statKey - Stat key * @param {number} value - Stat value (0-100) * @returns {string} CSS color */ function getStatColor(statKey, value) { // For most stats, green is high, red is low // For hunger and arousal, yellow/orange might be more appropriate if (statKey === 'hunger') { if (value < 30) return '#4CAF50'; // Green (not hungry) if (value < 60) return '#FFC107'; // Yellow (getting hungry) return '#F44336'; // Red (very hungry) } if (statKey === 'arousal') { if (value < 30) return '#9E9E9E'; // Grey (low) if (value < 70) return '#E91E63'; // Pink (moderate) return '#880E4F'; // Dark pink (high) } // Default: green for high, red for low if (value > 70) return '#4CAF50'; // Green if (value > 40) return '#FFC107'; // Yellow return '#F44336'; // Red } /** * Updates character state display * Call this after parsing an LLM response to update the UI */ export function updateCharacterStateDisplay() { console.log('[Character State Renderer] 🎭 updateCharacterStateDisplay called'); // Find the main container const $mainContainer = $('#rpg-character-state-container'); console.log('[Character State Renderer] Container found:', $mainContainer && $mainContainer.length > 0); if ($mainContainer && $mainContainer.length) { console.log('[Character State Renderer] ✅ Rendering character state overview'); renderCharacterStateOverview($mainContainer); } else { console.warn('[Character State Renderer] ❌ Container #rpg-character-state-container not found in DOM'); } }