Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54e1b0c2b2 | |||
| 9720a7befe | |||
| 130998105a | |||
| 411dc3eb9c |
@@ -0,0 +1,330 @@
|
|||||||
|
# RPG Companion for SillyTavern — Agent Instructions
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
A **browser extension for SillyTavern** (AI roleplay frontend). It is NOT a standalone Node.js application. All JavaScript runs in the browser context inside SillyTavern. There is **no build step** — files are loaded directly by SillyTavern's extension loader.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
- **Entry point**: `index.js` (loaded by SillyTavern via `manifest.json`)
|
||||||
|
- **CSS**: `style.css` (single file, ~12,300 lines)
|
||||||
|
- **HTML templates**: `template.html` (main panel), `settings.html` (Extensions tab settings drawer)
|
||||||
|
- **Runtime**: Browser only. All code executes inside SillyTavern's page context.
|
||||||
|
- **jQuery**: Available globally as `$` / `jQuery`. Used extensively for DOM manipulation.
|
||||||
|
- **ES modules**: All `src/` files use `import`/`export` (browser ES module syntax).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manifest and loading
|
||||||
|
|
||||||
|
`manifest.json` tells SillyTavern how to load the extension:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"display_name": "RPG Companion",
|
||||||
|
"loading_order": 100,
|
||||||
|
"js": "index.js",
|
||||||
|
"css": "style.css"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
SillyTavern loads `index.js` and `style.css` relative to the extension directory. The extension name used internally is `third-party/rpg-companion-sillytavern`.
|
||||||
|
|
||||||
|
### Extension path detection
|
||||||
|
|
||||||
|
`src/core/config.js` auto-detects whether the extension is installed globally (`public/extensions/`) or per-user (`data/default-user/extensions/`) by inspecting `import.meta.url`. This determines template loading paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SillyTavern integration
|
||||||
|
|
||||||
|
The extension imports from SillyTavern's global scripts using **relative paths** that assume the extension lives at:
|
||||||
|
- `scripts/extensions/third-party/rpg-companion-sillytavern/` (global install), or
|
||||||
|
- `data/default-user/extensions/third-party/rpg-companion-sillytavern/` (user install)
|
||||||
|
|
||||||
|
Key imports in `index.js`:
|
||||||
|
```js
|
||||||
|
import { getContext, renderExtensionTemplateAsync, extension_settings as st_extension_settings }
|
||||||
|
from '../../../extensions.js';
|
||||||
|
import { eventSource, event_types, substituteParams, chat, saveSettingsDebounced, ... }
|
||||||
|
from '../../../../script.js';
|
||||||
|
import { selected_group, getGroupMembers } from '../../../group-chats.js';
|
||||||
|
import { power_user } from '../../../power-user.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
The deeper relative imports in `src/` files (e.g., `../../../../../../script.js`) follow the same pattern adjusted for directory depth.
|
||||||
|
|
||||||
|
### SillyTavern globals available at runtime
|
||||||
|
|
||||||
|
- `self.SillyTavern` — global namespace object
|
||||||
|
- `$` / `jQuery` — jQuery
|
||||||
|
- `toastr` — toast notification library
|
||||||
|
- `eventSource` — SillyTavern's event emitter
|
||||||
|
- `chat` — current chat message array
|
||||||
|
- `chat_metadata` — per-chat metadata store
|
||||||
|
- `characters` — loaded character array
|
||||||
|
- `getContext()` — returns current chat context
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Source tree (`src/`)
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── core/ # Core infrastructure
|
||||||
|
│ ├── config.js # Extension name, folder path, default settings
|
||||||
|
│ ├── state.js # Centralized state variables + getters/setters
|
||||||
|
│ ├── persistence.js # Save/load settings, chat data, migrations
|
||||||
|
│ ├── events.js # SillyTavern event system wrapper
|
||||||
|
│ └── i18n.js # Translation system (fetches JSON, applies data-i18n-key)
|
||||||
|
├── i18n/ # Translation JSON files
|
||||||
|
│ ├── en.json # English (reference, 513 lines)
|
||||||
|
│ ├── fr.json # French
|
||||||
|
│ ├── ru.json # Russian
|
||||||
|
│ ├── zh-cn.json # Simplified Chinese
|
||||||
|
│ ├── zh-tw.json # Traditional Chinese
|
||||||
|
│ └── validator.js # Node.js script to validate translation consistency
|
||||||
|
├── systems/ # Feature systems (organized by concern)
|
||||||
|
│ ├── generation/ # AI prompt generation, parsing, API calls
|
||||||
|
│ │ ├── promptBuilder.js # Builds tracker prompts for AI
|
||||||
|
│ │ ├── parser.js # Parses AI responses to extract tracker data
|
||||||
|
│ │ ├── apiClient.js # Makes API calls for separate/external generation
|
||||||
|
│ │ ├── injector.js # Injects tracker prompts into generation context
|
||||||
|
│ │ ├── lockManager.js # Manages locked tracker fields
|
||||||
|
│ │ ├── suppression.js # Controls when to skip tracker injection
|
||||||
|
│ │ ├── encounterPrompts.js # Combat encounter prompt building
|
||||||
|
│ │ ├── inventoryParser.js # Parses inventory data from AI responses
|
||||||
|
│ │ └── jsonPromptHelpers.js # JSON formatting helpers for prompts
|
||||||
|
│ ├── rendering/ # UI rendering functions
|
||||||
|
│ │ ├── userStats.js # Renders user stats panel
|
||||||
|
│ │ ├── infoBox.js # Renders info box (date, weather, location, events)
|
||||||
|
│ │ ├── thoughts.js # Renders character thoughts + chat bubbles
|
||||||
|
│ │ ├── inventory.js # Renders inventory section
|
||||||
|
│ │ ├── equipment.js # Renders equipment section
|
||||||
|
│ │ ├── quests.js # Renders quests section
|
||||||
|
│ │ └── musicPlayer.js # Renders Spotify music player
|
||||||
|
│ ├── ui/ # UI components and helpers
|
||||||
|
│ │ ├── theme.js # Theme system (default, sci-fi, fantasy, cyberpunk, custom)
|
||||||
|
│ │ ├── modals.js # Settings modal, dice modal, deprecation modal
|
||||||
|
│ │ ├── layout.js # Panel positioning, collapse/expand, section visibility
|
||||||
|
│ │ ├── mobile.js # Mobile FAB button, mobile tabs, keyboard handling
|
||||||
|
│ │ ├── desktop.js # Desktop strip widgets
|
||||||
|
│ │ ├── trackerEditor.js # Tracker configuration editor
|
||||||
|
│ │ ├── promptsEditor.js # Custom prompt editor
|
||||||
|
│ │ ├── checkpointUI.js # Chapter checkpoint buttons
|
||||||
|
│ │ ├── encounterUI.js # Combat encounter modal
|
||||||
|
│ │ ├── snowflakes.js # Snowflake animation effect
|
||||||
|
│ │ ├── weatherEffects.js # Dynamic weather visual effects
|
||||||
|
│ │ └── alternatePresentCharacters.js # Below-chat character panel
|
||||||
|
│ ├── integration/ # SillyTavern event integration
|
||||||
|
│ │ ├── sillytavern.js # Event handlers: message sent/received, swipe, etc.
|
||||||
|
│ │ └── thoughtBasedExpressions.js # Integrates with ST Character Expressions
|
||||||
|
│ ├── interaction/ # User interaction handlers
|
||||||
|
│ │ ├── inventoryActions.js # Inventory click/edit handlers
|
||||||
|
│ │ ├── equipmentActions.js # Equipment click/edit handlers
|
||||||
|
│ │ └── inventoryEdit.js # Inventory inline editing
|
||||||
|
│ └── features/ # Feature modules
|
||||||
|
│ ├── plotProgression.js # Plot progression buttons
|
||||||
|
│ ├── classicStats.js # D&D-style attribute buttons
|
||||||
|
│ ├── dice.js # Dice roller
|
||||||
|
│ ├── htmlCleaning.js # Regex-based HTML tag cleaning
|
||||||
|
│ ├── jsonCleaning.js # Regex-based JSON cleanup in messages
|
||||||
|
│ ├── musicPlayer.js # Spotify URL parsing and embedding
|
||||||
|
│ ├── chapterCheckpoint.js # Save/restore tracker state checkpoints
|
||||||
|
│ ├── avatarGenerator.js # Auto-generate character avatars
|
||||||
|
│ └── encounterState.js # Combat encounter state management
|
||||||
|
├── types/ # JSDoc type definitions
|
||||||
|
│ └── inventory.js # InventoryV2 type definition
|
||||||
|
└── utils/ # Utility functions
|
||||||
|
├── avatars.js # Avatar URL handling
|
||||||
|
├── imageUrls.js # Image URL utilities
|
||||||
|
├── itemParser.js # Item string parsing
|
||||||
|
├── jsonMigration.js # v2 to v3 JSON format migration
|
||||||
|
├── jsonRepair.js # JSON repair for malformed AI responses
|
||||||
|
├── migration.js # Settings migration helpers
|
||||||
|
├── presentCharacters.js # Present character data helpers
|
||||||
|
├── responseExtractor.js # Extract tracker data from AI responses
|
||||||
|
├── security.js # Input validation and sanitization
|
||||||
|
├── sillyTavernExpressions.js # ST expression mapping
|
||||||
|
├── thoughtBasedExpressionPortraits.js # Expression-to-portrait mapping
|
||||||
|
└── transformations.js # Data transformation utilities
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data flow
|
||||||
|
|
||||||
|
1. **User sends message** → `MESSAGE_SENT` event fires
|
||||||
|
2. **Extension hooks** `onMessageSent` in `sillytavern.js`
|
||||||
|
3. **Together mode**: Tracker prompt is injected into the generation context via `injector.js`
|
||||||
|
4. **Separate mode**: After main response, `apiClient.js` makes a separate API call
|
||||||
|
5. **External API mode**: Uses user-configured external API endpoint
|
||||||
|
6. **AI responds** → `MESSAGE_RECEIVED` event fires
|
||||||
|
7. **Extension hooks** `onMessageReceived` → `parser.js` extracts tracker data
|
||||||
|
8. **Rendering**: `renderUserStats()`, `renderInfoBox()`, `renderThoughts()`, etc. update the UI
|
||||||
|
9. **Persistence**: Tracker data is saved to `chat_metadata` per chat, and per-swipe
|
||||||
|
|
||||||
|
### State management
|
||||||
|
|
||||||
|
All extension state lives in `src/core/state.js` as module-level `let` variables with getter/setter functions. Key state:
|
||||||
|
|
||||||
|
- `extensionSettings` — persisted settings object (merged with defaults on load)
|
||||||
|
- `lastGeneratedData` — tracker data from the last AI generation
|
||||||
|
- `committedTrackerData` — tracker data committed after a message
|
||||||
|
- `lastActionWasSwipe` — flag to handle swipe-specific logic
|
||||||
|
- `isGenerating` — flag to prevent re-entrant generation
|
||||||
|
- `pendingDiceRoll` — dice roll result to inject into next generation
|
||||||
|
- DOM element caches: `$panelContainer`, `$userStatsContainer`, etc.
|
||||||
|
|
||||||
|
### Persistence
|
||||||
|
|
||||||
|
`src/core/persistence.js` handles:
|
||||||
|
- Extension settings: saved via SillyTavern's `saveSettingsDebounced()`
|
||||||
|
- Chat-specific data: saved in `chat_metadata.rpg_companion` per chat
|
||||||
|
- Per-swipe data: stored on each chat message object
|
||||||
|
- Settings versioning: automatic migration via `CURRENT_SETTINGS_VERSION` (currently 7)
|
||||||
|
- Deferred saves: batches chat data saves to avoid excessive writes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Validate translation files (check missing keys, type mismatches, empty values)
|
||||||
|
npm run validate_locale_once
|
||||||
|
|
||||||
|
# Watch mode: re-validate on file changes
|
||||||
|
npm run validate_locale
|
||||||
|
```
|
||||||
|
|
||||||
|
No build, lint, test, or typecheck commands exist. The code runs directly in the browser.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## i18n system
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
1. Translation files are flat JSON in `src/i18n/{locale}.json`
|
||||||
|
2. Keys use dot notation: `"template.settingsModal.display.showUserStats"`
|
||||||
|
3. HTML elements use `data-i18n-key`, `data-i18n-title`, or `data-i18n-aria-label` attributes
|
||||||
|
4. `src/core/i18n.js` fetches translations via `fetch()` from the extension path
|
||||||
|
5. `applyTranslations()` walks the DOM and updates `textContent`, `title`, and `aria-label`
|
||||||
|
|
||||||
|
### Adding translations
|
||||||
|
|
||||||
|
1. Add keys to `src/i18n/en.json` (reference locale)
|
||||||
|
2. Add corresponding keys to all other locale files
|
||||||
|
3. Add `data-i18n-key="your.key.here"` to HTML elements in `template.html` or `settings.html`
|
||||||
|
4. For dynamically generated text, use `i18n.getTranslation('your.key.here')` in JS
|
||||||
|
|
||||||
|
### Validator
|
||||||
|
|
||||||
|
`src/i18n/validator.js` is a **Node.js script** (uses `require()`, not ES modules). It:
|
||||||
|
- Compares all locales against the reference locale for missing/extra keys
|
||||||
|
- Checks for type mismatches
|
||||||
|
- Checks for empty strings and null values
|
||||||
|
- Scans `.html`/`.js`/`.jsx` files for unlocalized text (text in tags without `data-i18n-key`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key conventions
|
||||||
|
|
||||||
|
### jQuery usage
|
||||||
|
|
||||||
|
DOM manipulation uses jQuery throughout. Element caches are stored as jQuery objects with `$` prefix:
|
||||||
|
```js
|
||||||
|
$panelContainer = $('#rpg-companion-panel')
|
||||||
|
$userStatsContainer = $('#rpg-user-stats')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event system
|
||||||
|
|
||||||
|
All SillyTavern event registration goes through `src/core/events.js`:
|
||||||
|
```js
|
||||||
|
registerAllEvents({
|
||||||
|
[event_types.MESSAGE_SENT]: onMessageSent,
|
||||||
|
[event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS naming
|
||||||
|
|
||||||
|
All CSS classes use `rpg-` prefix to avoid conflicts with SillyTavern and other extensions:
|
||||||
|
- `.rpg-panel`, `.rpg-section`, `.rpg-stat-bar`, `.rpg-btn-primary`, etc.
|
||||||
|
- CSS custom properties: `--rpg-bg`, `--rpg-accent`, `--rpg-text`, `--rpg-highlight`, `--rpg-border`, `--rpg-shadow`
|
||||||
|
|
||||||
|
### Panel structure
|
||||||
|
|
||||||
|
The main panel (`template.html`) has these sections in order:
|
||||||
|
1. Collapse toggle button
|
||||||
|
2. Strip widget container (shown when panel is collapsed)
|
||||||
|
3. Game container with header
|
||||||
|
4. Dice roll display
|
||||||
|
5. Content box with sections: User Stats → Info Box → Thoughts → Inventory → Equipment → Quests → Music Player
|
||||||
|
6. Feature toggles row (HTML, Dialogue Coloring, Deception, Omniscience, CYOA, Spotify, Weather, Narrator, Auto Avatars)
|
||||||
|
7. Manual update button
|
||||||
|
8. Settings and Edit Trackers buttons
|
||||||
|
|
||||||
|
### Generation modes
|
||||||
|
|
||||||
|
Three modes control how tracker data is generated:
|
||||||
|
- **`together`**: Tracker prompt injected into main generation. Single API call, faster, but tracker JSON mixed in response.
|
||||||
|
- **`separate`**: Two API calls — one for roleplay, one for tracker data. Cleaner roleplay, slower.
|
||||||
|
- **`external`**: Uses user-configured external API (OpenAI-compatible endpoint) for tracker generation.
|
||||||
|
|
||||||
|
### Guided generation compatibility
|
||||||
|
|
||||||
|
The `skipInjectionsForGuided` setting controls tracker injection behavior during guided generations:
|
||||||
|
- `'none'` — always inject (default)
|
||||||
|
- `'impersonation'` — skip only for impersonation-style guided generations
|
||||||
|
- `'guided'` — skip for any guided/instruct or quiet_prompt generation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important gotchas
|
||||||
|
|
||||||
|
1. **No bundler**: All imports use relative paths that depend on the extension's directory structure within SillyTavern. Changing file locations requires updating import paths.
|
||||||
|
|
||||||
|
2. **Relative import depth matters**: Files in `src/` use deeper relative paths (`../../../../../../script.js`) than `index.js` (`../../../../script.js`). When moving files between directories, adjust accordingly.
|
||||||
|
|
||||||
|
3. **Settings merge**: On load, saved settings are deep-merged with defaults from `src/core/state.js`. New settings fields should be added to both the state module (runtime defaults) AND `src/core/config.js` (documentation defaults).
|
||||||
|
|
||||||
|
4. **Chat metadata**: Per-chat tracker data lives in `chat_metadata.rpg_companion`. Per-swipe data lives on individual chat message objects in the `swipe_store`.
|
||||||
|
|
||||||
|
5. **i18n fetch path**: Translations are fetched at `/scripts/extensions/third-party/rpg-companion-sillytavern/src/i18n/{lang}.json`. The hardcoded path in `i18n.js` must match the extension's actual URL path.
|
||||||
|
|
||||||
|
6. **Extension enable/disable**: The extension can be toggled from SillyTavern's Extensions tab. When disabled, all UI elements are removed from the DOM. When re-enabled, `initUI()` rebuilds everything.
|
||||||
|
|
||||||
|
7. **Mobile vs desktop**: Viewport breakpoint is 1000px. Below: FAB button + mobile tabs. Above: fixed panel + desktop tabs.
|
||||||
|
|
||||||
|
8. **External API key**: Stored in `localStorage` as `rpg_companion_external_api_key`, NOT in extension settings (for security).
|
||||||
|
|
||||||
|
9. **The `package.json` has no runtime dependencies**. The `devDependencies` (chokidar, fs-extra, glob) are only for the i18n validator.
|
||||||
|
|
||||||
|
10. **Deprecation status**: The README states this extension is deprecated in favor of Marinara Engine. The code shows a deprecation modal that displays on first load.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File reference
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `index.js` | Main entry point. All imports, UI init, event registration, startup logic. |
|
||||||
|
| `manifest.json` | SillyTavern extension manifest (name, loading order, JS/CSS files). |
|
||||||
|
| `style.css` | All CSS (~12,300 lines). Themes, animations, responsive breakpoints. |
|
||||||
|
| `template.html` | Main panel HTML template. Rendered by SillyTavern's `renderExtensionTemplateAsync`. |
|
||||||
|
| `settings.html` | Extensions tab settings drawer. Minimal — most settings are in the panel modal. |
|
||||||
|
| `src/core/state.js` | Central state module. All extension-wide variables live here. |
|
||||||
|
| `src/core/persistence.js` | Settings/chat data save/load, version migrations. |
|
||||||
|
| `src/core/config.js` | Extension name, path detection, default settings documentation. |
|
||||||
|
| `src/core/events.js` | SillyTavern event system wrapper with bulk register/unregister. |
|
||||||
|
| `src/core/i18n.js` | Translation loader and DOM applier. |
|
||||||
|
| `src/systems/integration/sillytavern.js` | All SillyTavern event handlers (message lifecycle, swipes, chat changes). |
|
||||||
|
| `src/systems/generation/promptBuilder.js` | Builds AI prompts for tracker generation. |
|
||||||
|
| `src/systems/generation/parser.js` | Parses AI responses to extract tracker JSON. |
|
||||||
|
| `src/systems/generation/apiClient.js` | API client for separate/external generation modes. |
|
||||||
|
| `src/systems/generation/injector.js` | Injects tracker prompts into SillyTavern's generation context. |
|
||||||
|
| `src/i18n/validator.js` | Node.js script for validating translation consistency. |
|
||||||
+28
-19
@@ -40,6 +40,30 @@ const DEFAULT_USER_STATS = {
|
|||||||
clothing: "None",
|
clothing: "None",
|
||||||
stored: {},
|
stored: {},
|
||||||
assets: "None"
|
assets: "None"
|
||||||
|
},
|
||||||
|
equipment: {
|
||||||
|
items: [],
|
||||||
|
slots: {
|
||||||
|
helmet: null,
|
||||||
|
ring1: null,
|
||||||
|
ring2: null,
|
||||||
|
ring3: null,
|
||||||
|
ring4: null,
|
||||||
|
ring5: null,
|
||||||
|
ring6: null,
|
||||||
|
ring7: null,
|
||||||
|
ring8: null,
|
||||||
|
ring9: null,
|
||||||
|
ring10: null,
|
||||||
|
necklace: null,
|
||||||
|
bodyArmor: null,
|
||||||
|
pants: null,
|
||||||
|
shoes: null,
|
||||||
|
gloves: null,
|
||||||
|
accessory1: null,
|
||||||
|
accessory2: null,
|
||||||
|
accessory3: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1016,23 +1040,7 @@ export function loadChatData() {
|
|||||||
if (!savedData) {
|
if (!savedData) {
|
||||||
// Reset to defaults if no metadata exists, then try to rebuild from message swipe data below.
|
// Reset to defaults if no metadata exists, then try to rebuild from message swipe data below.
|
||||||
updateExtensionSettings({
|
updateExtensionSettings({
|
||||||
userStats: {
|
userStats: cloneSerializable(DEFAULT_USER_STATS),
|
||||||
health: 100,
|
|
||||||
satiety: 100,
|
|
||||||
energy: 100,
|
|
||||||
hygiene: 100,
|
|
||||||
arousal: 0,
|
|
||||||
mood: '😐',
|
|
||||||
conditions: 'None',
|
|
||||||
// Use v2 inventory format for defaults
|
|
||||||
inventory: {
|
|
||||||
version: 2,
|
|
||||||
onPerson: "None",
|
|
||||||
clothing: "None",
|
|
||||||
stored: {},
|
|
||||||
assets: "None"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quests: {
|
quests: {
|
||||||
main: "None",
|
main: "None",
|
||||||
optional: []
|
optional: []
|
||||||
@@ -1052,9 +1060,10 @@ export function loadChatData() {
|
|||||||
clearThoughtBasedExpressionPortraits();
|
clearThoughtBasedExpressionPortraits();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore stats
|
// Restore stats — merge with defaults to preserve properties like `equipment`
|
||||||
|
// that may not exist in older saves
|
||||||
if (savedData?.userStats) {
|
if (savedData?.userStats) {
|
||||||
extensionSettings.userStats = { ...savedData.userStats };
|
extensionSettings.userStats = mergeWithDefaults(DEFAULT_USER_STATS, savedData.userStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore classic stats
|
// Restore classic stats
|
||||||
|
|||||||
@@ -525,7 +525,8 @@ export async function onMessageReceived(data) {
|
|||||||
// Remove the tracker code blocks from the visible message
|
// Remove the tracker code blocks from the visible message
|
||||||
let cleanedMessage = responseText;
|
let cleanedMessage = responseText;
|
||||||
|
|
||||||
// Note: JSON code blocks are hidden from display by regex script (but preserved in message data)
|
// Remove JSON code blocks (v3 format) — primary defense, works regardless of regex script
|
||||||
|
cleanedMessage = cleanedMessage.replace(/```(?:json|markdown)?\s*[\s\S]*?```/gim, '');
|
||||||
|
|
||||||
// Remove old text format code blocks (legacy support)
|
// Remove old text format code blocks (legacy support)
|
||||||
cleanedMessage = cleanedMessage.replace(/```[^`]*?Stats\s*\n\s*---[^`]*?```\s*/gi, '');
|
cleanedMessage = cleanedMessage.replace(/```[^`]*?Stats\s*\n\s*---[^`]*?```\s*/gi, '');
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ function generateItemId() {
|
|||||||
* Updates lastGeneratedData and committedTrackerData to include current equipment state
|
* Updates lastGeneratedData and committedTrackerData to include current equipment state
|
||||||
*/
|
*/
|
||||||
function updateEquipmentData() {
|
function updateEquipmentData() {
|
||||||
const equipment = extensionSettings.userStats.equipment;
|
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
|
||||||
const currentData = lastGeneratedData.userStats || committedTrackerData.userStats;
|
const currentData = lastGeneratedData.userStats || committedTrackerData.userStats;
|
||||||
|
|
||||||
if (currentData) {
|
if (currentData) {
|
||||||
@@ -153,7 +153,7 @@ export function showCreateModal() {
|
|||||||
* @param {string} itemId - ID of the item to edit
|
* @param {string} itemId - ID of the item to edit
|
||||||
*/
|
*/
|
||||||
export function showEditModal(itemId) {
|
export function showEditModal(itemId) {
|
||||||
const equipment = extensionSettings.userStats.equipment;
|
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
|
||||||
const item = equipment.items.find(i => i.id === itemId);
|
const item = equipment.items.find(i => i.id === itemId);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
@@ -363,7 +363,7 @@ export function saveEquipmentItem() {
|
|||||||
stats[attr] = Math.max(1, Math.min(20, val));
|
stats[attr] = Math.max(1, Math.min(20, val));
|
||||||
});
|
});
|
||||||
|
|
||||||
const equipment = extensionSettings.userStats.equipment;
|
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
|
||||||
|
|
||||||
// Migrate old types (ring1-ring10 -> ring, accessory1-3 -> accessory)
|
// Migrate old types (ring1-ring10 -> ring, accessory1-3 -> accessory)
|
||||||
migrateItemTypes(equipment.items);
|
migrateItemTypes(equipment.items);
|
||||||
@@ -423,7 +423,7 @@ export function saveEquipmentItem() {
|
|||||||
* @param {string} itemId - ID of the item to equip
|
* @param {string} itemId - ID of the item to equip
|
||||||
*/
|
*/
|
||||||
export function equipItem(itemId) {
|
export function equipItem(itemId) {
|
||||||
const equipment = extensionSettings.userStats.equipment;
|
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
|
||||||
const item = equipment.items.find(i => i.id === itemId);
|
const item = equipment.items.find(i => i.id === itemId);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
@@ -454,7 +454,7 @@ export function equipItem(itemId) {
|
|||||||
* @param {string} slotId - The slot to unequip from
|
* @param {string} slotId - The slot to unequip from
|
||||||
*/
|
*/
|
||||||
export function unequipItem(slotId) {
|
export function unequipItem(slotId) {
|
||||||
const equipment = extensionSettings.userStats.equipment;
|
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
|
||||||
const item = equipment.items.find(i => i.slot === slotId);
|
const item = equipment.items.find(i => i.slot === slotId);
|
||||||
if (item) {
|
if (item) {
|
||||||
item.slot = null;
|
item.slot = null;
|
||||||
@@ -473,7 +473,7 @@ export function unequipItem(slotId) {
|
|||||||
* @param {string} itemId - ID of the item to delete
|
* @param {string} itemId - ID of the item to delete
|
||||||
*/
|
*/
|
||||||
export function deleteItem(itemId) {
|
export function deleteItem(itemId) {
|
||||||
const equipment = extensionSettings.userStats.equipment;
|
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
|
||||||
|
|
||||||
// Unequip if currently equipped
|
// Unequip if currently equipped
|
||||||
const item = equipment.items.find(i => i.id === itemId);
|
const item = equipment.items.find(i => i.id === itemId);
|
||||||
@@ -497,7 +497,7 @@ export function deleteItem(itemId) {
|
|||||||
* @returns {Object} Map of attribute ID to total bonus value
|
* @returns {Object} Map of attribute ID to total bonus value
|
||||||
*/
|
*/
|
||||||
export function getEquipmentBonuses() {
|
export function getEquipmentBonuses() {
|
||||||
const equipment = extensionSettings.userStats.equipment;
|
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
|
||||||
const bonuses = {};
|
const bonuses = {};
|
||||||
const items = equipment.items || [];
|
const items = equipment.items || [];
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ function renderSlot(slotDef, item) {
|
|||||||
* @returns {string} Complete HTML for the equipment section
|
* @returns {string} Complete HTML for the equipment section
|
||||||
*/
|
*/
|
||||||
function generateEquipmentHTML() {
|
function generateEquipmentHTML() {
|
||||||
const equipment = extensionSettings.userStats.equipment;
|
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
|
||||||
const slots = equipment.slots || {};
|
const slots = equipment.slots || {};
|
||||||
const items = equipment.items || [];
|
const items = equipment.items || [];
|
||||||
|
|
||||||
|
|||||||
@@ -12183,7 +12183,7 @@ body.documentstyle .rpg-inline-thoughts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rpg-equipment-modal-content {
|
.rpg-equipment-modal-content {
|
||||||
background: var(--SmartThemeDialogBgColor);
|
background: var(--SmartThemeDialogBgColor, #1e1e2e);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
|
|||||||
Reference in New Issue
Block a user