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
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ export function renderThoughts() {
|
||||
const statColor = getStatColor(statValue, extensionSettings.statBarColorLow, extensionSettings.statBarColorHigh);
|
||||
html += `
|
||||
<div class="rpg-character-stat">
|
||||
<span class="rpg-stat-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${stat.name}" title="Click to edit ${stat.name}">${stat.name}: <span style="color: ${statColor}">${statValue}%</span></span>
|
||||
<span class="rpg-stat-name">${stat.name}: </span><span class="rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${stat.name}" style="color: ${statColor}" title="Click to edit ${stat.name}">${statValue}%</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
// 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}: ${value}%`;
|
||||
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--) {
|
||||
|
||||
@@ -179,7 +179,19 @@ export function renderUserStats() {
|
||||
html += '</div>'; // Close rpg-stats-left
|
||||
|
||||
// RPG Attributes section (dynamically generated from config)
|
||||
const rpgAttributes = config.rpgAttributes || [];
|
||||
// Check if RPG Attributes section is enabled
|
||||
const showRPGAttributes = config.showRPGAttributes !== undefined ? config.showRPGAttributes : true;
|
||||
|
||||
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) {
|
||||
@@ -209,6 +221,7 @@ export function renderUserStats() {
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
html += '</div>'; // Close rpg-stats-content
|
||||
|
||||
|
||||
@@ -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,9 +222,19 @@ function renderUserStatsTab() {
|
||||
|
||||
// RPG Attributes section
|
||||
html += '<h4><i class="fa-solid fa-dice-d20"></i> RPG Attributes</h4>';
|
||||
|
||||
// Enable/disable toggle for entire RPG Attributes section
|
||||
const showRPGAttributes = config.showRPGAttributes !== undefined ? config.showRPGAttributes : true;
|
||||
html += '<div class="rpg-editor-toggle-row">';
|
||||
html += `<input type="checkbox" id="rpg-show-rpg-attrs" ${showRPGAttributes ? 'checked' : ''}>`;
|
||||
html += '<label for="rpg-show-rpg-attrs">Enable RPG Attributes Section</label>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="rpg-editor-stats-list" id="rpg-editor-attrs-list">';
|
||||
|
||||
const rpgAttributes = config.rpgAttributes || [
|
||||
// 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 },
|
||||
@@ -231,6 +242,11 @@ function renderUserStatsTab() {
|
||||
{ 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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user