Multicharacter Database and Saved Data
Three tables, all created for you on the first start. No SQL file to import, and nothing of VORP's is replaced — the characters themselves stay where VORP already keeps them.
VORP oxmysql No SQL to importWhere each piece of data lives
| Data | Where | Survives a reinstall |
|---|---|---|
| Characters, appearance, clothing | VORP's own tables | Yes |
| Partner pairings | character_partners | Yes |
| Playtime per character | character_playtime | Yes |
| Scenes saved in the Scene Creator | vorp_character_scenes | Yes |
| Pose editor adjustments | partner_poses.json in the resource folder | Only if you keep the file |
| Each player's own screen settings | That player's PC | Yes, on that PC |
| Locations, poses, emote library, languages | The config_*.lua files | Only if you keep the files |
Two of those rows are the ones people get wrong. The pose editor writes to a file inside the resource folder, which an update overwrites. Player settings are not on your server at all.
The three tables
Each one is created with CREATE TABLE IF NOT EXISTS inside oxmysql's MySQL.ready callback, so the tables appear the first time the resource starts with a working database connection. Running the resource again changes nothing.
oxmysql has to start first
The table creation runs when oxmysql reports ready. If vorp_character starts first, the queries have nothing to talk to. Keep ensure oxmysql above ensure vorp_character in server.cfg — see Installation.
character_partners
Written by the partner system. One row per pairing.
| Column | Type | Holds |
|---|---|---|
id | INT(11) auto-increment | Primary key |
char_a | INT(11), unique | One character's charidentifier |
char_b | INT(11), unique | The other character's charidentifier |
type | VARCHAR(16), default friend | lover or friend |
created_at | TIMESTAMP, defaults to now | When the pair was made |
Both character columns carry a unique key, which is how the rule "one partner per character" is enforced in the database rather than only in the code. A second pairing for a character that already has one is refused by the insert.
The table is only created when ConfigSelector.Partner.enabled is true. If the create fails, the partner system switches itself off for that session and prints the reason to the console rather than erroring on every request. Behaviour and settings are on Partner system.
character_playtime
| Column | Type | Holds |
|---|---|---|
charidentifier | INT(11) | Primary key |
seconds | INT(11), default 0 | Total seconds played, accumulated |
lastlogin | TIMESTAMP, nullable | When the row was last written |
Writes use INSERT ... ON DUPLICATE KEY UPDATE seconds = seconds + VALUES(seconds), so the column accumulates instead of being overwritten. A flush happens every ConfigSelector.Playtime.saveInterval seconds (default 300), when the player disconnects, and when the resource stops — a restart mid-session does not lose the time already played.
Because every write also sets lastlogin to the current time, that column tracks the most recent save rather than strictly the moment of login. For a character sitting in the world it moves forward every five minutes.
When the selection screen opens, the playtime for every slot is read in a single query. A character with no row yet has never been played, and its card shows ConfigSelector.Playtime.newLabel — "New" by default.
The table is only created when ConfigSelector.Playtime.enabled is true, and a failed create switches playtime off for that session.
vorp_character_scenes
| Column | Type | Holds |
|---|---|---|
name | VARCHAR(64) | Primary key, the cleaned scene name |
enabled | TINYINT(1), default 0 | 1 when the scene is LIVE |
data | LONGTEXT | The whole scene as JSON |
updatedby | VARCHAR(64), nullable | Who saved it last |
updatedat | TIMESTAMP | Set on insert and on every update |
The name of this table is not fixed in the SQL. It comes from ConfigCreator.Table in config_scenecreator.lua, which defaults to vorp_character_scenes, and the CREATE TABLE statement is a Lua format string with a %s where the name goes. The same is true of every read, write and delete the Scene Creator performs.
Renaming ConfigCreator.Table does not move your scenes
Change the value and the next start creates a second, empty table under the new name. The old table and every scene in it are still there, no longer read. Rename the table in your database first, or copy the rows across, then change the config value.
data holds everything a scene is: the cameras, the character slots and their poses, weather, time, music, props, wagons, NPCs and the camera travel path. The server trims an incoming scene to ConfigCreator.Limits before writing — 8 character slots, 60 props, 12 vehicles, 20 NPCs and 20 travel waypoints — so a row cannot grow past those ceilings no matter what a client sends.
Scene names are cleaned before use: lowercased, anything that is not a letter, a digit or _ replaced with _, and cut to 64 characters to match the primary key. A name that comes out empty or entirely underscores is rejected. Saving under a name that already exists overwrites that row, which also means two names that clean down to the same string are the same scene.
On start the server reads every row into a cache, and reloads that cache after each save, toggle and delete. A row whose data is not valid JSON is skipped with a console line naming it, rather than taking the rest of the list down with it. The ready line looks like this:
[vorp_character:creator] ready, 3 scene(s) stored, /scenecreator to openEditing data by hand will not reach players
The cache is only reloaded when the resource starts or when the Scene Creator itself writes. Change a row in a database tool and you need to restart the resource for anyone to see it. Saves made in the editor reach players immediately. Full detail on Scene Creator.
Characters stay in VORP's tables
This resource replaces the stock vorp_character folder, but it does not replace VORP's character data. Characters, their appearance, their clothing and their outfits are read through vorp_core exactly as before, from the tables VORP already created.
That has three practical consequences:
- Installing this on a running server keeps every existing character. There is no migration step and no conversion.
- Anything that reads VORP characters keeps working. Other resources querying VORP's tables are untouched.
- The three tables above are additions. Drop all three and you lose partner pairings, playtime and saved scenes; you do not lose a single character.
More on the framework side at VORP.
What is not in the database
Player settings — on each player's own PC
Every choice a player makes in the O settings panel is stored client side, in RedM's resource key-value store, under one key:
vorp_character_selector_settings_v1The value is a single JSON object. It carries the theme, the menu style, the button plate, cinematic bars, the keybind hints, sound and music switches, the sound volume, the chosen language, weather and time override, both zoom sliders, character rotation, camera orbit and height, the framing preset, the screen filter, the music track, and a poses map of character identifier to the pose that character was last left in.
That last pose is stored by pose name, not by position in the list, so adding entries to animations.lua or reordering it does not disturb anyone's saves. Renaming an entry is what invalidates them.
These settings are not on your server, and you cannot edit them
The key lives in the player's own RedM installation. It does not follow them to another computer, it is not backed up by your database dump, and there is no table for you to look in or change. A player on a new PC starts from your defaults again.
What you control is the starting point: ConfigSelector.UI in config_selector.lua supplies the defaults, and a stored value only overrides a key that exists there. See Configuration.
Two further details worth knowing:
- Unknown keys are rejected. A setting name the resource does not recognise is logged and dropped, so a stale key from an older version cannot poison the table.
- A removed language falls back. The stored language is checked against
ConfigSelector.Locales; if that table is gone, the player falls back toLanginconfig.lua. Old English-language names such asGermanorRussianare mapped to the current locale keys. See Languages.
The reset button in the settings panel deletes the key outright and reloads from your defaults, which is the fix for a player whose camera or filter has ended up somewhere unusable.
Pose editor saves — a file in the resource folder
The partner pose editor does not use the database. Saves go to partner_poses.json in the resource root, written on the server with SaveResourceFile, and each save is pushed to every connected client at once. No restart.
{
"lover": {
"Slow dance": [0.0, 0.6, 0.0, 180.0]
},
"friend": {
"Chat": [0.0, 1.05, 0.0, 180.0]
}
}Poses are keyed by the kind (lover or friend) and then by the pose's label. The four numbers are the same order as the attach vector in animations.lua — right, forward, up, heading — rounded to three decimals, with the heading to one. A stored entry is used instead of the attach numbers in animations.lua for that pose.
The server validates every save before it writes: the pose label has to exist in ConfigSelector.Partner.animations, and the numbers have to be inside the range the editor can actually produce. If the file is not valid JSON it is ignored with a console line, and the config values are used. Working defaults are 0.60 m in front for a lover pose and 1.05 m for a friend pose. The editor itself is covered on Pose editor.
Every save also prints a paste-ready line
Each save prints its attach line to F8. Pasting that into animations.lua moves the value into your config, where an update cannot take it away with the file.
Config files, not tables
Three things people reasonably expect to find in a table are config instead: the shipped locations (config.lua and config_scenes.lua), the player-facing pose list (animations.lua) and the Scene Creator's 305-pose browser (the emote library, in config_emotes.lua). All of them are plain readable Lua — nothing in this resource is escrowed — and all of them are files you have to keep across an update.
What a character delete removes
Deleting a character runs VORP's own removal, then two clean-up passes this resource adds.
VORP removes the character. The delete webhook in
logs.luafires if you have filled in its URL.The cascade runs. Every entry in
ConfigSelector.Delete.cascadegets aDELETE ... WHERE column = ?for that character identifier.The partner row is cleared, if
ConfigSelector.Partner.clearOnDeleteistrue, which is the default. Any pairing naming that character on either side is removed, so the other half is freed rather than left pointing at a character that no longer exists.
The shipped cascade list is fourteen tables:
| Table | Column |
|---|---|
character_playtime | charidentifier |
outfits | charidentifier |
loadout | charidentifier |
character_inventories | character_id |
character_weapons | charidentifier |
banking | charidentifier |
housing | charidentifier |
stables | charidentifier |
mailbox | charidentifier |
billing | charidentifier |
walkanim | charidentifier |
metabolism | charidentifier |
vorp_mining | charidentifier |
vorp_fishing | charidentifier |
A table you do not have is skipped. Each delete runs on its own and a failure — missing table, wrong column name — is counted rather than raised. You do not have to prune the list to match your server, and nothing breaks if you run none of those scripts. With /selectordebug on, the pass reports how many tables it cleared and how many it skipped.
Adding your own is a row in the same shape:
-- config_selector.lua
ConfigSelector.Delete = {
enabled = true,
requireNameConfirm = true,
cascade = {
{ table = "character_playtime", column = "charidentifier" },
{ table = "my_script_data", column = "charidentifier" },
},
}Set ConfigSelector.Delete.enabled = false and the cascade does not run at all — VORP still removes the character, but its rows elsewhere are left behind. requireNameConfirm is on by default and makes the player type the character's first name before the delete goes through.
Rows removed by the cascade are gone
There is no soft delete and no recycle bin. If your server lets players delete characters and you care about the money, housing or inventory attached to them, your protection is a database backup, not this script.
Checking what is stored
From a database tool or an adminer panel:
SELECT charidentifier, seconds, lastlogin
FROM character_playtime
ORDER BY seconds DESC
LIMIT 10;
SELECT char_a, char_b, type, created_at
FROM character_partners;
SELECT name, enabled, updatedby, updatedat
FROM vorp_character_scenes;Leave data out of that last query unless you mean to read a whole scene — it is a LONGTEXT column holding the full JSON.
/selectordump in-game prints the selector's own view of the same things: the config in force, the staged scene, the player's settings, the characters, the partner and the Scene Creator state. That is usually the faster first check when a number on screen disagrees with a number in a table. More in Troubleshooting.
Backing up before an update
An update replaces the resource folder. It does not touch your database, so scenes, partners and playtime come through on their own. What you have to carry across are files.
Keep copies of:
| File | Holds |
|---|---|
config.lua | The 11 shipped locations, Lang, shops |
config_scenes.lua | Exported scenes, appended to the location list |
config_selector.lua | Every selection-screen setting, the cascade list, partner settings, the 7 locales |
config_scenecreator.lua | Scene Creator settings, including ConfigCreator.Table |
config_emotes.lua | The emote library, if you added to it |
config_shops.lua | Clothing store setup |
animations.lua | The player-facing pose lists |
logs.lua | The delete webhook |
partner_poses.json | Pose editor adjustments |
Copy those files somewhere outside the resource folder.
Replace the resource folder with the new version.
Move your settings across by hand, key by key, rather than dropping the old files back in. A new version may have added settings that your old file does not contain, and an old file is missing them silently.
Start the server and watch the console. The three tables already exist, so the
CREATE TABLE IF NOT EXISTSstatements do nothing.
A dump of the three tables, for the belt-and-braces version:
mysqldump --single-transaction DBNAME \
character_partners character_playtime vorp_character_scenes \
-u DBUSER -p > vorp_character_backup.sqlName only the tables you actually have — character_partners and character_playtime are not created if you turned those features off before the first start. And back up VORP's character tables separately; they are the ones that matter most and they are not this resource's.
The Export tab is the portable backup for a scene
A database dump is the right tool for the whole installation. For one scene you care about, use the Scene Creator's Export tab and paste the result into config_scenes.lua — that is readable, lives in a file you are already keeping, and works on any server running the resource. It carries the cameras, slots, poses, weather, time and music, but not props, wagons, NPCs or camera travel; those exist only in the database. See Scene Creator.
Still stuck? Discord.