Skip to content

The Partner System ​

VORP

A pairing ties two characters together, not two accounts. Once it exists, the partner is staged beside the character on the selection screen, dressed as that character, and the two share one pose.

Everything on this page lives in ConfigSelector.Partner (config_selector.lua), with the duo poses themselves in animations.lua. All of it is plain readable Lua.

Pairing two characters ​

The panel opens with P on a character slot. The key is ConfigSelector.Keys.partner, and the on-screen hint follows whatever you set it to. With Partner.enabled = false the button, the hint and the key all disappear.

  1. Both players open the character screen. This is not optional — the panel only lists players who have it open right now.
  2. One of them presses P and types the other player's name, or their server ID, into the search box. The list holds player names as the server sees them, not character names, and it matches on any part of the name. An ID has to match exactly.
  3. On the row that comes back, press Couple or Friend. The request is sent for the character that is currently highlighted.
  4. The other player gets a notification and a count on the Incoming tab. They accept or decline.
  5. On accept, both sides get a fresh character list and the scene restages. The partner appears without anyone reopening the screen.

Both halves use the character highlighted at that moment

Sending binds the character you were on when you pressed Couple or Friend, and accepting binds the character the accepting player is on. Someone who scrolls to another slot and then accepts has paired that slot instead. Highlight the character you mean before you answer.

The panel has three tabs:

TabHolds
SearchEvery other player at the character screen, sorted by name, with the Couple and Friend buttons
IncomingRequests sent to you, with Accept and Decline. The tab carries a count badge
SentRequests you have sent, with Withdraw

The panel's subtitle shows your own server ID. That is the number to read out when someone wants to search for you by ID rather than by name.

Unpair sits at the top of the panel whenever the highlighted character has a partner. It ends the pairing from either side, and the other player's screen updates too if they are on it.

One partner per character ​

char_a and char_b are both unique keys in the database, so a character can be in exactly one pairing. A player with five characters can pair all five, each with somebody different.

While a character already has a partner, the search rows show Already partnered instead of the two buttons. Accepting a request that arrived before a pairing was made is re-checked on the server and refused — the request is dropped rather than quietly overwriting what exists.

Why a request is refused ​

Every send and every accept is validated on the server. The panel plays its error sound and re-reads the state; the reason is in the response:

ReasonWhat happened
not_at_selectorThe other player is online but not on the character screen
offlineThey left between the list being built and the button being pressed
alreadyOne of the two characters was paired in the meantime
duplicateYou already have an open request to that player
not_yoursThe character identifier sent did not belong to the sender
typeThe requested type is not in allowedTypes
selfThe target was the sender

Requests are held in memory, not in the database. A restart clears them, and so does either player disconnecting. Expiry is swept every 5 seconds, and the sender's panel refreshes when one lapses.

What pairing changes on screen ​

The partner pedBuilt from the partner character's own skinPlayer, compPlayer and compTints rows, so it is that character with that character's clothes
Online or notThe data comes from the database. The partner shows up whether or not that player is connected
ECycles the duo poses instead of the character's solo poses
The pose pickerLists the duo poses for that pairing type, with the current one marked
The cameraFrames both people: a side-on shot aimed at the middle of the pair, stepping back until both fit with 0.45 m of room either side
The cardCarries a heart for a couple and a dot for a friend, and the stats panel names the partner

The partner ped is placed, never attached. Attaching would make it inherit the character's height, and the resource works in foot-level terms deliberately. It is also invincible, cannot be targeted, and ignores non-temporary events, so nothing in the world walks it off its mark.

The last duo pose is remembered per character in the client's own settings, so reopening the screen brings back the pose that character was last seen in.

Paired characters only turn while they are on screen

The rotation setting in Settings turns every staged character. A paired one is skipped unless it is the highlighted slot, because moving the partner to match cancels the clip it is running. Turn the slot you are looking at and the partner swings round with it; the camera re-aims afterwards.

With ConfigSelector.Scene.showOnlySelected at its default of true, a partner is visible exactly when its own character is — so you see one couple at a time, not the whole rotation.

Settings ​

lua
-- config_selector.lua
ConfigSelector.Partner = {
    enabled        = true,
    requestTimeout = 60,
    allowedTypes   = { "lover", "friend" },
    sync           = true,
    freeOffset     = vector4(0.85, 0.35, 0.0, 200.0),
    freePoseSource = "global",
    allowPropPoses = true,
    clearOnDelete  = true,
}
SettingDefaultDoes
enabledtrueThe whole system. false hides the P button and the hint, and the server refuses sends, replies and unpairs
requestTimeout60Seconds a sent request stays open
allowedTypes{ "lover", "friend" }Which pairing types the server accepts
synctrueOne shared duo pose, or a partner that poses on its own. See below
freeOffsetvector4(0.85, 0.35, 0.0, 200.0)Where a free partner stands. Read only when sync = false
freePoseSource"global""global" uses ConfigSelector.Animations.list; "character" uses the character's own list. sync = false only
clipOffsets2 entriesPer-clip height corrections, in metres. Positive lowers the ped
allowPropPosestrueKeep duo poses marked needsProp in the list
clearOnDeletetrueWipe the pairing when either character is deleted
editorenabled, command, groups, aceThe partner pose editor. See Pose editor

allowedTypes is enforced on the server only

The panel always draws both the Couple and the Friend button. Cut "lover" from allowedTypes and the Couple button is still there — it just fails with the error sound when pressed. If you want one type gone from the interface, remove its button from the page as well.

What sync actually switches ​

sync = true (default)sync = false
Pose sourceConfigSelector.Partner.animations, by pairing typeThe solo list named by freePoseSource
PlacementThe pose's own attach, or the type defaultfreeOffset, the same spot for every pose
ContactDances, bows and kisses line upNone. Two people standing near each other
What E movesBoth halves, onto the next duo poseThe partner's own pose. The character keeps the pose its slot staged

sync = false is the safer setting on a location whose marks are tight or uneven: a free partner never needs its clip to line up with the character's, so nothing can be visibly out by half a metre. A missing sync key counts as true.

The attach defaults ​

Where the partner stands, for a duo pose that does not carry its own attach:

lua
-- animations.lua
ConfigSelector.Partner.attach = {
    lover  = vector4(0.0, 0.60, 0.0, 180.0),
    friend = vector4(0.0, 1.05, 0.0, 180.0),
}

The vector is right, forward, up, heading, measured in the character's own space. A couple stands 0.60 m in front, a friend 1.05 m in front, and heading 180 turns the partner to face your character. Every shipped duo pose carries its own attach, so these two are the fallback for poses you add yourself.

Where the duo poses come from ​

ConfigSelector.Partner.animations in animations.lua holds two lists, picked by pairing type: 8 poses for lover and 12 for friend, 20 in total. Three kinds appear in them:

TypeWhat playsContact
emoteTwo clips from one dictionary, played togetherYes. This is the only kind with real contact, such as the slow dance
kitAn RDO emote kit action on each ped, replayed on repeatMsNo
scenarioA world scenario on each pedNo

Several entries hold female variants (primaryFemale, partnerFemale), and the slow dance carries swapByGender = true so a female character takes the woman's track and her partner the man's.

Two filters sit in front of the list. allowPropPoses = false drops entries marked needsProp = true — the shipped At the railing and Campfire friend poses were authored around furniture the spawn marks do not have, and flagging them is how you retire them without deleting them. ConfigSelector.ScenariosOnly = true drops every pose that is not a scenario on both halves, which is the blunt fix for floating.

The full list, the entry shapes and the mechanics of playing two clips in step are on Poses. To move a partner by hand instead of guessing numbers, use the Pose editor: it writes partner_poses.json, which overrides the attach values written here and reaches every player at once.

This is not the Scene Creator's library

The 305-pose browser documented in Emote library belongs to the Scene Creator and has its own paired category. Adding a row there does not put a pose in front of players pressing E.

The data ​

One table, created with CREATE TABLE IF NOT EXISTS when the resource starts. There is no SQL file to import.

sql
CREATE TABLE IF NOT EXISTS `character_partners` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `char_a` INT(11) NOT NULL,
    `char_b` INT(11) NOT NULL,
    `type` VARCHAR(16) NOT NULL DEFAULT 'friend',
    `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uniq_char_a` (`char_a`),
    UNIQUE KEY `uniq_char_b` (`char_b`)
);
ColumnHolds
char_aThe character that sent the request. Unique
char_bThe character that accepted. Unique
typelover or friend

A row is read in both directions, so it does not matter which side of it a character sits on. The unique keys are what enforce one partner per character — a second pairing cannot be inserted even if something tries.

With clearOnDelete = true, deleting a character removes the row it appears in, on either side. The other character is left unpaired and free to pair again. Set it to false and the row survives the deletion, which leaves a pairing pointing at a character that is gone.

If the CREATE fails, the partner system turns itself off for that session and prints the error on the server console rather than failing on every request.

Pairings are the only partner data in the database. The per-character last pose is client-side, in the KVP settings, and pose editor saves live in partner_poses.json in the resource folder — see Database.

Troubleshooting ​

The two players cannot find each other ​

The search list is built from players who have the character screen open at that moment. Nobody who is in the world, in the character creator, or on a loading screen appears in it.

  1. Both players press Esc or Backspace out to the character screen and leave it open.
  2. Re-open P. The list is rebuilt from scratch each time the panel opens.
  3. Compare server IDs rather than names. The subtitle of each player's own panel shows their ID, and an ID search has to match exactly.
  4. If one player sees the other but not the reverse, the one who cannot see is probably looking at a stale panel — close it and open it again.

A request also lapses after requestTimeout seconds, 60 by default. Nothing is wrong if a request simply vanished while the other player was reading it; send it again, or raise the timeout.

The partner floats or sinks ​

A duo pose that plays a raw clip brings the height of the set it was authored on with it. The resource measures it and tells you the number instead of moving the ped, because moving a ped cancels its task.

  1. Set ConfigSelector.Debug = true and restart the resource.
  2. Open the character screen on the paired character and put it in the pose that looks wrong.
  3. Run /selectordump in the F8 console. Use /selectoroverlay for the same reading live on screen while you step poses with /selectorposenext.
  4. Read the partner section of the dump.
text
-- heights (the bug lives here) --
  partner foot z         -0.619
  partner gap            0.420
  VERDICT                FLOATING 0.42m ABOVE the ground
  FIX                    ["script_re@roaming_thugs@laramie/thug_b_argue_idle"] = 0.420,

The FIX line is ready to paste into clipOffsets:

lua
clipOffsets = {
    ["KIT_EMOTE_GREET_FANCY_BOW_1"] = 0.128,
    ["MP_LOBBY_SCENARIO_06"]        = 0.116,
    ["script_re@roaming_thugs@laramie/thug_b_argue_idle"] = 0.420,
}

A positive value lowers the ped. The key is looked up as dictionary/clip first and then as the clip name on its own, which is what makes a bare key apply to that clip in any dictionary. Scenarios and kit emotes have no dictionary, so they are keyed by their own name — the two shipped entries above are one of each. The table lives under Partner but is read for solo character poses too.

The alternative is the Pose editor, which is the better tool when the partner is standing at the wrong distance or angle rather than the wrong height: you nudge the ped with the arrow keys, press Enter, and the saved position reaches every player immediately.

A hovering character, rather than a hovering partner

Then the pose is a solo one and clipOffsets still applies, but start at Troubleshooting for the full sequence.

Nothing happens when P is pressed ​

In order:

  1. Is Partner.enabled still true? When it is false the button is hidden rather than disabled.
  2. Is a character actually highlighted? The panel needs one — it does not open on an empty slot.
  3. Did the character_partners table get created? A database error turns the system off for the session and prints on the server console.
  4. Has ConfigSelector.Keys.partner been changed to something else? The on-screen hint shows the key that is really bound.

Permissions for the pose editor are separate from all of this: VORP groups in Partner.editor.groups, default admin, or the ace in Partner.editor.ace, default command, which txAdmin admins already hold. Groups come from the character's group — see VORP.

Still stuck, with the dump output in hand, ask on Discord.

Documentation for RedMorrow. Scripts are licensed per server — redistribution is not permitted.