diff --git a/src/core/state.js b/src/core/state.js index 354609c..4e86ae3 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -53,6 +53,7 @@ export let extensionSettings = { assets: "None" } }, + level: 1, // User's character level classicStats: { str: 10, dex: 10, diff --git a/src/systems/generation/parser.js b/src/systems/generation/parser.js index 60c6b70..72ead86 100644 --- a/src/systems/generation/parser.js +++ b/src/systems/generation/parser.js @@ -75,20 +75,26 @@ export function parseUserStats(statsText) { const hygieneMatch = statsText.match(/Hygiene:\s*(\d+)%/); const arousalMatch = statsText.match(/Arousal:\s*(\d+)%/); - // Match new format: [Emoji]: [Conditions] - // Look for a line after Arousal that has format [something]: [text] - // Split by lines and find the line after percentages - const lines = statsText.split('\n'); + // Match new format: Status: [Emoji, Conditions] + // Also support legacy format: [Emoji]: [Conditions] for backward compatibility let moodMatch = null; - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - // Skip lines with percentages or "Inventory:" - if (line.includes('%') || line.toLowerCase().startsWith('inventory:')) continue; - // Match emoji followed by colon and conditions - const match = line.match(/^(.+?):\s*(.+)$/); - if (match) { - moodMatch = match; - break; + const statusMatch = statsText.match(/Status:\s*(.+?),\s*(.+)/i); + if (statusMatch) { + // New format: Status: [Emoji, Conditions] + moodMatch = [null, statusMatch[1].trim(), statusMatch[2].trim()]; + } else { + // Legacy format: [Emoji]: [Conditions] + const lines = statsText.split('\n'); + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + // Skip lines with percentages or "Inventory:" or "Status:" + if (line.includes('%') || line.toLowerCase().startsWith('inventory:') || line.toLowerCase().startsWith('status:')) continue; + // Match emoji followed by colon and conditions + const match = line.match(/^(.+?):\s*(.+)$/); + if (match) { + moodMatch = match; + break; + } } } diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index 970a5ce..8070ad1 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -100,7 +100,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Only add tracker instructions if at least one tracker is enabled if (hasAnyTrackers) { // Universal instruction header - instructions += `\nAt the start of every reply, you must attach update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with proper numbers and [placeholders] with in-world details ${userName} perceives about the current scene and the present characters. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences:\n`; + instructions += `\nAt the start of every reply, you must attach an update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with actual numbers (e.g., 69) and replace all [placeholders] with concrete in-world details that ${userName} perceives about the current scene and the present characters. Do NOT keep the brackets or placeholder text in your response. For example: [Location] becomes Forest Clearing, [Mood Emoji] becomes π. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences:\n`; // Add format specifications for each enabled tracker if (extensionSettings.showUserStats) { @@ -112,7 +112,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon instructions += '- Energy: X%\n'; instructions += '- Hygiene: X%\n'; instructions += '- Arousal: X%\n'; - instructions += '[Mood Emoji]: [Conditions (up to three traits)]\n'; + instructions += 'Status: [Mood Emoji, Conditions (up to three traits)]\n'; // Add inventory format based on feature flag if (FEATURE_FLAGS.useNewInventory) { @@ -132,11 +132,11 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon instructions += '```\n'; instructions += 'Info Box\n'; instructions += '---\n'; - instructions += 'ποΈ: [Weekday, Month, Year]\n'; - instructions += '[Weather Emoji]: [Forecast]\n'; - instructions += 'π‘οΈ: [Temperature in Β°C]\n'; - instructions += 'π: [Time Start β Time End]\n'; - instructions += 'πΊοΈ: [Location]\n'; + instructions += 'Date: [Weekday, Month, Year]\n'; + instructions += 'Weather: [Weather Emoji, Forecast]\n'; + instructions += 'Temperature: [Temperature in Β°C]\n'; + instructions += 'Time: [Time Start β Time End]\n'; + instructions += 'Location: [Location]\n'; instructions += '```\n\n'; } @@ -150,13 +150,13 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Only add continuation instruction if includeContinuation is true if (includeContinuation) { - instructions += `After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting the protagonist's performance, low hygiene influencing their social interactions, environmental factors shaping the scene, a character's emotional state coloring their responses, and so on. Do not render brackets.\n\n`; + instructions += `After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting the protagonist's performance, low hygiene influencing their social interactions, environmental factors shaping the scene, a character's emotional state coloring their responses, and so on. Remember, all bracketed placeholders (e.g., [Location], [Mood Emoji]) MUST be replaced with actual content without the square brackets.\n\n`; } // Include attributes and dice roll only if there was a dice roll if (extensionSettings.lastDiceRoll) { const roll = extensionSettings.lastDiceRoll; - instructions += `${userName}'s attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}\n`; + instructions += `${userName}'s attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}, LVL ${extensionSettings.level}\n`; instructions += `${userName} rolled ${roll.total} on the last ${roll.formula} roll. Based on their attributes, decide whether they succeeded or failed the action they attempted.\n\n`; } } @@ -215,14 +215,14 @@ export function generateContextualSummary() { if (extensionSettings.lastDiceRoll) { const classicStats = extensionSettings.classicStats; const roll = extensionSettings.lastDiceRoll; - summary += `Attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}\n`; + summary += `Attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}, LVL ${extensionSettings.level}\n`; summary += `${userName} rolled ${roll.total} on the last ${roll.formula} roll. Based on their attributes, decide whether they succeed or fail the action they attempt.\n`; } summary += `\n`; } if (extensionSettings.showInfoBox && committedTrackerData.infoBox) { - // Parse info box data + // Parse info box data - support both new and legacy formats const lines = committedTrackerData.infoBox.split('\n'); let date = '', weather = '', temp = '', time = '', location = ''; @@ -230,30 +230,35 @@ export function generateContextualSummary() { for (const line of lines) { // console.log('[RPG Companion] π Processing line:', line); - // Use separate if statements (not else if) so each line is checked against all conditions - if (line.includes('ποΈ:')) { + + // New format with text labels + if (line.startsWith('Date:')) { + date = line.replace('Date:', '').trim(); + } else if (line.startsWith('Weather:')) { + weather = line.replace('Weather:', '').trim(); + } else if (line.startsWith('Temperature:')) { + temp = line.replace('Temperature:', '').trim(); + } else if (line.startsWith('Time:')) { + time = line.replace('Time:', '').trim(); + } else if (line.startsWith('Location:')) { + location = line.replace('Location:', '').trim(); + } + // Legacy format with emojis (for backward compatibility) + else if (line.includes('ποΈ:')) { date = line.replace('ποΈ:', '').trim(); - // console.log('[RPG Companion] π Found date:', date); - } - if (line.includes('π‘οΈ:')) { + } else if (line.includes('π‘οΈ:')) { temp = line.replace('π‘οΈ:', '').trim(); - // console.log('[RPG Companion] π‘οΈ Found temp:', temp); - } - if (line.includes('π:')) { + } else if (line.includes('π:')) { time = line.replace('π:', '').trim(); - // console.log('[RPG Companion] π Found time:', time); - } - if (line.includes('πΊοΈ:')) { + } else if (line.includes('πΊοΈ:')) { location = line.replace('πΊοΈ:', '').trim(); - // console.log('[RPG Companion] πΊοΈ Found location:', location); - } - // Check for weather emojis - use a simpler approach - const weatherEmojis = ['π€οΈ', 'βοΈ', 'β ', 'π¦οΈ', 'π§οΈ', 'βοΈ', 'π©οΈ', 'π¨οΈ', 'βοΈ', 'π«οΈ']; - const startsWithWeatherEmoji = weatherEmojis.some(emoji => line.startsWith(emoji + ':')); - if (startsWithWeatherEmoji && !line.includes('π‘οΈ') && !line.includes('πΊοΈ')) { - // Extract weather description (remove emoji and colon) - weather = line.substring(line.indexOf(':') + 1).trim(); - // console.log('[RPG Companion] π§οΈ Found weather:', weather); + } else { + // Check for weather emojis in legacy format + const weatherEmojis = ['π€οΈ', 'βοΈ', 'β ', 'π¦οΈ', 'π§οΈ', 'βοΈ', 'π©οΈ', 'π¨οΈ', 'βοΈ', 'π«οΈ']; + const startsWithWeatherEmoji = weatherEmojis.some(emoji => line.startsWith(emoji + ':')); + if (startsWithWeatherEmoji && !line.includes('π‘οΈ') && !line.includes('πΊοΈ')) { + weather = line.substring(line.indexOf(':') + 1).trim(); + } } } @@ -373,7 +378,7 @@ export function generateSeparateUpdatePrompt() { // Build the instruction message let instructionMessage = `\n\n`; instructionMessage += generateRPGPromptText().replace('start your response with', 'respond with'); - instructionMessage += `Provide ONLY the requested data in the exact formats specified above. Do not include any roleplay response, other text, or commentary. Do not render brackets.`; + instructionMessage += `Provide ONLY the requested data in the exact formats specified above. Do not include any roleplay response, other text, or commentary. Remember, all bracketed placeholders (e.g., [Location], [Mood Emoji]) MUST be replaced with actual content without the square brackets.`; messages.push({ role: 'user', diff --git a/src/systems/interaction/inventoryActions.js b/src/systems/interaction/inventoryActions.js index 2bbd511..4742070 100644 --- a/src/systems/interaction/inventoryActions.js +++ b/src/systems/interaction/inventoryActions.js @@ -213,10 +213,10 @@ export function removeItem(field, itemIndex, location) { const items = parseItems(currentString); // console.log('[RPG Companion] DEBUG items array before removal:', items); - + items.splice(itemIndex, 1); // Remove item at index // console.log('[RPG Companion] DEBUG items array after removal:', items); - + const newString = serializeItems(items); // console.log('[RPG Companion] DEBUG newString after removal:', newString); @@ -344,7 +344,7 @@ export function confirmRemoveLocation(locationName) { // console.log('[RPG Companion] DEBUG confirmRemoveLocation called for:', locationName); const inventory = extensionSettings.userStats.inventory; // console.log('[RPG Companion] DEBUG inventory.stored before deletion:', inventory.stored); - + delete inventory.stored[locationName]; // console.log('[RPG Companion] DEBUG inventory.stored after deletion:', inventory.stored); diff --git a/src/systems/rendering/infoBox.js b/src/systems/rendering/infoBox.js index e73ce45..544ff88 100644 --- a/src/systems/rendering/infoBox.js +++ b/src/systems/rendering/infoBox.js @@ -66,35 +66,42 @@ export function renderInfoBox() { for (const line of lines) { // console.log('[RPG Companion] Processing line:', line); - if (line.includes('ποΈ:')) { + // Support both new text format (Date:) and legacy emoji format (ποΈ:) + if (line.startsWith('Date:') || line.includes('ποΈ:')) { // console.log('[RPG Companion] β Matched DATE'); - const dateStr = line.replace('ποΈ:', '').trim(); + const dateStr = line.replace('Date:', '').replace('ποΈ:', '').trim(); // Parse format: "Weekday, Month Day, Year" or "Weekday, Month, Year" const dateParts = dateStr.split(',').map(p => p.trim()); data.weekday = dateParts[0] || ''; data.month = dateParts[1] || ''; data.year = dateParts[2] || ''; data.date = dateStr; - } else if (line.includes('π‘οΈ:')) { + } else if (line.startsWith('Temperature:') || line.includes('π‘οΈ:')) { // console.log('[RPG Companion] β Matched TEMPERATURE'); - const tempStr = line.replace('π‘οΈ:', '').trim(); + const tempStr = line.replace('Temperature:', '').replace('π‘οΈ:', '').trim(); data.temperature = tempStr; // Extract numeric value const tempMatch = tempStr.match(/(-?\d+)/); if (tempMatch) { data.tempValue = parseInt(tempMatch[1]); } - } else if (line.includes('π:')) { + } else if (line.startsWith('Time:') || line.includes('π:')) { // console.log('[RPG Companion] β Matched TIME'); - const timeStr = line.replace('π:', '').trim(); + const timeStr = line.replace('Time:', '').replace('π:', '').trim(); data.time = timeStr; // Parse "HH:MM β HH:MM" format const timeParts = timeStr.split('β').map(t => t.trim()); data.timeStart = timeParts[0] || ''; data.timeEnd = timeParts[1] || ''; - } else if (line.includes('πΊοΈ:')) { + } else if (line.startsWith('Location:') || line.includes('πΊοΈ:')) { // console.log('[RPG Companion] β Matched LOCATION'); - data.location = line.replace('πΊοΈ:', '').trim(); + data.location = line.replace('Location:', '').replace('πΊοΈ:', '').trim(); + } else if (line.startsWith('Weather:')) { + // New text format: Weather: [Emoji], [Forecast] + const weatherStr = line.replace('Weather:', '').trim(); + const weatherParts = weatherStr.split(',').map(p => p.trim()); + data.weatherEmoji = weatherParts[0] || ''; + data.weatherForecast = weatherParts[1] || ''; } else { // Check if it's a weather line // Since \p{Emoji} doesn't work reliably, use a simpler approach diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index 25d90bc..7edb78f 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -71,6 +71,7 @@ export function renderThoughts() { // console.log('[RPG Companion] Split into lines:', lines); // Parse format: [Emoji]: [Name, Status, Demeanor] | [Relationship] | [Thoughts] + // Also supports 4-part format: [Emoji]: [Name, Status] | [Demeanor] | [Relationship] | [Thoughts] for (const line of lines) { // Skip empty lines, headers, dividers, and code fences if (line.trim() && @@ -89,17 +90,40 @@ export function renderThoughts() { if (emojiMatch) { const emoji = emojiMatch[1].trim(); const info = emojiMatch[2].trim(); - const relationship = parts[1].trim(); // Enemy/Neutral/Friend/Lover - const thoughts = parts[2] ? parts[2].trim() : ''; + + // Handle both 3-part and 4-part formats + let relationship, thoughts, traits; + + if (parts.length === 3) { + // 3-part format: Emoji:Name,traits | Relationship | Thoughts + relationship = parts[1].trim(); + thoughts = parts[2].trim(); + const infoParts = info.split(',').map(p => p.trim()); + traits = infoParts.slice(1).join(', '); + } else if (parts.length >= 4) { + // 4-part format: Emoji:Name,traits | Demeanor | Relationship | Thoughts + // Add the demeanor to traits and use last two parts for relationship/thoughts + const demeanor = parts[1].trim(); + relationship = parts[2].trim(); + thoughts = parts[3].trim(); + const infoParts = info.split(',').map(p => p.trim()); + const baseTraits = infoParts.slice(1).join(', '); + traits = baseTraits ? `${baseTraits}, ${demeanor}` : demeanor; + } else { + // Fallback for 2-part format + relationship = parts[1].trim(); + thoughts = ''; + const infoParts = info.split(',').map(p => p.trim()); + traits = infoParts.slice(1).join(', '); + } // Parse name from info (first part before comma) const infoParts = info.split(',').map(p => p.trim()); const name = infoParts[0] || ''; - const traits = infoParts.slice(1).join(', '); if (name && name.toLowerCase() !== 'unavailable') { presentCharacters.push({ emoji, name, traits, relationship, thoughts }); - // console.log('[RPG Companion] Parsed character:', { name, relationship }); + // console.log('[RPG Companion] Parsed character:', { name, relationship, thoughts }); } } } @@ -429,6 +453,7 @@ export function updateChatThoughts() { const parts = line.split('|').map(p => p.trim()); // console.log('[RPG Companion] Line parts:', parts); + // Handle both 3-part and 4-part formats if (parts.length >= 3) { const firstPart = parts[0].trim(); const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/); @@ -436,7 +461,15 @@ export function updateChatThoughts() { if (emojiMatch) { const emoji = emojiMatch[1].trim(); const info = emojiMatch[2].trim(); - const thoughts = parts[2] ? parts[2].trim() : ''; + + let thoughts; + if (parts.length === 3) { + // 3-part format: Emoji:Name,traits | Relationship | Thoughts + thoughts = parts[2].trim(); + } else if (parts.length >= 4) { + // 4-part format: Emoji:Name,traits | Demeanor | Relationship | Thoughts + thoughts = parts[3].trim(); + } const infoParts = info.split(',').map(p => p.trim()); const name = infoParts[0] || ''; diff --git a/src/systems/rendering/userStats.js b/src/systems/rendering/userStats.js index d84e8e0..eed3789 100644 --- a/src/systems/rendering/userStats.js +++ b/src/systems/rendering/userStats.js @@ -55,8 +55,12 @@ export function renderUserStats() { const html = `