merge: integrate upstream RPG attributes customization system
Merged commits from upstream/main (d870731): - Add customizable RPG attributes (STR/DEX/etc) with add/remove/rename - Fix character stats editing (0% display bug, missing Stats line creation) - Add mobile font-size overrides for better readability - Fix together mode rendering order (render panels before cleaning DOM) - Fix temperature unit toggle (C/F) and thermometer thresholds - Add buildAttributesString() for custom attribute names in AI prompts Upstream Features: - trackerConfig.rpgAttributes array replaces showRPGAttributes boolean - Per-attribute enable/disable, custom names, reordering - Tracker editor UI for managing attributes - Custom attribute names appear in AI prompts and dice rolls - Backward compatible migration from old boolean toggle Merge Conflict Resolution: - src/systems/integration/sillytavern.js: * Kept both: upstream's "render before DOM cleaning" + our refreshDashboard() * Result: render panels → refresh dashboard → update DOM - style.css: * Kept both: our Widget Integration CSS + upstream's Mobile Font Overrides * Our Recent Events width fix (width: 100%) preserved Related upstream commits: -d870731: Add customizable RPG attributes and fix character stats editing -f20710f: Make RPG attributes customizable and editable -883212b: Add comprehensive mobile font-size overrides -718696e: Fix multiple UI and functionality issues No functional changes to v2 dashboard yet - integration in next commit.
This commit is contained in:
@@ -381,6 +381,14 @@ function migrateToTrackerConfig() {
|
||||
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,
|
||||
@@ -438,6 +446,47 @@ 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 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) {
|
||||
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;
|
||||
|
||||
+9
-1
@@ -71,8 +71,16 @@ export let extensionSettings = {
|
||||
{ id: 'hygiene', name: 'Hygiene', enabled: true },
|
||||
{ id: 'arousal', name: 'Arousal', enabled: true }
|
||||
],
|
||||
// RPG Attributes toggle
|
||||
// RPG Attributes (customizable D&D-style attributes)
|
||||
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 }
|
||||
],
|
||||
// Status section config
|
||||
statusSection: {
|
||||
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.
|
||||
@@ -170,7 +205,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) {
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,11 +103,11 @@ export async function onMessageReceived(data) {
|
||||
// console.log('[RPG Companion] Parsing together mode response:', responseText);
|
||||
|
||||
const parsedData = parseResponse(responseText);
|
||||
console.log('[RPG Companion] Parsed data results:', {
|
||||
hasUserStats: !!parsedData.userStats,
|
||||
hasInfoBox: !!parsedData.infoBox,
|
||||
hasCharacterThoughts: !!parsedData.characterThoughts
|
||||
});
|
||||
// console.log('[RPG Companion] Parsed data results:', {
|
||||
// hasUserStats: !!parsedData.userStats,
|
||||
// hasInfoBox: !!parsedData.infoBox,
|
||||
// hasCharacterThoughts: !!parsedData.characterThoughts
|
||||
// });
|
||||
|
||||
// Update stored data (both lastGeneratedData for old UI and extensionSettings for dashboard widgets)
|
||||
if (parsedData.userStats) {
|
||||
@@ -171,9 +171,7 @@ export async function onMessageReceived(data) {
|
||||
lastMessage.swipes[currentSwipeId] = cleanedMessage.trim();
|
||||
}
|
||||
|
||||
// console.log('[RPG Companion] Cleaned message, removed tracker code blocks');
|
||||
|
||||
// Render the updated data (old panel UI)
|
||||
// Render the updated data FIRST (before cleaning DOM)
|
||||
renderUserStats();
|
||||
renderInfoBox();
|
||||
renderThoughts();
|
||||
@@ -183,6 +181,17 @@ export async function onMessageReceived(data) {
|
||||
// Refresh dashboard widgets (v2 dashboard)
|
||||
refreshDashboard();
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -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(`
|
||||
<div class="rpg-dashboard-widget rpg-temp-widget">
|
||||
<div class="rpg-thermometer">
|
||||
|
||||
@@ -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) {
|
||||
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--) {
|
||||
|
||||
@@ -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,64 +178,49 @@ export function renderUserStats() {
|
||||
|
||||
html += '</div>'; // Close rpg-stats-left
|
||||
|
||||
// RPG Attributes section (conditionally rendered)
|
||||
if (config.showRPGAttributes) {
|
||||
// RPG Attributes section (dynamically generated from config)
|
||||
// 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) {
|
||||
html += `
|
||||
<div class="rpg-stats-right">
|
||||
<div class="rpg-classic-stats">
|
||||
<div class="rpg-classic-stats-grid">
|
||||
<div class="rpg-classic-stat" data-stat="str">
|
||||
<span class="rpg-classic-stat-label">STR</span>
|
||||
`;
|
||||
|
||||
enabledAttributes.forEach(attr => {
|
||||
const value = extensionSettings.classicStats[attr.id] !== undefined ? extensionSettings.classicStats[attr.id] : 10;
|
||||
html += `
|
||||
<div class="rpg-classic-stat" data-stat="${attr.id}">
|
||||
<span class="rpg-classic-stat-label">${attr.name}</span>
|
||||
<div class="rpg-classic-stat-buttons">
|
||||
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="str">−</button>
|
||||
<span class="rpg-classic-stat-value">${extensionSettings.classicStats.str}</span>
|
||||
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="str">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpg-classic-stat" data-stat="dex">
|
||||
<span class="rpg-classic-stat-label">DEX</span>
|
||||
<div class="rpg-classic-stat-buttons">
|
||||
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="dex">−</button>
|
||||
<span class="rpg-classic-stat-value">${extensionSettings.classicStats.dex}</span>
|
||||
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="dex">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpg-classic-stat" data-stat="con">
|
||||
<span class="rpg-classic-stat-label">CON</span>
|
||||
<div class="rpg-classic-stat-buttons">
|
||||
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="con">−</button>
|
||||
<span class="rpg-classic-stat-value">${extensionSettings.classicStats.con}</span>
|
||||
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="con">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpg-classic-stat" data-stat="int">
|
||||
<span class="rpg-classic-stat-label">INT</span>
|
||||
<div class="rpg-classic-stat-buttons">
|
||||
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="int">−</button>
|
||||
<span class="rpg-classic-stat-value">${extensionSettings.classicStats.int}</span>
|
||||
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="int">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpg-classic-stat" data-stat="wis">
|
||||
<span class="rpg-classic-stat-label">WIS</span>
|
||||
<div class="rpg-classic-stat-buttons">
|
||||
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="wis">−</button>
|
||||
<span class="rpg-classic-stat-value">${extensionSettings.classicStats.wis}</span>
|
||||
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="wis">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpg-classic-stat" data-stat="cha">
|
||||
<span class="rpg-classic-stat-label">CHA</span>
|
||||
<div class="rpg-classic-stat-buttons">
|
||||
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="cha">−</button>
|
||||
<span class="rpg-classic-stat-value">${extensionSettings.classicStats.cha}</span>
|
||||
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="cha">+</button>
|
||||
<button class="rpg-classic-stat-btn rpg-stat-decrease" data-stat="${attr.id}">−</button>
|
||||
<span class="rpg-classic-stat-value">${value}</span>
|
||||
<button class="rpg-classic-stat-btn rpg-stat-increase" data-stat="${attr.id}">+</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
html += '</div>'; // Close rpg-stats-content
|
||||
|
||||
@@ -137,6 +137,14 @@ function resetToDefaults() {
|
||||
{ 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,
|
||||
@@ -221,12 +229,47 @@ function renderUserStatsTab() {
|
||||
html += '</div>';
|
||||
html += '<button class="rpg-btn-secondary" id="rpg-add-stat"><i class="fa-solid fa-plus"></i> Add Custom Stat</button>';
|
||||
|
||||
// RPG Attributes toggle
|
||||
// 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" ${config.showRPGAttributes ? 'checked' : ''}>`;
|
||||
html += '<label for="rpg-show-rpg-attrs">Show RPG Attributes (STR, DEX, etc.)</label>';
|
||||
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">';
|
||||
|
||||
// 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 += `
|
||||
<div class="rpg-editor-stat-item" data-index="${index}">
|
||||
<input type="checkbox" ${attr.enabled ? 'checked' : ''} class="rpg-attr-toggle" data-index="${index}">
|
||||
<input type="text" value="${attr.name}" class="rpg-attr-name" data-index="${index}" placeholder="Attribute Name">
|
||||
<button class="rpg-attr-remove" data-index="${index}" title="Remove attribute"><i class="fa-solid fa-trash"></i></button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
html += '<button class="rpg-btn-secondary" id="rpg-add-attr"><i class="fa-solid fa-plus"></i> Add Attribute</button>';
|
||||
|
||||
// Status Section
|
||||
html += '<h4><i class="fa-solid fa-face-smile"></i> Status Section</h4>';
|
||||
html += '<div class="rpg-editor-toggle-row">';
|
||||
@@ -296,7 +339,52 @@ function setupUserStatsListeners() {
|
||||
extensionSettings.trackerConfig.userStats.customStats[index].name = $(this).val();
|
||||
});
|
||||
|
||||
// RPG attributes toggle
|
||||
// Add attribute
|
||||
$('#rpg-add-attr').off('click').on('click', function() {
|
||||
// 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({
|
||||
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();
|
||||
});
|
||||
|
||||
// Enable/disable RPG Attributes section toggle
|
||||
$('#rpg-show-rpg-attrs').off('change').on('change', function() {
|
||||
extensionSettings.trackerConfig.userStats.showRPGAttributes = $(this).is(':checked');
|
||||
});
|
||||
@@ -452,7 +540,7 @@ function renderPresentCharactersTab() {
|
||||
<input type="text" value="${relationship}" class="rpg-relationship-name" placeholder="Relationship type">
|
||||
<span class="rpg-arrow">→</span>
|
||||
<input type="text" value="${emoji}" class="rpg-relationship-emoji" placeholder="Emoji" maxlength="4">
|
||||
<button class="rpg-field-remove rpg-remove-relationship" data-relationship="${relationship}" title="Remove"><i class="fa-solid fa-trash"></i></button>
|
||||
<button class="rpg-remove-relationship" data-relationship="${relationship}" title="Remove"><i class="fa-solid fa-trash"></i></button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -7981,3 +7981,67 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
.rpg-widget-empty-state a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user