From 718696e611ea921950537619a24392fb01e464a7 Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Sun, 2 Nov 2025 10:59:06 +0100 Subject: [PATCH 1/4] Fix multiple UI and functionality issues - Fixed together mode: Render panels before cleaning DOM so trackers display properly - Fixed temperature unit toggle: Changed from 'celsius'/'fahrenheit' to 'C'/'F' to match config - Fixed temperature widget: Thermometer color thresholds now use Celsius internally for consistency - Fixed relationship remove buttons: Removed duplicate class causing wrong fields to be deleted - Added styling for relationship remove buttons to match custom field buttons - Added mobile font sizes for Past Events widget for better readability - Added parsing debug log to help troubleshoot together mode issues --- src/systems/generation/promptBuilder.js | 2 +- src/systems/integration/sillytavern.js | 16 ++++++++++--- src/systems/rendering/infoBox.js | 16 +++++++------ src/systems/ui/trackerEditor.js | 2 +- style.css | 30 ++++++++++++++++--------- 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index 4049536..68f1e5b 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -170,7 +170,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon instructions += 'Weather: [Weather Emoji, Forecast]\n'; } if (widgets.temperature?.enabled) { - const unit = widgets.temperature.unit === 'fahrenheit' ? '°F' : '°C'; + const unit = widgets.temperature.unit === 'F' ? '°F' : '°C'; instructions += `Temperature: [Temperature in ${unit}]\n`; } if (widgets.time?.enabled) { diff --git a/src/systems/integration/sillytavern.js b/src/systems/integration/sillytavern.js index a0bb38e..b40b0ca 100644 --- a/src/systems/integration/sillytavern.js +++ b/src/systems/integration/sillytavern.js @@ -99,6 +99,7 @@ export async function onMessageReceived(data) { // console.log('[RPG Companion] Parsing together mode response:', responseText); const parsedData = parseResponse(responseText); + // console.log('[RPG Companion] Parsed data:', parsedData); // Update stored data if (parsedData.userStats) { @@ -158,15 +159,24 @@ export async function onMessageReceived(data) { lastMessage.swipes[currentSwipeId] = cleanedMessage.trim(); } - // console.log('[RPG Companion] Cleaned message, removed tracker code blocks'); - - // Render the updated data + // Render the updated data FIRST (before cleaning DOM) renderUserStats(); renderInfoBox(); renderThoughts(); renderInventory(); renderQuests(); + // Then update the DOM to reflect the cleaned message + const lastMessageElement = $('#chat').children('.mes').last(); + if (lastMessageElement.length) { + const messageText = lastMessageElement.find('.mes_text'); + if (messageText.length) { + messageText.html(substituteParams(cleanedMessage.trim())); + } + } + + // console.log('[RPG Companion] Cleaned message, removed tracker code blocks from DOM'); + // Save to chat metadata saveChatData(); } diff --git a/src/systems/rendering/infoBox.js b/src/systems/rendering/infoBox.js index 4a4a5e6..d223e84 100644 --- a/src/systems/rendering/infoBox.js +++ b/src/systems/rendering/infoBox.js @@ -329,18 +329,18 @@ export function renderInfoBox() { let tempValue = data.tempValue || 20; // Apply temperature unit conversion - const preferredUnit = config.widgets.temperature.unit || 'celsius'; + const preferredUnit = config.widgets.temperature.unit || 'C'; if (data.temperature) { // Detect current unit in the data const isCelsius = tempDisplay.includes('°C'); const isFahrenheit = tempDisplay.includes('°F'); - if (preferredUnit === 'fahrenheit' && isCelsius) { + if (preferredUnit === 'F' && isCelsius) { // Convert C to F const fahrenheit = Math.round((tempValue * 9/5) + 32); tempDisplay = `${fahrenheit}°F`; tempValue = fahrenheit; - } else if (preferredUnit === 'celsius' && isFahrenheit) { + } else if (preferredUnit === 'C' && isFahrenheit) { // Convert F to C const celsius = Math.round((tempValue - 32) * 5/9); tempDisplay = `${celsius}°C`; @@ -348,12 +348,14 @@ export function renderInfoBox() { } } else { // No data yet, use default for preferred unit - tempDisplay = preferredUnit === 'fahrenheit' ? '68°F' : '20°C'; - tempValue = preferredUnit === 'fahrenheit' ? 68 : 20; + tempDisplay = preferredUnit === 'F' ? '68°F' : '20°C'; + tempValue = preferredUnit === 'F' ? 68 : 20; } - const tempPercent = Math.min(100, Math.max(0, ((tempValue + 20) / 60) * 100)); - const tempColor = tempValue < 10 ? '#4a90e2' : tempValue < 25 ? '#67c23a' : '#e94560'; + // Calculate thermometer display (convert to Celsius for consistent thresholds) + const tempInCelsius = preferredUnit === 'F' ? Math.round((tempValue - 32) * 5/9) : tempValue; + const tempPercent = Math.min(100, Math.max(0, ((tempInCelsius + 20) / 60) * 100)); + const tempColor = tempInCelsius < 10 ? '#4a90e2' : tempInCelsius < 25 ? '#67c23a' : '#e94560'; row1Widgets.push(`
diff --git a/src/systems/ui/trackerEditor.js b/src/systems/ui/trackerEditor.js index 4e31561..02bcd5f 100644 --- a/src/systems/ui/trackerEditor.js +++ b/src/systems/ui/trackerEditor.js @@ -443,7 +443,7 @@ function renderPresentCharactersTab() { - +
`; } diff --git a/style.css b/style.css index 8e0a61b..76c127e 100644 --- a/style.css +++ b/style.css @@ -1980,19 +1980,14 @@ body:has(.rpg-panel.rpg-position-left) #sheld { .rpg-character-stat { flex-shrink: 0; +} + +.rpg-character-stat .rpg-stat-name { font-size: clamp(0.5vw, 0.6vw, 0.7vw) !important; font-weight: 600 !important; white-space: nowrap !important; } -.rpg-character-stat .rpg-stat-label { - color: var(--rpg-text) !important; -} - -.rpg-character-stat .rpg-stat-value { - font-weight: bold !important; -} - /* Placeholder styles for empty sections */ .rpg-thoughts-placeholder, .rpg-placeholder-widget { @@ -3885,7 +3880,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld { font-size: 0.95em; } -.rpg-field-remove { +.rpg-field-remove, +.rpg-remove-relationship { flex-shrink: 0; padding: 0.375em 0.625em; background: var(--rpg-highlight); @@ -3896,7 +3892,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld { transition: opacity 0.2s; } -.rpg-field-remove:hover { +.rpg-field-remove:hover, +.rpg-remove-relationship:hover { opacity: 0.8; } @@ -4938,6 +4935,19 @@ body:has(.rpg-panel.rpg-position-left) #sheld { font-size: clamp(16px, 4.1vw, 20px) !important; } + /* Recent Events widget - mobile text sizing */ + .rpg-notebook-title { + font-size: clamp(9px, 2.2vw, 11px) !important; + } + + .rpg-bullet { + font-size: clamp(9px, 2.2vw, 11px) !important; + } + + .rpg-event-text { + font-size: clamp(8px, 2vw, 10px) !important; + } + /* ======================================== MOBILE STATS TAB LAYOUT IMPROVEMENTS ======================================== */ From 883212b5e936b53b3914f5b1c4eea75598c896bc Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Sun, 2 Nov 2025 11:07:46 +0100 Subject: [PATCH 2/4] Add comprehensive mobile font-size overrides - Added clamp() to all vw-based font sizes for mobile compatibility - Fixed collapse toggle button text size on mobile - Fixed top position panel titles and stats text sizes - Fixed panel header, loading indicator, and dice display sizes - Applied overrides for both @media (max-width: 768px) and (max-width: 1000px) - Ensures all text is readable on mobile devices without being too small - Font sizes now scale responsively with minimum readable sizes --- style.css | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/style.css b/style.css index 76c127e..77fcec8 100644 --- a/style.css +++ b/style.css @@ -6373,5 +6373,133 @@ body:has(.rpg-panel.rpg-position-left) #sheld { .rpg-main-quest-actions button { width: 100%; } + + /* ======================================== + MOBILE FONT SIZE OVERRIDES + Fix all vw-based font sizes for mobile readability + ======================================== */ + + /* Collapse toggle button */ + .rpg-collapse-toggle { + font-size: clamp(16px, 3vw, 20px) !important; + } + + /* Top position panel titles */ + .rpg-panel.rpg-position-top .rpg-stats-title { + font-size: clamp(12px, 2.6vw, 16px) !important; + } + + .rpg-panel.rpg-position-top .rpg-mood { + font-size: clamp(10px, 2vw, 13px) !important; + } + + .rpg-panel.rpg-position-top .rpg-classic-stats-title { + font-size: clamp(10px, 2vw, 13px) !important; + } + + .rpg-panel.rpg-position-top .rpg-classic-stat-label { + font-size: clamp(8px, 1.7vw, 11px) !important; + } + + .rpg-panel.rpg-position-top .rpg-classic-stat-value { + font-size: clamp(12px, 2.6vw, 16px) !important; + } + + .rpg-panel.rpg-position-top .rpg-classic-stat-btn { + font-size: clamp(10px, 2.2vw, 14px) !important; + } + + .rpg-panel.rpg-position-top .rpg-info-content, + .rpg-panel.rpg-position-top .rpg-thoughts-content { + font-size: clamp(10px, 2.2vw, 14px) !important; + } + + /* Panel header */ + .rpg-panel-header h3 { + font-size: clamp(14px, 3.4vw, 18px) !important; + } + + /* Loading indicator */ + .rpg-loading { + font-size: clamp(12px, 2.6vw, 16px) !important; + } + + /* Dice display */ + .rpg-dice-display { + font-size: clamp(10px, 2vw, 13px) !important; + } + + .rpg-dice-display i { + font-size: clamp(12px, 2.6vw, 16px) !important; + } + + .rpg-clear-dice-btn { + font-size: clamp(14px, 3vw, 18px) !important; + } } +/* ======================================== + MOBILE FONT SIZE OVERRIDES (ALL SCREENS) + Apply to screens up to 1000px + ======================================== */ +@media (max-width: 1000px) { + /* Collapse toggle button */ + .rpg-collapse-toggle { + font-size: clamp(16px, 3vw, 20px) !important; + } + + /* Top position panel titles */ + .rpg-panel.rpg-position-top .rpg-stats-title { + font-size: clamp(12px, 2.6vw, 16px) !important; + } + + .rpg-panel.rpg-position-top .rpg-mood { + font-size: clamp(10px, 2vw, 13px) !important; + } + + .rpg-panel.rpg-position-top .rpg-classic-stats-title { + font-size: clamp(10px, 2vw, 13px) !important; + } + + .rpg-panel.rpg-position-top .rpg-classic-stat-label { + font-size: clamp(8px, 1.7vw, 11px) !important; + } + + .rpg-panel.rpg-position-top .rpg-classic-stat-value { + font-size: clamp(12px, 2.6vw, 16px) !important; + } + + .rpg-panel.rpg-position-top .rpg-classic-stat-btn { + font-size: clamp(10px, 2.2vw, 14px) !important; + } + + .rpg-panel.rpg-position-top .rpg-info-content, + .rpg-panel.rpg-position-top .rpg-thoughts-content { + font-size: clamp(10px, 2.2vw, 14px) !important; + } + + /* Panel header */ + .rpg-panel-header h3 { + font-size: clamp(14px, 3.4vw, 18px) !important; + } + + /* Loading indicator */ + .rpg-loading { + font-size: clamp(12px, 2.6vw, 16px) !important; + } + + /* Dice display */ + .rpg-dice-display { + font-size: clamp(10px, 2vw, 13px) !important; + } + + .rpg-dice-display i { + font-size: clamp(12px, 2.6vw, 16px) !important; + } + + .rpg-clear-dice-btn { + font-size: clamp(14px, 3vw, 18px) !important; + } +} + + From f20710f5a35ebe1ddb987c46254b8d9eff9a1ad2 Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Mon, 3 Nov 2025 11:09:42 +0100 Subject: [PATCH 3/4] Make RPG attributes (STR/DEX/etc) customizable and editable - Replace showRPGAttributes boolean with rpgAttributes array in trackerConfig - Add RPG Attributes section in Edit Trackers with add/remove/rename/toggle - Dynamically generate attribute display from config in userStats.js - Add migration from old showRPGAttributes to new rpgAttributes array - Initialize new attributes with default value of 10 in classicStats - Default attributes: STR, DEX, CON, INT, WIS, CHA (all enabled) --- Marinara's Spaghetti Recipe.json | 72 ++++++++++++++++++++++++++++ RPG Companion Regexes.json | 30 ++++++++++++ index.js | 11 +++++ src/core/persistence.js | 45 +++++++++++++++++- src/core/state.js | 11 ++++- src/systems/rendering/userStats.js | 75 +++++++++++------------------- src/systems/ui/trackerEditor.js | 75 ++++++++++++++++++++++++++---- 7 files changed, 260 insertions(+), 59 deletions(-) create mode 100644 Marinara's Spaghetti Recipe.json create mode 100644 RPG Companion Regexes.json diff --git a/Marinara's Spaghetti Recipe.json b/Marinara's Spaghetti Recipe.json new file mode 100644 index 0000000..5f1fccb --- /dev/null +++ b/Marinara's Spaghetti Recipe.json @@ -0,0 +1,72 @@ +{ + "chat_completion_source": "custom", + "openai_model": "gpt-5-2025-08-07", + "claude_model": "claude-sonnet-4-0", + "openrouter_model": "anthropic/claude-sonnet-4.5", + "openrouter_use_fallback": false, + "openrouter_group_models": true, + "openrouter_sort_models": "context_length", + "openrouter_providers": [], + "openrouter_allow_fallbacks": false, + "openrouter_middleout": "off", + "ai21_model": "jamba-1.5-large", + "mistralai_model": "mistral-large-latest", + "cohere_model": "command-r-plus", + "perplexity_model": "", + "groq_model": "llama3-70b-8192", + "xai_model": "grok-3-beta", + "pollinations_model": "openai", + "aimlapi_model": "gpt-4o-mini-2024-07-18", + "electronhub_model": "gpt-4o-mini", + "electronhub_sort_models": "alphabetically", + "electronhub_group_models": false, + "moonshot_model": "kimi-latest", + "fireworks_model": "accounts/fireworks/models/kimi-k2-instruct", + "cometapi_model": "gpt-4o", + "zai_model": "glm-4.6", + "custom_model": "claude-opus-4-1-20250805-thinking-16k-v2", + "custom_prompt_post_processing": "semi", + "google_model": "gemini-2.0-flash-thinking-exp-01-21", + "vertexai_model": "gemini-2.0-flash-001", + "nanogpt_model": "gpt-4o-mini", + "deepseek_model": "chatgpt-4o-latest", + "azure_api_version": "2024-02-15-preview", + "azure_openai_model": "", + "temperature": 1, + "frequency_penalty": 0, + "presence_penalty": 0, + "top_p": 1, + "top_k": 0, + "top_a": 1, + "min_p": 0, + "repetition_penalty": 1, + "openai_max_context": 2000000, + "openai_max_tokens": 8192, + "wrap_in_quotes": false, + "names_behavior": -1, + "send_if_empty": "", + "impersonation_prompt": "Change of plans! Write a response as the user's {{user}} now. Match their style from the conversation history so far.", + "new_chat_prompt": "", + "new_group_chat_prompt": "", + "new_example_chat_prompt": "{{trim}}", + "continue_nudge_prompt": "Your last message got cut off. Continue writing it. Do not include any parts of the original one. Respond with a direct continuation only.", + "bias_preset_selected": "Marinara's Logit Bias", + "max_context_unlocked": false, + "wi_format": "{0}", + "scenario_format": "{{scenario}}", + "personality_format": "{{personality}}{{trim}}", + "group_nudge_prompt": "", + "stream_openai": true, + "show_external_models": false, + "assistant_prefill": "", + "assistant_impersonation": "", + "claude_use_sysprompt": true, + "use_makersuite_sysprompt": false, + "squash_system_messages": true, + "image_inlining": true, + "inline_image_quality": "high", + "bypass_status_check": false, + "continue_prefill": false, + "seed": -1, + "n": 1 +} diff --git a/RPG Companion Regexes.json b/RPG Companion Regexes.json new file mode 100644 index 0000000..544c5b0 --- /dev/null +++ b/RPG Companion Regexes.json @@ -0,0 +1,30 @@ +[ + { + "scriptName": "RPG: Clean HTML (From Outgoing Prompt)", + "findRegex": "/\\s?<(?!\\!--)(?:\"[^\"]*\"|'[^']*'|[^'\">])*>/g", + "replaceString": "", + "trimStrings": [], + "placement": [2], + "disabled": false, + "markdownOnly": false, + "promptOnly": true, + "runOnEdit": true, + "substituteRegex": 0, + "minDepth": null, + "maxDepth": null + }, + { + "scriptName": "RPG: Clean Tracker Blocks (Keeping Last)", + "findRegex": "/```\\n[\\s\\S]*?\\n---\\n[\\s\\S]*?```\\n+/g", + "replaceString": "", + "trimStrings": [], + "placement": [1, 2], + "disabled": false, + "markdownOnly": false, + "promptOnly": true, + "runOnEdit": true, + "substituteRegex": 0, + "minDepth": 3, + "maxDepth": null + } +] diff --git a/index.js b/index.js index f311e44..3200bae 100644 --- a/index.js +++ b/index.js @@ -118,6 +118,9 @@ import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/sy import { setupMemoryRecollectionButton, updateMemoryRecollectionButton } from './src/systems/features/memoryRecollection.js'; import { initLorebookLimiter } from './src/systems/features/lorebookLimiter.js'; +// Utility modules +import { importAllDefaults } from './src/utils/importDefaults.js'; + // Integration modules import { commitTrackerData, @@ -601,6 +604,14 @@ jQuery(async () => { // Non-critical - continue anyway } + // Import default preset and regexes if user doesn't have them + try { + await importAllDefaults(); + } catch (error) { + console.error('[RPG Companion] Failed to import defaults:', error); + // Non-critical - continue anyway + } + // Register all event listeners try { registerAllEvents({ diff --git a/src/core/persistence.js b/src/core/persistence.js index b590b91..426de9d 100644 --- a/src/core/persistence.js +++ b/src/core/persistence.js @@ -365,7 +365,14 @@ function migrateToTrackerConfig() { extensionSettings.trackerConfig = { userStats: { customStats: [], - showRPGAttributes: true, + rpgAttributes: [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ], statusSection: { enabled: true, showMoodEmoji: true, @@ -423,6 +430,42 @@ function migrateToTrackerConfig() { } } + // Migrate old showRPGAttributes boolean to rpgAttributes array + if (extensionSettings.trackerConfig.userStats.showRPGAttributes !== undefined) { + const shouldShow = extensionSettings.trackerConfig.userStats.showRPGAttributes; + extensionSettings.trackerConfig.userStats.rpgAttributes = [ + { id: 'str', name: 'STR', enabled: shouldShow }, + { id: 'dex', name: 'DEX', enabled: shouldShow }, + { id: 'con', name: 'CON', enabled: shouldShow }, + { id: 'int', name: 'INT', enabled: shouldShow }, + { id: 'wis', name: 'WIS', enabled: shouldShow }, + { id: 'cha', name: 'CHA', enabled: shouldShow } + ]; + delete extensionSettings.trackerConfig.userStats.showRPGAttributes; + console.log('[RPG Companion] Migrated showRPGAttributes to rpgAttributes array'); + } + + // Ensure rpgAttributes exists even if no migration was needed + if (!extensionSettings.trackerConfig.userStats.rpgAttributes) { + extensionSettings.trackerConfig.userStats.rpgAttributes = [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ]; + } + + // Ensure all rpgAttributes have corresponding values in classicStats + if (extensionSettings.classicStats) { + for (const attr of extensionSettings.trackerConfig.userStats.rpgAttributes) { + if (extensionSettings.classicStats[attr.id] === undefined) { + extensionSettings.classicStats[attr.id] = 10; + } + } + } + // Migrate old presentCharacters structure to new format if (extensionSettings.trackerConfig.presentCharacters) { const pc = extensionSettings.trackerConfig.presentCharacters; diff --git a/src/core/state.js b/src/core/state.js index 34e95d5..c115e3b 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -71,8 +71,15 @@ export let extensionSettings = { { id: 'hygiene', name: 'Hygiene', enabled: true }, { id: 'arousal', name: 'Arousal', enabled: true } ], - // RPG Attributes toggle - showRPGAttributes: true, + // RPG Attributes (customizable D&D-style attributes) + rpgAttributes: [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ], // Status section config statusSection: { enabled: true, diff --git a/src/systems/rendering/userStats.js b/src/systems/rendering/userStats.js index 12de415..a181ad2 100644 --- a/src/systems/rendering/userStats.js +++ b/src/systems/rendering/userStats.js @@ -86,7 +86,14 @@ export function renderUserStats() { { id: 'hygiene', name: 'Hygiene', enabled: true }, { id: 'arousal', name: 'Arousal', enabled: true } ], - showRPGAttributes: true, + rpgAttributes: [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ], statusSection: { enabled: true, showMoodEmoji: true, customFields: ['Conditions'] }, skillsSection: { enabled: false, label: 'Skills' } }; @@ -171,60 +178,32 @@ export function renderUserStats() { html += '
'; // Close rpg-stats-left - // RPG Attributes section (conditionally rendered) - if (config.showRPGAttributes) { + // RPG Attributes section (dynamically generated from config) + const rpgAttributes = config.rpgAttributes || []; + const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id); + + if (enabledAttributes.length > 0) { html += `
-
- STR + `; + + enabledAttributes.forEach(attr => { + const value = extensionSettings.classicStats[attr.id] !== undefined ? extensionSettings.classicStats[attr.id] : 10; + html += ` +
+ ${attr.name}
- - ${extensionSettings.classicStats.str} - -
-
-
- DEX -
- - ${extensionSettings.classicStats.dex} - -
-
-
- CON -
- - ${extensionSettings.classicStats.con} - -
-
-
- INT -
- - ${extensionSettings.classicStats.int} - -
-
-
- WIS -
- - ${extensionSettings.classicStats.wis} - -
-
-
- CHA -
- - ${extensionSettings.classicStats.cha} - + + ${value} +
+ `; + }); + + html += `
diff --git a/src/systems/ui/trackerEditor.js b/src/systems/ui/trackerEditor.js index 02bcd5f..c27f217 100644 --- a/src/systems/ui/trackerEditor.js +++ b/src/systems/ui/trackerEditor.js @@ -127,7 +127,14 @@ function resetToDefaults() { { id: 'hygiene', name: 'Hygiene', enabled: true }, { id: 'arousal', name: 'Arousal', enabled: true } ], - showRPGAttributes: true, + rpgAttributes: [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ], statusSection: { enabled: true, showMoodEmoji: true, @@ -212,11 +219,31 @@ function renderUserStatsTab() { html += '
'; html += ''; - // RPG Attributes toggle - html += '
'; - html += ``; - html += ''; + // RPG Attributes section + html += '

RPG Attributes

'; + html += '
'; + + const rpgAttributes = config.rpgAttributes || [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ]; + + rpgAttributes.forEach((attr, index) => { + html += ` +
+ + + +
+ `; + }); + html += '
'; + html += ''; // Status Section html += '

Status Section

'; @@ -287,9 +314,41 @@ function setupUserStatsListeners() { extensionSettings.trackerConfig.userStats.customStats[index].name = $(this).val(); }); - // RPG attributes toggle - $('#rpg-show-rpg-attrs').off('change').on('change', function() { - extensionSettings.trackerConfig.userStats.showRPGAttributes = $(this).is(':checked'); + // Add attribute + $('#rpg-add-attr').off('click').on('click', function() { + if (!extensionSettings.trackerConfig.userStats.rpgAttributes) { + extensionSettings.trackerConfig.userStats.rpgAttributes = []; + } + const newId = 'attr_' + Date.now(); + extensionSettings.trackerConfig.userStats.rpgAttributes.push({ + id: newId, + name: 'NEW', + enabled: true + }); + // Initialize value in classicStats if doesn't exist + if (extensionSettings.classicStats[newId] === undefined) { + extensionSettings.classicStats[newId] = 10; + } + renderUserStatsTab(); + }); + + // Remove attribute + $('.rpg-attr-remove').off('click').on('click', function() { + const index = $(this).data('index'); + extensionSettings.trackerConfig.userStats.rpgAttributes.splice(index, 1); + renderUserStatsTab(); + }); + + // Toggle attribute + $('.rpg-attr-toggle').off('change').on('change', function() { + const index = $(this).data('index'); + extensionSettings.trackerConfig.userStats.rpgAttributes[index].enabled = $(this).is(':checked'); + }); + + // Rename attribute + $('.rpg-attr-name').off('blur').on('blur', function() { + const index = $(this).data('index'); + extensionSettings.trackerConfig.userStats.rpgAttributes[index].name = $(this).val(); }); // Status section toggles From d8707318c83f76ba1caaae46657f1b75859c889b Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Mon, 3 Nov 2025 17:01:53 +0100 Subject: [PATCH 4/4] Add customizable RPG attributes and fix character stats editing Features: - Made RPG attributes (STR/DEX/CON/INT/WIS/CHA) fully customizable - Added enable/disable toggle for entire RPG Attributes section - Users can add/remove/rename/toggle individual attributes - Custom attribute names now appear in AI prompts for dice rolls - Added proper CSS styling for attribute editor fields Bug Fixes: - Fixed character stat editing showing 0% on blur but saving correctly - Character stats now create Stats line if missing from AI response - Separated stat name from editable percentage value - Added value sanitization (removes %, validates 0-100 range) - Stats line now inserts before Thoughts line when created Technical: - Added buildAttributesString() helper in promptBuilder.js - Updated generateTrackerInstructions and generateContextualSummary - Restructured character stat HTML to prevent nested contenteditable - Enhanced updateCharacterField to handle missing Stats lines - Removed legacy default preset/regex import code --- Marinara's Spaghetti Recipe.json | 72 ----------------- RPG Companion Regexes.json | 30 ------- index.js | 11 --- src/core/persistence.js | 6 ++ src/core/state.js | 1 + src/systems/generation/promptBuilder.js | 42 +++++++++- src/systems/rendering/thoughts.js | 101 ++++++++++++++++++++---- src/systems/rendering/userStats.js | 19 ++++- src/systems/ui/trackerEditor.js | 49 +++++++++--- style.css | 30 +++++++ 10 files changed, 218 insertions(+), 143 deletions(-) delete mode 100644 Marinara's Spaghetti Recipe.json delete mode 100644 RPG Companion Regexes.json diff --git a/Marinara's Spaghetti Recipe.json b/Marinara's Spaghetti Recipe.json deleted file mode 100644 index 5f1fccb..0000000 --- a/Marinara's Spaghetti Recipe.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "chat_completion_source": "custom", - "openai_model": "gpt-5-2025-08-07", - "claude_model": "claude-sonnet-4-0", - "openrouter_model": "anthropic/claude-sonnet-4.5", - "openrouter_use_fallback": false, - "openrouter_group_models": true, - "openrouter_sort_models": "context_length", - "openrouter_providers": [], - "openrouter_allow_fallbacks": false, - "openrouter_middleout": "off", - "ai21_model": "jamba-1.5-large", - "mistralai_model": "mistral-large-latest", - "cohere_model": "command-r-plus", - "perplexity_model": "", - "groq_model": "llama3-70b-8192", - "xai_model": "grok-3-beta", - "pollinations_model": "openai", - "aimlapi_model": "gpt-4o-mini-2024-07-18", - "electronhub_model": "gpt-4o-mini", - "electronhub_sort_models": "alphabetically", - "electronhub_group_models": false, - "moonshot_model": "kimi-latest", - "fireworks_model": "accounts/fireworks/models/kimi-k2-instruct", - "cometapi_model": "gpt-4o", - "zai_model": "glm-4.6", - "custom_model": "claude-opus-4-1-20250805-thinking-16k-v2", - "custom_prompt_post_processing": "semi", - "google_model": "gemini-2.0-flash-thinking-exp-01-21", - "vertexai_model": "gemini-2.0-flash-001", - "nanogpt_model": "gpt-4o-mini", - "deepseek_model": "chatgpt-4o-latest", - "azure_api_version": "2024-02-15-preview", - "azure_openai_model": "", - "temperature": 1, - "frequency_penalty": 0, - "presence_penalty": 0, - "top_p": 1, - "top_k": 0, - "top_a": 1, - "min_p": 0, - "repetition_penalty": 1, - "openai_max_context": 2000000, - "openai_max_tokens": 8192, - "wrap_in_quotes": false, - "names_behavior": -1, - "send_if_empty": "", - "impersonation_prompt": "Change of plans! Write a response as the user's {{user}} now. Match their style from the conversation history so far.", - "new_chat_prompt": "", - "new_group_chat_prompt": "", - "new_example_chat_prompt": "{{trim}}", - "continue_nudge_prompt": "Your last message got cut off. Continue writing it. Do not include any parts of the original one. Respond with a direct continuation only.", - "bias_preset_selected": "Marinara's Logit Bias", - "max_context_unlocked": false, - "wi_format": "{0}", - "scenario_format": "{{scenario}}", - "personality_format": "{{personality}}{{trim}}", - "group_nudge_prompt": "", - "stream_openai": true, - "show_external_models": false, - "assistant_prefill": "", - "assistant_impersonation": "", - "claude_use_sysprompt": true, - "use_makersuite_sysprompt": false, - "squash_system_messages": true, - "image_inlining": true, - "inline_image_quality": "high", - "bypass_status_check": false, - "continue_prefill": false, - "seed": -1, - "n": 1 -} diff --git a/RPG Companion Regexes.json b/RPG Companion Regexes.json deleted file mode 100644 index 544c5b0..0000000 --- a/RPG Companion Regexes.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "scriptName": "RPG: Clean HTML (From Outgoing Prompt)", - "findRegex": "/\\s?<(?!\\!--)(?:\"[^\"]*\"|'[^']*'|[^'\">])*>/g", - "replaceString": "", - "trimStrings": [], - "placement": [2], - "disabled": false, - "markdownOnly": false, - "promptOnly": true, - "runOnEdit": true, - "substituteRegex": 0, - "minDepth": null, - "maxDepth": null - }, - { - "scriptName": "RPG: Clean Tracker Blocks (Keeping Last)", - "findRegex": "/```\\n[\\s\\S]*?\\n---\\n[\\s\\S]*?```\\n+/g", - "replaceString": "", - "trimStrings": [], - "placement": [1, 2], - "disabled": false, - "markdownOnly": false, - "promptOnly": true, - "runOnEdit": true, - "substituteRegex": 0, - "minDepth": 3, - "maxDepth": null - } -] diff --git a/index.js b/index.js index 3200bae..f311e44 100644 --- a/index.js +++ b/index.js @@ -118,9 +118,6 @@ import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/sy import { setupMemoryRecollectionButton, updateMemoryRecollectionButton } from './src/systems/features/memoryRecollection.js'; import { initLorebookLimiter } from './src/systems/features/lorebookLimiter.js'; -// Utility modules -import { importAllDefaults } from './src/utils/importDefaults.js'; - // Integration modules import { commitTrackerData, @@ -604,14 +601,6 @@ jQuery(async () => { // Non-critical - continue anyway } - // Import default preset and regexes if user doesn't have them - try { - await importAllDefaults(); - } catch (error) { - console.error('[RPG Companion] Failed to import defaults:', error); - // Non-critical - continue anyway - } - // Register all event listeners try { registerAllEvents({ diff --git a/src/core/persistence.js b/src/core/persistence.js index 426de9d..c3152a2 100644 --- a/src/core/persistence.js +++ b/src/core/persistence.js @@ -365,6 +365,7 @@ function migrateToTrackerConfig() { extensionSettings.trackerConfig = { userStats: { customStats: [], + showRPGAttributes: true, rpgAttributes: [ { id: 'str', name: 'STR', enabled: true }, { id: 'dex', name: 'DEX', enabled: true }, @@ -457,6 +458,11 @@ function migrateToTrackerConfig() { ]; } + // Ensure showRPGAttributes exists (defaults to true) + if (extensionSettings.trackerConfig.userStats.showRPGAttributes === undefined) { + extensionSettings.trackerConfig.userStats.showRPGAttributes = true; + } + // Ensure all rpgAttributes have corresponding values in classicStats if (extensionSettings.classicStats) { for (const attr of extensionSettings.trackerConfig.userStats.rpgAttributes) { diff --git a/src/core/state.js b/src/core/state.js index c115e3b..1a4c190 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -72,6 +72,7 @@ export let extensionSettings = { { id: 'arousal', name: 'Arousal', enabled: true } ], // RPG Attributes (customizable D&D-style attributes) + showRPGAttributes: true, rpgAttributes: [ { id: 'str', name: 'STR', enabled: true }, { id: 'dex', name: 'DEX', enabled: true }, diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index 68f1e5b..62be66c 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -56,6 +56,41 @@ export function buildInventorySummary(inventory) { return 'None'; } +/** + * Builds a dynamic attributes string based on configured RPG attributes. + * Uses custom attribute names and values from classicStats. + * + * @returns {string} Formatted attributes string (e.g., "STR 10, DEX 12, INT 15, LVL 5") + */ +function buildAttributesString() { + const trackerConfig = extensionSettings.trackerConfig; + const classicStats = extensionSettings.classicStats; + const userStatsConfig = trackerConfig?.userStats; + + // Get enabled attributes from config + const rpgAttributes = userStatsConfig?.rpgAttributes || [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ]; + + const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id); + + // Build attributes string dynamically + const attributeParts = enabledAttributes.map(attr => { + const value = classicStats[attr.id] !== undefined ? classicStats[attr.id] : 10; + return `${attr.name} ${value}`; + }); + + // Add level at the end + attributeParts.push(`LVL ${extensionSettings.level}`); + + return attributeParts.join(', '); +} + /** * Generates an example block showing current tracker states in markdown code blocks. * Uses COMMITTED data (not displayed data) for generation context. @@ -250,7 +285,8 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // 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}, LVL ${extensionSettings.level}\n`; + const attributesString = buildAttributesString(); + instructions += `${userName}'s attributes: ${attributesString}\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`; } } @@ -327,9 +363,9 @@ export function generateContextualSummary() { // Include attributes and dice roll only if there was a dice roll if (extensionSettings.lastDiceRoll) { - const classicStats = extensionSettings.classicStats; const roll = extensionSettings.lastDiceRoll; - summary += `${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`; + const attributesString = buildAttributesString(); + summary += `${userName}'s attributes: ${attributesString}\n`; summary += `${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`; } diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index fa1fbc0..d263ed1 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -396,7 +396,7 @@ export function renderThoughts() { const statColor = getStatColor(statValue, extensionSettings.statBarColorLow, extensionSettings.statBarColorHigh); html += `
- ${stat.name}: ${statValue}% + ${stat.name}: ${statValue}%
`; } @@ -431,6 +431,7 @@ export function renderThoughts() { const character = $(this).data('character'); const field = $(this).data('field'); const value = $(this).text().trim(); + console.log('[RPG Companion] Character stat edit:', { character, field, value }); updateCharacterField(character, field, value); }); @@ -492,10 +493,20 @@ export function updateCharacterField(characterName, field, value) { } if (characterFound) { - // Update the specific field within the character block + // Check if we're updating a character stat + const isStatField = enabledCharStats.findIndex(s => s.name === field) !== -1; + let statsLineExists = false; + let statsLineIndex = -1; + + // First pass: check if Stats line exists and update other fields for (let i = characterStartIndex; i < characterEndIndex; i++) { const line = lines[i].trim(); + if (line.startsWith('Stats:')) { + statsLineExists = true; + statsLineIndex = i; + } + if (field === 'name' && line.startsWith('- ')) { lines[i] = `- ${value}`; } @@ -519,20 +530,68 @@ export function updateCharacterField(characterName, field, value) { const relationshipValue = emojiToRelationship[value] || value; lines[i] = `Relationship: ${relationshipValue}`; } - else if (line.startsWith('Stats:')) { - const statIndex = enabledCharStats.findIndex(s => s.name === field); - if (statIndex !== -1) { - const statsContent = line.substring(line.indexOf(':') + 1).trim(); - const statParts = statsContent.split('|').map(p => p.trim()); + } - for (let j = 0; j < statParts.length; j++) { - if (statParts[j].startsWith(field + ':')) { - statParts[j] = `${field}: ${value}%`; - break; - } + // Handle stat updates + if (isStatField) { + // Clean the value: remove % if present, parse as integer, clamp 0-100 + let cleanValue = value.replace('%', '').trim(); + let numValue = parseInt(cleanValue); + if (isNaN(numValue)) { + numValue = 0; + } + numValue = Math.max(0, Math.min(100, numValue)); + + console.log('[RPG Companion] Updating stat:', { field, rawValue: value, cleanValue, numValue }); + + if (statsLineExists) { + // Update existing Stats line + const line = lines[statsLineIndex]; + const statsContent = line.substring(line.indexOf(':') + 1).trim(); + const statParts = statsContent.split('|').map(p => p.trim()); + + let statFound = false; + for (let j = 0; j < statParts.length; j++) { + if (statParts[j].startsWith(field + ':')) { + statParts[j] = `${field}: ${numValue}%`; + statFound = true; + console.log('[RPG Companion] Updated stat part:', statParts[j]); + break; } - lines[i] = `Stats: ${statParts.join(' | ')}`; } + + // If stat wasn't found in existing parts, add it + if (!statFound) { + statParts.push(`${field}: ${numValue}%`); + console.log('[RPG Companion] Added new stat to existing line:', `${field}: ${numValue}%`); + } + + lines[statsLineIndex] = `Stats: ${statParts.join(' | ')}`; + console.log('[RPG Companion] Updated stats line:', lines[statsLineIndex]); + } else { + // Create new Stats line with all enabled stats (defaulting to 0% except the one being edited) + const statsParts = enabledCharStats.map(s => { + if (s.name === field) { + return `${s.name}: ${numValue}%`; + } + return `${s.name}: 0%`; + }); + const newStatsLine = `Stats: ${statsParts.join(' | ')}`; + + // Insert before Thoughts line or at end of character block + let insertIndex = characterEndIndex; + for (let i = characterStartIndex; i < characterEndIndex; i++) { + const line = lines[i].trim(); + const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts'; + if (line.startsWith(thoughtsFieldName + ':')) { + insertIndex = i; + break; + } + } + + lines.splice(insertIndex, 0, newStatsLine); + console.log('[RPG Companion] Created new stats line:', newStatsLine); + characterEndIndex++; // Adjust end index since we inserted a line } } } else { @@ -554,7 +613,19 @@ export function updateCharacterField(characterName, field, value) { } if (enabledCharStats.length > 0) { - const statsParts = enabledCharStats.map(s => `${s.name}: ${field === s.name ? value : '0'}%`); + const statsParts = enabledCharStats.map(s => { + if (field === s.name) { + // Clean the value: remove % if present, parse as integer, clamp 0-100 + let cleanValue = value.replace('%', '').trim(); + let numValue = parseInt(cleanValue); + if (isNaN(numValue)) { + numValue = 0; + } + numValue = Math.max(0, Math.min(100, numValue)); + return `${s.name}: ${numValue}%`; + } + return `${s.name}: 0%`; + }); newCharacterLines.push(`Stats: ${statsParts.join(' | ')}`); } @@ -565,6 +636,8 @@ export function updateCharacterField(characterName, field, value) { lastGeneratedData.characterThoughts = lines.join('\n'); committedTrackerData.characterThoughts = lines.join('\n'); + console.log('[RPG Companion] Updated characterThoughts data:', lastGeneratedData.characterThoughts); + const chat = getContext().chat; if (chat && chat.length > 0) { for (let i = chat.length - 1; i >= 0; i--) { diff --git a/src/systems/rendering/userStats.js b/src/systems/rendering/userStats.js index a181ad2..97fad33 100644 --- a/src/systems/rendering/userStats.js +++ b/src/systems/rendering/userStats.js @@ -179,10 +179,22 @@ export function renderUserStats() { html += '
'; // Close rpg-stats-left // RPG Attributes section (dynamically generated from config) - const rpgAttributes = config.rpgAttributes || []; - const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id); + // Check if RPG Attributes section is enabled + const showRPGAttributes = config.showRPGAttributes !== undefined ? config.showRPGAttributes : true; - if (enabledAttributes.length > 0) { + if (showRPGAttributes) { + // Use attributes from config, with fallback to defaults if not configured + const rpgAttributes = (config.rpgAttributes && config.rpgAttributes.length > 0) ? config.rpgAttributes : [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ]; + const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id); + + if (enabledAttributes.length > 0) { html += `
@@ -208,6 +220,7 @@ export function renderUserStats() {
`; + } } html += ''; // Close rpg-stats-content diff --git a/src/systems/ui/trackerEditor.js b/src/systems/ui/trackerEditor.js index c27f217..f872fab 100644 --- a/src/systems/ui/trackerEditor.js +++ b/src/systems/ui/trackerEditor.js @@ -127,6 +127,7 @@ function resetToDefaults() { { id: 'hygiene', name: 'Hygiene', enabled: true }, { id: 'arousal', name: 'Arousal', enabled: true } ], + showRPGAttributes: true, rpgAttributes: [ { id: 'str', name: 'STR', enabled: true }, { id: 'dex', name: 'DEX', enabled: true }, @@ -221,16 +222,31 @@ function renderUserStatsTab() { // RPG Attributes section html += '

RPG Attributes

'; + + // Enable/disable toggle for entire RPG Attributes section + const showRPGAttributes = config.showRPGAttributes !== undefined ? config.showRPGAttributes : true; + html += '
'; + html += ``; + html += ''; + html += '
'; + html += '
'; - const rpgAttributes = config.rpgAttributes || [ - { id: 'str', name: 'STR', enabled: true }, - { id: 'dex', name: 'DEX', enabled: true }, - { id: 'con', name: 'CON', enabled: true }, - { id: 'int', name: 'INT', enabled: true }, - { id: 'wis', name: 'WIS', enabled: true }, - { id: 'cha', name: 'CHA', enabled: true } - ]; + // Ensure rpgAttributes exists in the actual config (not just local fallback) + if (!config.rpgAttributes || config.rpgAttributes.length === 0) { + config.rpgAttributes = [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ]; + // Save the defaults back to the actual config + extensionSettings.trackerConfig.userStats.rpgAttributes = config.rpgAttributes; + } + + const rpgAttributes = config.rpgAttributes; rpgAttributes.forEach((attr, index) => { html += ` @@ -316,8 +332,16 @@ function setupUserStatsListeners() { // Add attribute $('#rpg-add-attr').off('click').on('click', function() { - if (!extensionSettings.trackerConfig.userStats.rpgAttributes) { - extensionSettings.trackerConfig.userStats.rpgAttributes = []; + // Ensure rpgAttributes array exists with defaults if needed + if (!extensionSettings.trackerConfig.userStats.rpgAttributes || extensionSettings.trackerConfig.userStats.rpgAttributes.length === 0) { + extensionSettings.trackerConfig.userStats.rpgAttributes = [ + { id: 'str', name: 'STR', enabled: true }, + { id: 'dex', name: 'DEX', enabled: true }, + { id: 'con', name: 'CON', enabled: true }, + { id: 'int', name: 'INT', enabled: true }, + { id: 'wis', name: 'WIS', enabled: true }, + { id: 'cha', name: 'CHA', enabled: true } + ]; } const newId = 'attr_' + Date.now(); extensionSettings.trackerConfig.userStats.rpgAttributes.push({ @@ -351,6 +375,11 @@ function setupUserStatsListeners() { extensionSettings.trackerConfig.userStats.rpgAttributes[index].name = $(this).val(); }); + // Enable/disable RPG Attributes section toggle + $('#rpg-show-rpg-attrs').off('change').on('change', function() { + extensionSettings.trackerConfig.userStats.showRPGAttributes = $(this).is(':checked'); + }); + // Status section toggles $('#rpg-status-enabled').off('change').on('change', function() { extensionSettings.trackerConfig.userStats.statusSection.enabled = $(this).is(':checked'); diff --git a/style.css b/style.css index 77fcec8..dd6e9ad 100644 --- a/style.css +++ b/style.css @@ -3682,6 +3682,36 @@ body:has(.rpg-panel.rpg-position-left) #sheld { opacity: 0.8; } +/* RPG Attributes editor styles (same as custom stats) */ +.rpg-attr-toggle { + flex-shrink: 0; +} + +.rpg-attr-name { + flex: 1; + padding: 0.375em 0.5em; + background: var(--rpg-bg); + border: 1px solid var(--rpg-border); + border-radius: 0.25em; + color: var(--rpg-text); + font-size: 0.95em; +} + +.rpg-attr-remove { + flex-shrink: 0; + padding: 0.375em 0.625em; + background: var(--rpg-highlight); + border: none; + border-radius: 0.25em; + color: white; + cursor: pointer; + transition: opacity 0.2s; +} + +.rpg-attr-remove:hover { + opacity: 0.8; +} + /* Toggle rows */ .rpg-editor-toggle-row { display: flex;