# Multicharacter (Optional)

## Cad Characters

> Works out of the box ( just start `cad-queue` before `cad-characters` )

## QB Multicharacter

> Replace below function in `qb-multicharacter/server/main.lua`

```lua
QBCore.Functions.CreateCallback('qb-multicharacter:server:setupCharacters', function(source, cb)
    local license = QBCore.Functions.GetIdentifier(source, 'license')
    local discord = GetPlayerIdentifierByType(source, 'discord')
    if not discord then return cb({}) end
    local data = exports['cad-queue']:GetPrio(discord)
    local jobName = (data and data.name) or 'all'
    local plyChars = {}
    local result = MySQL.query.await('SELECT * FROM players WHERE license = ?', { license })
    if result then
        for i = 1, (#result), 1 do
            result[i].charinfo = json.decode(result[i].charinfo)
            result[i].money = json.decode(result[i].money)
            result[i].job = json.decode(result[i].job)

            local job = result[i].job
            if (jobName == 'all') or (job?.name ~= jobName) then
                plyChars[#plyChars + 1] = result[i]
            end
        end
    end
    cb(plyChars)
end)
```

## Snappy Characters - ESX/QB/QBOX

> **ESX:** Replace below function in `snappy-characters/bridge/framework/server/esx.lua`

```lua
function GetAllCharacters(src)
    local identifier = ESX.GetIdentifier(src)
    local discord = GetPlayerIdentifierByType(src, 'discord') -- add the identifier which you are using in `Config.Identifier` in cad-queue
    if not discord then return {} end
    local data = (GetResourceState('cad-queue') == 'started') and exports['cad-queue']:GetPrio(discord)
    local jobName = (data and data.name) or 'all'
    local characters = {}
    local result = MySQL.query.await("SELECT identifier, job, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ?", { ("%s%%:%s"):format('char', identifier) })
    if result and result[1] then
        for _, v in pairs(result) do
            local job = v.job
            local gender = v.sex == 'm' and 'Male' or 'Female'
            characters[#characters+1] = {
                charInfo = {
                    firstName = v.firstname,
                    lastName = v.lastname,
                    dateOfBirth = v.dateofbirth,
                    nationality = v.nationality,
                    gender = gender,
                    citizenId = v.identifier
                },
                skinData = GetSkinData(v.identifier),
                isLocked = (jobName ~= 'all' and job ~= jobName) or (v.disabled ~= 0),
                businessName = job
            }
        end
    end
    return characters
end
```

> **QB:** Replace below function in `snappy-characters/bridge/framework/server/qb.lua`

```lua
function GetAllCharacters(src)
    local license = GetPlayerIdentifierByType(src, 'license')
    local discord = GetPlayerIdentifierByType(src, 'discord') -- add the identifier which you are using in `Config.Identifier` in cad-queue
    if not discord then return {} end
    local data = (GetResourceState('cad-queue') == 'started') and exports['cad-queue']:GetPrio(discord)
    local jobName = (data and data.name) or 'all'
    local characters = {}
    local result = MySQL.query.await('SELECT citizenid, charinfo, job FROM players WHERE license = ?', { license })
    if result and result[1] then
        for _, v in pairs(result) do
            local jobInfo = (v.job and json.decode(v.job)) or { name = 'none' }
            local charInfo = json.decode(v.charinfo)
            local gender = (charInfo.gender ~= 1 and 'Female') or 'Male'
            characters[#characters+1] = {
                charInfo = {
                    firstName = charInfo.firstname,
                    lastName = charInfo.lastname,
                    dateOfBirth = charInfo.birthdate,
                    nationality = charInfo.nationality,
                    gender = gender,
                    citizenId = v.citizenid
                },
                skinData = GetSkinData(v.citizenid),
                isLocked = (jobName ~= 'all' and jobInfo.name ~= jobName),
                businessName = jobInfo.label
            }
        end
    end
    return characters
end
```

> **QBOX:** Replace below function in `snappy-characters/bridge/framework/server/qbox.lua`

```lua
function GetAllCharacters(src)
    local license, license2 = GetPlayerIdentifierByType(src, 'license2'), GetPlayerIdentifierByType(src, 'license')
    local discord = GetPlayerIdentifierByType(src, 'discord') -- add the identifier which you are using in `Config.Identifier` in cad-queue
    if not discord then return {} end
    local data = (GetResourceState('cad-queue') == 'started') and exports['cad-queue']:GetPrio(discord)
    local jobName = (data and data.name) or 'all'
    local characters = {}
    local result = MySQL.query.await('SELECT citizenid, charinfo, job FROM players WHERE license IN (?, ?)', { license, license2 })
    if result and result[1] then
        for _, v in pairs(result) do
            local jobInfo = (v.job and json.decode(v.job)) or { name = 'none' }
            local charInfo = json.decode(v.charinfo)
            local gender = (charInfo.gender ~= 1 and 'Female') or 'Male'
            characters[#characters+1] = {
                charInfo = {
                    firstName = charInfo.firstname,
                    lastName = charInfo.lastname,
                    dateOfBirth = charInfo.birthdate,
                    nationality = charInfo.nationality,
                    gender = gender,
                    citizenId = v.citizenid
                },
                skinData = GetSkinData(v.citizenid),
                isLocked = (jobName ~= 'all' and jobInfo.name ~= jobName),
                businessName = jobInfo.label
            }
        end
    end
    return characters
end
```

## Forge Multicharacter (ESX/QB/QBOX)

> **ESX:** Replace below function in `forge-multicharacter/server/frameworks/ESX.lua`

```lua
function ST.ESX:LoadCharacters(source, cb)
    local discord = GetPlayerIdentifierByType(source, 'discord')
    local data = (GetResourceState('cad-queue') == 'started') and exports['cad-queue']:GetPrio(discord)
    local jobName = (data and data.name) or 'all'

    local roles = {}
    if Config.bot.use then
        roles = GetDiscordRoles(source)
        if roles == nil then
            DropPlayer(source, "Missing discord Identifier")
        end
    end

    local characters = {}
    local queryIdentifier = FormatIdentifier(IdentifierFormatTable(source, "%%"))
    local query = "SELECT * FROM `users` WHERE `identifier` LIKE '" .. tostring(queryIdentifier) .. "'"
    MySQL.Async.fetchAll(query, {}, function(results)
        local addSlots = 0
        if results then
            if #results > 0 then
                for _, char in ipairs(results) do
                    debug("char" .. tostring(_) .. ": " .. json.encode(char))
                    local location = json.decode(char.position)
                    local accounts = json.decode(char.accounts)
                    local sex = 0
                    -- cache update!
                    if char.sex == "m" then
                        sex = 1
                    end
                    local jobLabel = self:GetJobLabel(char.job)
                    if (jobName == 'all') or (char.job == jobName) then
                        table.insert(characters, {
                            identifier = char.identifier,
                            name = char.firstname .. " " .. char.lastname,
                            lastSpawnPoint = location,
                            cash = accounts.money,
                            bank = accounts.bank,
                            gender = sex,
                            job = jobLabel,
                            skin = json.decode(char.skin),
                            grade = char.job_grade,
                            playTime = FormatPlayTime(char.playtime),
                            nationality = char.nationality
                        })
                    end
                end
            end
        end
        addSlots = (MySQL.query.await('SELECT `slots` FROM `additionalSlots` WHERE `identifier`=?', {
            GetPlayerIdentifierByType(source, 'license')
        })[1] or {}).slots or 0
        cb(source, characters, roles, addSlots)
    end)
end
```

> **QB/QBOX:** Replace below function in `forge-multicharacter/server/frameworks/QB.lua`

```lua
function ST.QB:LoadCharacters(source, cb)
    local license = self:GetIdentifier(source)
    local discord = GetPlayerIdentifierByType(source, 'discord')
    local data = (GetResourceState('cad-queue') == 'started') and exports['cad-queue']:GetPrio(discord)
    local jobName = (data and data.name) or 'all'
    
    -- QBOX/QBCore compatibility check
    if QBCore and QBCore.Players then
        QBCore.Players[license] = true
    else
        debug("Warning: QBCore.Players is nil - possible QBOX compatibility issue")
    end
    
    local roles = {}
    if Config.bot.use then
        roles = GetDiscordRoles(source)
        if roles == nil then
            DropPlayer(source, "Missing discord Identifier")
        end
    end

    local characters = {}
    local query =
    "SELECT * FROM `players`, `playerskins` WHERE `license` LIKE @license AND `players`.`citizenid`=`playerskins`.`citizenid`"
    MySQL.Async.fetchAll(query, {
        ['@license'] = '%' .. license .. '%'
    }, function(results)
        local addSlots = 0
        if results then
            if #results > 0 then
                for _, char in ipairs(results) do
                    local location = json.decode(char.position)
                    local accounts = json.decode(char.money)
                    local job = json.decode(char.job)
                    local charinfo = json.decode(char.charinfo)
                    local sex = charinfo.gender == 0 and 1 or 0

                    local shareChar = {
                        identifier = char.citizenid,
                        name = charinfo.firstname .. " " .. charinfo.lastname,
                        lastSpawnPoint = location,
                        cash = accounts.cash,
                        bank = accounts.bank,
                        gender = sex,
                        job = job.label,
                        skin = json.decode(char.skin),
                        grade = job.grade.level,
                        playTime = FormatPlayTime(char.playtime),
                        nationality = charinfo.nationality
                    }
                    shareChar.skin.model = char.model

                    if (jobName == 'all') or (job?.name == jobName) then
                        table.insert(characters, shareChar)
                    end
                end
                addSlots = (MySQL.query.await('SELECT `slots` FROM `additionalSlots` WHERE `identifier`=?', {
                    license
                })[1] or {}).slots or 0
            end
        end
        cb(source, characters, roles, addSlots)
    end)
end
```

## ZSX Multicharacter

> Replace below function in `ZSX_Multicharacter/server/functions/characters.lua`

```lua
Characters.Get = function(src)
    local identifier = Characters.GetIdentifier(src)
    local PREFIX = Config.Prefix or 'char'
    local query
    if FrameworkSelected == 'ESX' then
        query = "SELECT * FROM `users` WHERE identifier LIKE '%"..identifier.."'"
    elseif FrameworkSelected == 'QBCore' then
        query = "SELECT * FROM `players` WHERE `license` = '"..identifier.."'"
    end
    local discord = GetPlayerIdentifierByType(src, 'discord')
    local queue_data = (GetResourceState('cad-queue') == 'started') and exports['cad-queue']:GetPrio(discord) -- added
    local queue_job_name = (queue_data and queue_data.name) or 'all' -- added
    local response = MySQL.query.await(query)
    local characters = {}
    local startTime = GetGameTimer()
    debugPrint("[^2CHARACTERS.GET^7] Gathering NUI Data [/]")
    for k,v in ipairs(response) do
        if FrameworkSelected == 'ESX' then
            local checkMulticharacterMode = string.sub(v.identifier, 1, string.len(PREFIX)) == PREFIX
            assert(checkMulticharacterMode, '^5Your es_extended does not use Config.Multichar. Player identifier: ^3'..v.identifier..'\n^2Please refer to the docs: https://zsx-development.gitbook.io/docs/multicharacter/common-issues/esx\n\n')
        end
        local id = FrameworkSelected == 'ESX' and tonumber(string.sub(v.identifier, #PREFIX+1, string.find(v.identifier, ':')-1)) or FrameworkSelected == 'QBCore' and tonumber(v.cid)
        if FrameworkSelected == 'ESX' then
            local job = v.job
            local grade = v.job_grade
            if GetResourceState('origen_masterjob') == 'started' then
                if not Characters.Jobs[job] then
                    local business = exports["origen_masterjob"]:GetBusiness(job)
                    if business then
                        local gradeObject = business.Functions.GetGrade(grade)
                        if gradeObject then
                            if not Characters.JobGrades[job] then
                                Characters.JobGrades[job] = {}
                            end
                            Characters.Jobs[job] = business.Data.label
                            Characters.JobGrades[job][tonumber(grade)] = gradeObject.label or ''
                            debugPrint('[ORIGEN_MASTERJOB] Applied changes to the Jobs and JobGrades objects. Job: ['..job..']')
                        else
                            debugPrint('[ORIGEN_MASTERJOB] No business grade has been found for: ['..job..']')
                        end
                    else
                        debugPrint('[ORIGEN_MASTERJOB] No business has been found for: ['..job..']')
                    end
                end
            end
           
            assert(v.position ~= nil and v.position ~= '', '^5Failed to get player position. Player identifier: ^3'..v.identifier..'\n^2Please refer to the docs: https://zsx-development.gitbook.io/docs/multicharacter/common-issues/esx\n\n')
            v.job = {
                name = job,
                label = Characters.Jobs[job] or 'Unemployed',
                grade_label = Characters.JobGrades[job] and Characters.JobGrades[job][tonumber(grade)] or ''
            }
            if (queue_job_name == 'all' or (v.job and v.job.name == queue_job_name)) then -- added
                local coords = json.decode(v.position)
                v.position = coords
                v.id = id
                v.skin = Characters.ConvertSkin(v.identifier)
                table.insert(characters, v)
            end -- added
        elseif FrameworkSelected == 'QBCore' then
            local skinResponse = MySQL.query.await("SELECT * FROM `playerskins` WHERE `citizenid` = '"..v.citizenid.."' AND active = 1")
            local responseCharData = json.decode(v.charinfo)
            local responseJobData = json.decode(v.job)
            if (queue_job_name == 'all' or (responseJobData and responseJobData.name == queue_job_name)) then -- added
                local characterData = {
                    sex = tonumber(response[1].gender) == 0 and 'm' or 'f',
                    skin = Characters.ConvertSkin(v.citizenid),
                    model = skinResponse[1] and tonumber(skinResponse[1].model) or false,
                    position = json.decode(v.position),
                    firstname = responseCharData.firstname,
                    lastname = responseCharData.lastname,
                    job = {
                        name = responseJobData.name,
                        label = responseJobData.label,
                        grade_label = responseJobData.grade and responseJobData.grade.name or ''
                    },
                    id = id
                }
                table.insert(characters, characterData)
            end -- added
        end
    end
    if Config.DebugTimers then
        print('[CHARACTERS.GET] Took: ^3'..(GetGameTimer() - startTime)..'ms^7')
    end
    debugPrint("[^2CHARACTERS.GET^7] Gathered NUI Data")
    return characters
end
```
