Configuration

This section includes all configurable files for the selected script. Most settings are configured automatically, but each config.lua file contains a header comment explaining the purpose and usage of every configuration type. Be sure to read the header of each file carefully to fully understand how to customize and adapt the system for QBCORE, ESX, or standalone environments, ensuring seamless integration with your server.


qs-multicharacter/shared/config.lua
--──────────────────────────────────────────────────────────────────────────────
--  Quasar Store · Configuration Guidelines
--──────────────────────────────────────────────────────────────────────────────
--  This configuration file defines all adjustable parameters for the script.
--  Comments are standardized to help you identify which sections you can safely edit.
--
--  • [EDIT] – Safe for users to modify. Adjust these values as needed.
--  • [INFO] – Informational note describing what the variable or block does.
--  • [ADV]  – Advanced settings. Change only if you understand the logic behind it.
--  • [CORE] – Core functionality. Do not modify unless you are a developer.
--  • [AUTO] – Automatically handled by the system. Never edit manually.
--
--  Always make a backup before editing configuration files.
--  Incorrect changes in [CORE] or [AUTO] sections can break the resource.
--──────────────────────────────────────────────────────────────────────────────

-- Note: If you’re not using qb-core, comment out '@qb-apartments/config.lua' in fxmanifest.lua to avoid console warnings. [INFO]

Config  = Config  or {} -- [CORE]
Locales = Locales or {} -- [CORE]

--──────────────────────────────────────────────────────────────────────────────
-- Language Configuration                                                      [EDIT]
-- [INFO] Choose the main language (see locales/*). Create a new file if missing.
--──────────────────────────────────────────────────────────────────────────────
Config.Language = 'en' -- [EDIT]

--──────────────────────────────────────────────────────────────────────────────
-- Framework Detection                                                         [AUTO]
-- [INFO] Auto-detects ESX / QBCore / QBX. If renamed, set manually after adapting files.
--──────────────────────────────────────────────────────────────────────────────
local frameworks = {
    ['es_extended'] = 'esx',
    ['qb-core']     = 'qb',
    ['qbx_core']    = 'qb'
}

-- [CORE] Generic dependency checker (returns alias if resource is started)
local function dependencyCheck(data)
    for k, v in pairs(data) do
        if GetResourceState(k) == 'started' then
            return v
        end
    end
    return false
end

Config.Framework  = dependencyCheck(frameworks)            -- [AUTO]
Config.EsxVersion = '1.2'                                  -- [EDIT] '1.1' or '1.2' (legacy support)
Config.QBX        = GetResourceState('qbx_core') == 'started' -- [AUTO]
Config.DefaultRoutingBucket = 0                            -- [EDIT] Default routing bucket

-- Ensure a framework is detected. [CORE]
assert(Config.Framework, 'No framework found, please ensure you have es_extended, qb-core, or qbx_core installed')

--──────────────────────────────────────────────────────────────────────────────
-- Apartment System Integration                                                [AUTO]
-- [INFO] Detects supported apartment resources. Enables apartment-specific features.
--──────────────────────────────────────────────────────────────────────────────
local apartments = {
    ['qb-apartments'] = true,
    ['qbx_properties'] = true,
    ['qs-apartments'] = true
}

Config.HasApartment   = dependencyCheck(apartments)                      -- [AUTO]
Config.HasQSApartment = GetResourceState('qs-apartments') == 'started'   -- [AUTO]
Config.DisableCharacterDeletion = false                                   -- [EDIT]

--──────────────────────────────────────────────────────────────────────────────
-- HUD / Interface Detection                                                   [AUTO]
--──────────────────────────────────────────────────────────────────────────────
local hudList = {
    ['qs-interface'] = 'qs'
}
Config.HudScript = dependencyCheck(hudList) or 'standalone' -- [AUTO]

--──────────────────────────────────────────────────────────────────────────────
-- Clothing / Appearance Systems                                               [AUTO]
-- [INFO] Auto-detects appearance resources. Edit client/custom/appearance/* to add more.
-- [WARN] For QBX & qbx_spawn: comment out the CreateFirstCharacter trigger in qbx_properties and qbx_core to use this creator.
--──────────────────────────────────────────────────────────────────────────────
local appearances = {
    ['esx_skin']            = 'esx_skin',
    ['qb-clothing']         = 'qb-clothing',
    ['illenium-appearance'] = 'illenium-appearance',
    ['qs-appearance']       = 'qs-appearance'
}

local rcoreHas = GetResourceState('rcore_clothing') == 'started' -- [AUTO]
Config.Appearance = rcoreHas and 'rcore_clothing' or dependencyCheck(appearances) or 'none' -- [AUTO]
Info('Appearance:', Config.Appearance) -- [INFO]

-- [CORE] Guard against multiple appearance resources (when rcore_clothing isn't present)
if not rcoreHas then
    CreateThread(function()
        for k, v in pairs(appearances) do
            if GetResourceState(k) == 'started' and Config.Appearance ~= v then
                LoopError('Multiple appearance resources detected. Please apply the docs of the appearance script. If you think you already did it. Please remove one of the following resources: ' .. k .. ' or ' .. Config.Appearance)
            end
        end
    end)
end

-- [CORE] Hard stop if no appearance resource is detected
if Config.Appearance == 'none' then
    LoopError('No appearance found. Ensure you have a compatible skin resource installed and that it is started before this script.')
    return
end

--──────────────────────────────────────────────────────────────────────────────
-- Character Creator Defaults                                                  [EDIT]
-- [INFO] Disable multi-character creator or provide defaults (ESX skin only).
--──────────────────────────────────────────────────────────────────────────────
Config.DisableCharacterCreator = true                                       -- [EDIT]
Config.UseDefaultClothing      = GetConvarBool('qs:showcase', false)        -- [EDIT]

Config.DefaultClothes = { -- [EDIT] Used only for esx_skin default creator
    male = {
        tshirt_1 = 55, tshirt_2 = 0,
        torso_1  = 70, torso_2  = 5,
        decals_1 = 0,  decals_2 = 0,
        arms     = 17,
        pants_1  = 34, pants_2  = 0,
        shoes_1  = 28, shoes_2  = 1,
        helmet_1 = -1, helmet_2 = 0,
        chain_1  = 0,  chain_2  = 0,
        ears_1   = 0,  ears_2   = 0,
        mask_1   = 3,  mask_2   = 0
    },
    female = {
        tshirt_1 = 58, tshirt_2 = 1,
        torso_1  = 65, torso_2  = 5,
        decals_1 = 0,  decals_2 = 0,
        arms     = 23,
        pants_1  = 75, pants_2  = 0,
        shoes_1  = 11, shoes_2  = 2,
        helmet_1 = -1, helmet_2 = 0,
        chain_1  = 0,  chain_2  = 0,
        ears_1   = 0,  ears_2   = 0,
        mask_1   = 3,  mask_2   = 0
    }
}
--──────────────────────────────────────────────────────────────────────────────
-- Server · General                                                            [EDIT]
-- [INFO] Name shown in menus and default spawn position/orientation.
--──────────────────────────────────────────────────────────────────────────────
Config.ServerName = 'QUASAR ROLEPLAY'                       -- [EDIT]
Config.Spawn      = vec4(-284.2856, 562.4627, 172.9182, 19.9895) -- [EDIT]

--──────────────────────────────────────────────────────────────────────────────
-- Starter Items                                                               [EDIT]
-- [INFO] Granted to players on their first join.
--──────────────────────────────────────────────────────────────────────────────
Config.StarterItems = {
    { item = 'phone', amount = 1 },
}

--──────────────────────────────────────────────────────────────────────────────
-- Multicharacter · Music                                                      [EDIT]
-- [INFO] Background music settings for the character UI.
--──────────────────────────────────────────────────────────────────────────────
Config.Music = {
    Enable = true,            -- [EDIT] true/false
    Sound  = 'background.mp3',-- [EDIT] file name
    Volume = 0.1              -- [EDIT] 0.0–1.0
}

--──────────────────────────────────────────────────────────────────────────────
-- Multicharacter · Identity                                                   [EDIT]
-- [INFO] Requires identity system (esx_identity on ESX; QB has identity by default).
--──────────────────────────────────────────────────────────────────────────────
Config.Identity = true
if Config.Framework == 'qb' then Config.Identity = true end -- [AUTO]

--──────────────────────────────────────────────────────────────────────────────
-- Spawn / Character Selection                                                 [EDIT]
-- [INFO] Control spawn selector, qb-creator block, logout command, and intro scene.
--──────────────────────────────────────────────────────────────────────────────
Config.DisableOtherSpawns        = false -- [EDIT] If true, show only last position
Config.EnableDefaultSpawnSelector = true -- [EDIT] Use this script's selector; remove qb-spawn on QB
Config.BlockQBCreation           = true  -- [EDIT] Blocks qb-core creator
Config.BackCharCommand           = true  -- [EDIT] Enables /qs:logout
Config.EnableSpawnArea           = true  -- [EDIT] Expanded spawn area visuals
Config.EnableSpawnScene          = false -- [EDIT] Cinematic intro on join

--──────────────────────────────────────────────────────────────────────────────
-- Logout Command                                                              [CORE]
-- [INFO] Registers /qs:logout to return to character selection when enabled.
--──────────────────────────────────────────────────────────────────────────────
if Config.BackCharCommand then
    RegisterCommand('qs:logout', function()
        TriggerServerEvent('multicharacter:backToCharacterSelect')
    end, false)
end

--──────────────────────────────────────────────────────────────────────────────
-- Server-Side Safeguards                                                      [ADV]
-- [INFO] Data wipe toggle when no character exists. Dangerous—use with caution.
--──────────────────────────────────────────────────────────────────────────────
local isServerSide = IsDuplicityVersion()
if isServerSide then
    Config.WipeIfThereIsNoData = false -- [ADV] true = wipe data if no character found
end

--──────────────────────────────────────────────────────────────────────────────
-- Camera Settings                                                             [EDIT]
-- [INFO] Tune zoom/FOV/animation to shape the spawn selector experience.
--──────────────────────────────────────────────────────────────────────────────
Config.CameraZoom            = 1000.0  -- [EDIT] Default zoom level
Config.DefaultFov            = 130.0   -- [EDIT] Default field of view (FOV)
Config.ZoomFov               = 50.0    -- [EDIT] Zoomed-in FOV
Config.CameraAnimateDuration = 1       -- [EDIT] Camera transition duration (seconds)
Config.CameraFovDuration     = 0.5     -- [EDIT] FOV change duration (seconds)

--──────────────────────────────────────────────────────────────────────────────
-- HUD Toggle                                                                  [ADV]
-- [INFO] Replace with your HUD export if required.
--──────────────────────────────────────────────────────────────────────────────
function ToggleHud(toggle)
    DisplayRadar(toggle) -- [ADV] Enables/disables radar on HUD
    -- exports['hud']:DisplayHud(toggle) -- [ADV] Uncomment if using an external HUD
end

--──────────────────────────────────────────────────────────────────────────────
-- Respawn Policy                                                              [EDIT]
-- [INFO] If true, last-position respawn is allowed only when the player is dead.
--──────────────────────────────────────────────────────────────────────────────
Config.DeathOnlyLastLocation = true

--──────────────────────────────────────────────────────────────────────────────
-- Spawn Locations                                                             [EDIT]
-- [INFO] Add more entries as needed; coords use vec4(x, y, z, heading).
--──────────────────────────────────────────────────────────────────────────────
Config.SpawnLocations = {
    {
        spawnCoords = vec4(450.8258666992188, -651.27587890625, 28.42591857910156, 266.3955993652344), -- [EDIT]
        label       = 'Station',        -- [EDIT] UI label
        address     = 'Integrity Way',  -- [EDIT] Displayed address
    },
    {
        spawnCoords = vec4(315.6956176757813, -234.84115600585935, 53.97057342529297, 161.53314208984375),
        label       = 'Motel',
        address     = 'Hawick Awe',
    },
    {
        spawnCoords = vec4(426.2351684570313, -982.10986328125, 30.70991134643554, 86.68370056152344),
        label       = 'Police Department',
        address     = 'Atleet st',
    },
    {
        spawnCoords = vec4(74.31816864013672, 6415.984375, 31.44810104370117, 38.07055282592773),
        label       = 'Paleto',
        address     = 'Great Ocean Hwy',
    },
    -- Add additional spawn locations in similar format
}

--──────────────────────────────────────────────────────────────────────────────
-- Character Slots (UI Positions)                                              [EDIT]
-- [INFO] Absolute (CSS-like) placement in the character selector UI.
--──────────────────────────────────────────────────────────────────────────────
Config.CharacterSlots = {
    {
        position = 'fixed', -- [EDIT]
        left     = '33.4vw',-- [EDIT]
        top      = '33.69vh'-- [EDIT]
    },
    {
        position = 'fixed',
        left     = '65.18vw',
        top      = '50.22vh'
    },
    {
        position = 'fixed',
        left     = '34.03vw',
        top      = '45.69vh'
    },
    {
        position = 'fixed',
        left     = '41.17vw',
        top      = '50.87vh'
    },
}

--──────────────────────────────────────────────────────────────────────────────
-- Character Spawn Positions & Animations                                      [EDIT]
-- [INFO] World placement, look-at targets, and idle animations per slot.
-- [ADV]  Set tebex=true to manage slot availability via Tebex or /addslot.
--──────────────────────────────────────────────────────────────────────────────
Config.CharacterPositions = {
    [0] = {
        coords   = vec3(-1009.081299, -475.674713, 48.495923),  -- [EDIT] World position
        heading  = 201.259842,                                   -- [EDIT] Orientation (degrees)
        lookAt   = vec3(-1005.929688, -478.549438, 50.021973),   -- [EDIT] Camera look-at
        animation = {
            [0] = { dict = 'anim@amb@office@boardroom@boss@male@', anim = 'base' },
            [1] = { dict = 'anim@amb@office@boardroom@boss@male@', anim = 'base' }
        },
        tebex = false, -- [EDIT] true = managed externally
        -- prop = 'prop_beer_pissh' -- [ADV] Optional prop
    },
    [1] = {
        coords   = vec3(-1007.729001, -478.238470, 48.421973),
        heading  = 22.677164,
        lookAt   = vec3(-1009.081299, -475.674713, 48.695923),
        animation = {
            [0] = { dict = 'amb@prop_human_seat_chair@female@legs_crossed@base', anim = 'base' },
            [1] = { dict = 'amb@prop_human_seat_chair@female@legs_crossed@base', anim = 'base' }
        },
        tebex = false -- [EDIT] Manage via Tebex or /addslot when true
        -- prop = 'prop_beer_pissh'
    },
    [2] = {
        coords   = vec3(-1006.311074, -477.524507, 48.421973),
        heading  = 51.023624,
        lookAt   = vec3(-1009.081299, -475.674713, 48.695923),
        animation = {
            [0] = { dict = 'amb@prop_human_seat_chair@male@right_foot_out@base', anim = 'base' },
            [1] = { dict = 'amb@prop_human_seat_chair@male@right_foot_out@base', anim = 'base' }
        },
        tebex = false -- [EDIT]
        -- prop = 'prop_beer_pissh'
    },
    [3] = {
        coords   = vec3(-1004.676941, -475.252747, 49.521973),
        heading  = 124.724411,
        lookAt   = vec3(-1005.415405, -477.270325, 50.021973),
        animation = {
            [0] = { dict = 'amb@world_human_picnic@female@idle_a', anim = 'idle_a' },
            [1] = { dict = 'amb@world_human_picnic@female@idle_a', anim = 'idle_a' },
        },
        tebex = false -- [EDIT]
        -- prop = 'prop_beer_pissh'
    }
}

--──────────────────────────────────────────────────────────────────────────────
-- Character Camera & Spawn Selection                                          [INFO]
-- [INFO] Camera positions, FOV, collision setup and fixed slot safeguards.
-- [ADV]  Do not alter dependency checks or collision loader unless necessary.
--──────────────────────────────────────────────────────────────────────────────

-- Primary slot guard                                                          [CORE]
-- [INFO] Ensures slot 0 is never Tebex-managed.
if Config.CharacterPositions[0].tebex then
    Error('The first slot cannot be managed by tebex, please disable the tebex parameter in the configuration')
    Config.CharacterPositions[0].tebex = false
end

-- Character camera positions per slot                                         [EDIT]
-- [INFO] Camera origin (coords), look-at (pointAt), and slot mapping.
Config.CharacterCameraPositions = {
    [0] = {
        coords  = vec3(-1008.659363, -477.309875, 50.130811),
        pointAt = vec3(-1008.672546, -474.393414, 49.621973),
        slots   = { 0 }
    },
    [1] = {
        coords  = vec3(-1009.013196, -476.558228, 50.131885),
        pointAt = vec3(-1007.050537, -477.890106, 50.021973),
        slots   = { 1, 2 }
    },
    [2] = {
        coords  = vec3(-1005.283508, -476.465942, 50.021973),
        pointAt = vec3(-1004.953857, -476.049026, 50.02197),
        slots   = { 3 }
    },
}

-- Main camera reference points                                                [EDIT]
-- [INFO] Common cinematic anchors for entrance/stage/face/body views.
Config.CameraPositions = {
    entrance = {
        coords  = vec3(-1005.982422, -474.593414, 50.021973),
        pointAt = vec3(-1005.850525, -479.973633, 50.021973)
    },
    stageOffset = {
        coords  = vec3(-1007.142883, -477.718689, 50.021973),
        pointAt = vec3(-1005.850525, -479.973633, 50.021973)
    },
    stage = {
        coords  = vec3(-1007.142883, -477.718689, 50.021973),
        pointAt = vec3(-1005.850525, -479.973633, 50.021973)
    },
    face = {
        coords  = vec3(-1005.771423, -478.892303, 50.621973),
        pointAt = vec3(-1005.032959, -480.079132, 50.521973)
    },
    faceProfile = {
        coords  = vec3(-1005.956055, -479.472534, 50.021973),
        pointAt = vec3(-1005.494507, -480.290100, 50.021973)
    },
    body = {
        coords  = vec3(-1005.810974, -477.692322, 50.021973),
        pointAt = vec3(-1005.032959, -480.079132, 50.021973)
    }
}

-- Character creator placements                                                [EDIT]
-- [INFO] Male/Female preview positions and the controller (creator) pivot.
Config.CreatorCharacters = {
    male = {
        coords = vec4(-1005.006592, -479.815369, 49.121973, 50.021973)
    },
    female = {
        coords = vec4(-1006.338440, -480.461548, 49.121973, 11.338582)
    },
    creator = {
        coords = vec4(-1005.507690, -479.683502, 49.121973, 17.007874)
    }
}

-- Camera FOV                                                                  [EDIT]
-- [INFO] Default and zoom FOVs for generic/close-up views.
Config.DefaultCameraFov     = 60.0
Config.DefaultCameraFovZoom = 90.0

-- Client-side initialization                                                  [CORE]
-- [INFO] Prepares editable male/female bodies; hidden until used.
if not isServerSide then
    CreateThread(function()
        local creators = Config.CreatorCharacters
        bodyMale   = EditableCharacter(0, creators.male.coords.xyz, creators.male.coords.w)
        bodyFemale = EditableCharacter(1, creators.female.coords.xyz, creators.female.coords.w)
        bodyMale.hide()
        bodyFemale.hide()
    end)
end

-- Collision loader                                                            [CORE]
-- [INFO] Loads interior and sets focus area for spawn selector cameras.
function LoadCollision()
    local firstCam = Config.CharacterCameraPositions[0].coords
    local interior = GetInteriorAtCoords(firstCam.x, firstCam.y, firstCam.z)
    local time = GetGameTimer()
    LoadInterior(interior)
    while not IsInteriorReady(interior) and GetGameTimer() - time < 5000 do
        Wait(250)
    end
    if not IsInteriorReady(interior) then
        print('Failed to load interior')
    end
    SetFocusArea(firstCam.x, firstCam.y, firstCam.z, 50.021973, 0, 0, 0)
end

--──────────────────────────────────────────────────────────────────────────────
-- Debug Mode                                                                  [EDIT]
-- [INFO] Verbose logging and dev shortcuts. Disable on production servers.
--──────────────────────────────────────────────────────────────────────────────
Config.Debug         = true          -- [EDIT] Enable/disable debug prints
Config.Prefix        = 'char'        -- [ADV] Character prefix (avoid changing)
Config.ShortcutIntro = false         -- [EDIT] Faster intro for testing
Config.DefaultMaxChars = 4           -- [EDIT] Default max characters per player