The Join Flow#
From the moment a player connects to the moment they stand in the world, Multicharacter runs every step. Each one can be switched off or tuned in config/main.lua.
Step | What the player sees | Controlled by |
|---|---|---|
1. Loading screen | Stays up while the selection room streams in behind it. |
|
2. HUD setup | First join only, with qs-interface: the player configures their HUD. | Automatic |
3. Landing page | Logo, server name, rules, staff, changelogs and the Ready to start button. |
|
4. Scenario | A short cinematic scene with the player's own character. |
|
5. Selection room | Every character in its spot, with a card to enter, delete or create. |
|
6. Identity | New characters only: name, nationality, gender, birth date and height. |
|
7. Appearance | Your clothing script's own creator. | Your appearance script |
8. Arrival cutscene | New characters only: the plane intro and a logo transition. |
|
9. Spawn selector | A camera over the map with every place the character may wake up. |
|
Instancing. From the landing page until a character is picked, each player sits in a private routing bucket (
SelectionBucketOffset+ server id, 1000 by default), so nobody walks through anyone else's scene. They return toDefaultRoutingBucketafterwards. If another resource already handles instancing, setConfig.InstanceSelectionScene = false.
The Landing Page#
The first screen after loading is your server's front door. Everything on it is written in the config:
Config.ServerName = 'QUASAR ROLEPLAY'
Config.LogoFile = 'logo.webp'
Config.LandingPageRules = {
'Players must respect each other',
'RDM and VDM are prohibited',
'Read the rules before starting to play',
}
Config.LandingPageStaff = {
{ name = 'Admin 1', role = 'Founder', discord = 'admin1#0001' },
{ name = 'Moderator 1', role = 'Moderator', discord = 'mod1#0003' },
}
Config.DiscordUrl = 'https://discord.gg/your-server'
Config.YouTubeUrl = 'https://www.youtube.com/@your-channel'
Config.TebexUrl = 'https://your-store.tebex.io' -- also opened by the Buy Slot button
Config.Changelogs = {
{
version = '2.0.5',
title = 'Major Update 2.0.5',
date = '2024-01-15',
image = 'https://…/banner.jpg',
description = { 'Added new character customization features', 'Fixed various bugs' }
},
}
Config.KeyboardLayout = {
{ key = 'F', description = 'Enter Vehicle' },
{ key = 'M', description = 'Map' },
}Player settings#
A settings panel lets each player tune the menu for themselves:
Setting | What it does | Server default |
|---|---|---|
Music | Any YouTube link as background music, with its own volume, or no music at all. |
|
Camera animation | How the camera moves between characters: default, smooth, cinematic or dynamic. |
|
Camera filter | A colour grade over the room: none, cinematic, vintage, blackwhite, warm, cold, neon, dreamy, dramatic, golden, midnight, foggy, retro, saturated or desaturated. |
|
Streamer mode | Hides names and money on the character cards. | Off |
Performance mode | A lighter interface for weak PCs. |
|
Remove an entry from
CameraAnimationsorCameraFiltersto stop players picking it.
Cinematic Scenarios#
When the player presses Ready to start, the screen fades and one scenario plays with a copy of their own character before the selection room opens. If the player has no character yet, Config.ScenarioFallbackPed is used instead.
Scenario | Scene | Settings table |
|---|---|---|
| A drink served by a bartender at the Vanilla Unicorn bar. |
|
| A private dance in the club. |
|
| The character shakes a spray can and tags a wall. |
|
The scene is picked at random, weighted by weight. A scenario missing from Config.Scenarios counts as enabled with weight 1 — that is why graffiti plays by default. To change the mix:
Config.EnableScenarios = true -- false skips straight to the selection room
Config.Scenarios = {
drink = { enabled = true, weight = 2 }, -- twice as likely
lapdance = { enabled = false, weight = 1 }, -- never plays
graffiti = { enabled = true, weight = 1 },
}Each settings table holds the scene position, the extra ped (bartender, dancer), the animation length and a camera that glides from startPosition to endPosition over interpDuration milliseconds, changing FOV on the way.
The Selection Room#
Every slot is a spot in the world where that character stands, sits or leans with its own animation. Empty slots show a translucent silhouette. Three settings build the room:
Setting | Defines |
|---|---|
| One entry per slot, numbered from 0: where the ped stands, which way it faces, where it looks, its animation (male |
| The cameras of the room. Each one lists the slots it frames; the camera glides between them as the player browses. |
| Where each character card sits on screen, in CSS units ( |
Config.CharacterPositions = {
[0] = {
coords = vec3(-1009.081299, -475.674713, 48.495923), -- where the ped stands
heading = 201.259842,
lookAt = vec3(-1005.929688, -478.549438, 50.021973),
animation = {
[0] = { dict = 'anim@amb@office@boardroom@boss@male@', anim = 'base' }, -- male
[1] = { dict = 'anim@amb@office@boardroom@boss@male@', anim = 'base' } -- female
},
tebex = false, -- true = locked until bought or given
-- prop = 'prop_beer_pissh', -- optional prop in hand
},
-- [1], [2], [3] ...
}
Config.CharacterCameraPositions = {
[0] = { coords = vec3(-1008.65, -477.30, 50.13), pointAt = vec3(-1008.67, -474.39, 49.62), slots = { 0 } },
[1] = { coords = vec3(-1009.01, -476.55, 50.13), pointAt = vec3(-1007.05, -477.89, 50.02), slots = { 1, 2 } },
[2] = { coords = vec3(-1005.28, -476.46, 50.02), pointAt = vec3(-1004.95, -476.04, 50.02), slots = { 3 } },
}Each card shows the character's name, birth date, job and grade, cash and bank, with Enter game and Delete. Empty cards show New character, and locked ones show Buy slot plus a field for a Tebex transaction ID.
The number of slots is the number of entries in
Config.CharacterPositions— four by default. Adding, locking and selling slots is covered on Character Slots and Tebex.
Creating a Character#
The identity form#
Choosing an empty slot opens the identity form in front of a live preview ped: first name, last name, nationality (searchable list of countries), gender (the preview switches between male and female), birth date and height (slider, 170 cm by default). Players must be between 18 and 80 years old.
Framework | Where the identity is saved |
|---|---|
ESX | Through |
QBCore / Qbox | Built-in — no identity resource needed. Saved to |
qs-core | Built-in, saved to qs-core's |
The appearance creator#
After the identity, your appearance script's own creator opens — qs-appearance, illenium-appearance, esx_skin, qb-clothing, qs-clothing or rcore_clothing — and the saved outfit is what the character wears in the selection room from then on.
On ESX with esx_skin you can skip the clothing menu and dress every new character in a fixed outfit instead:
Config.UseDefaultClothing = GetConvarBool('qs:showcase', false) -- true = no clothing menu
Config.DefaultClothes = {
male = { tshirt_1 = 55, torso_1 = 70, torso_2 = 5, arms = 17, pants_1 = 34, shoes_1 = 28, shoes_2 = 1, mask_1 = 3 },
female = { tshirt_1 = 58, tshirt_2 = 1, torso_1 = 65, torso_2 = 5, arms = 23, pants_1 = 75, shoes_1 = 11, shoes_2 = 2, mask_1 = 3 },
}Config.NakedPlayerClothes sets the basic clothes of the empty-slot silhouettes and of preview peds that have no saved outfit yet.
Starter items#
Config.StarterItems = {
{ item = 'phone', amount = 1 },
{ item = 'water', amount = 2 },
}Given once, to new characters only. On ESX and qs-core this list is used. On QBCore and Qbox the framework's own starter items (QBCore.Shared.StarterItems or Qbox's starterItems) take priority, and ID cards and driver licences get the character's details. Set Config.StarterItems = {} to give nothing.
The Arrival Cutscene#
Brand-new characters arrive in Los Santos the way GTA Online players do: the plane intro with fellow passengers, then a glitch transition into your logo, then the spawn selector. It never plays for existing characters.
Config.EnableNewCharacterCutscene = true
Config.NewCharacterCutscene = {
SkipKey = 38, -- E
SkipText = '~INPUT_CONTEXT~ Skip Scene',
}Config.EnableSpawnScene = true adds a second, separate scene for returning players: they wake up on the beach after spawning.
The Spawn Selector#
The camera rises over the map and every spawn the character may use is marked on screen. The player browses with the arrows, previews the character standing at the spot with Showcase, and confirms. The list is built from:
Last location — first in the list for existing characters.
Your spawn points from
Config.SpawnLocations, optionally restricted by job.Owned houses with qs-housing.
Starting apartments for new characters, with qs-apartments, qb-apartments or Qbox properties.
Config.SpawnLocations = {
{
spawnCoords = vec4(450.82, -651.27, 27.42, 266.39),
label = 'Station',
address = 'Integrity Way',
},
{
spawnCoords = vec4(426.23, -982.10, 29.70, 86.68),
label = 'Police Department',
address = 'Atlee St',
jobs = { 'police', 'sheriff' }, -- any grade of these jobs
-- jobs = { police = 2, ambulance = 0 }, -- job = minimum grade
},
}Entries without jobs are open to everyone — keep at least one. A character with nowhere to go is placed at Config.Spawn.
Option | Default | Effect |
|---|---|---|
| true | Use this selector. |
| true | A character that logged out dead can only respawn where it fell — no escaping a death by relogging. |
| true | When only one spawn is available, enter it without showing the selector. |
| false | Existing characters with a saved position spawn there directly. New characters still get the selector. |
| true | An expanding ring and an entrance sound where the player appears. |
| false | High-quality depth of field in the showcase. Heavy on the GPU. |
qs-tutorial — if it is started, its tutorial runs before the spawn selector opens, and the selector rebuilds its camera if the tutorial removes it.
Logging Out and Deleting#
With Config.BackCharCommand = true, any player can type /qs:logout to save their character and return to the selection room.
The Delete button removes the character from your framework and then from every table that has an identifier, owner, citizenid or creator column matching it — vehicles, houses, phone data and so on — in one transaction.
Look and Feel#
Every colour of the interface is a token in Config.UITheme. Change them to your brand without touching the interface files:
Config.UITheme = {
textPrimary = '#E2E8F0',
textSecondary = '#94A3B8',
accent = 'rgba(0, 163, 255, 0.9)',
accentSoft = 'rgba(0, 163, 255, 0.2)',
success = '#22C55E',
warning = '#F59E0B',
danger = '#EF4444',
panelBackground = 'linear-gradient(135deg, rgba(15, 23, 42, 0.95) 0%, rgba(30, 41, 59, 0.95) 100%)',
-- start button, slot and button gradients follow
}Money on the cards is formatted for your country and currency:
Config.Intl = {
locales = 'es-ES',
options = { style = 'currency', currency = 'EUR', minimumFractionDigits = 0 }
}Config.Music plays a local track from web/sounds/ — drop your background.mp3 there, or point Sound at another file:
Config.Music = {
Enable = true,
Sound = 'background.mp3',
Volume = 0.1 -- 0.0 to 1.0
}Performance and Timeouts#
Every wait in the join flow has a limit, so a slow PC or a heavy server never leaves a player stuck on a black screen. Raise these on servers with lots of streamed clothing or a slow database — never remove them.
Option | Default | Limits |
|---|---|---|
| 15000 | How long one ped model may take to load. |
| 15000 | The wait for the freemode model at the first spawn. |
| 27000 | Start the menu anyway if spawnmanager never reports the spawn. |
| 120000 | Release the loading screen if the interface has not answered. |
| 8000 | Re-grab the mouse cursor if the loading screen drops it while closing. |
| 30000 | Wait for qs-interface to start. |
| 90000 | Maximum time for the first-time HUD setup. |
| 20000 | Keep the loading screen up while the room streams in. |
| 45000 | Show the room with whichever peds are ready. |
| 10000 | Wait for the ground to load at the chosen spawn. |
| 1500 | ESX: reopen the menu after logout if ESX never confirms the save. |
| 15000 | ESX: wait for esx_identity to register a new character. |
All values are in milliseconds. Config.DisableLoadModelLoopError = true silences the repeated Failed to load model message once you know its cause.
Debug Mode#
Config.Debug = true prints every step of the join flow to the console and registers test commands for the scenarios and the cutscene (see Commands and Exports).
Keep it off in production. Every debug line is written to the log synchronously and slows joins down on a busy server.



