local databaseConnected = false local databaseFound = false local oneSyncState = GetConvar("onesync", "off") local DATABASE do local connectionString = GetConvar("mysql_connection_string", "") if connectionString == "" then error(connectionString .. "\n^1Unable to start Multicharacter - unable to determine database from mysql_connection_string^0", 0) elseif connectionString:find("mysql://") then connectionString = connectionString:sub(9, -1) DATABASE = connectionString:sub(connectionString:find("/") + 1, -1):gsub("[%?]+[%w%p]*$", "") databaseFound = true else connectionString = { string.strsplit(";", connectionString) } for i = 1, #connectionString do local v = connectionString[i] if v:match("database") then DATABASE = v:sub(10, #v) databaseFound = true break end end end end local DB_TABLES = { users = "identifier" } local SLOTS = Config.Slots or 4 local PREFIX = Config.Prefix or "char" local PRIMARY_IDENTIFIER = ESX.GetConfig().Identifier or GetConvar("sv_lan", "") == "true" and "ip" or "license" local function GetIdentifier(source) local fxDk = GetConvarInt("sv_fxdkMode", 0) if fxDk == 1 then return "ESX-DEBUG-LICENCE" end local identifier = GetPlayerIdentifierByType(source, PRIMARY_IDENTIFIER) return identifier and identifier:gsub(PRIMARY_IDENTIFIER .. ":", "") end if next(ESX.Players) then local players = table.clone(ESX.Players) table.wipe(ESX.Players) for _, v in pairs(players) do ESX.Players[GetIdentifier(v.source)] = true end else ESX.Players = {} end local function SetupCharacters(source) while not databaseConnected do Wait(100) end local identifier = GetIdentifier(source) ESX.Players[identifier] = true local slots = MySQL.scalar.await("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or SLOTS identifier = PREFIX .. "%:" .. identifier local result = MySQL.query.await("SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?", { identifier, slots }) local characters if result then local characterCount = #result characters = table.create(0, characterCount) for i = 1, characterCount, 1 do local v = result[i] local job, grade = v.job or "unemployed", tostring(v.job_grade) if ESX.Jobs[job] and ESX.Jobs[job].grades[grade] then if job ~= "unemployed" then grade = ESX.Jobs[job].grades[grade].label else grade = "" end job = ESX.Jobs[job].label end local accounts = json.decode(v.accounts) local id = tonumber(string.sub(v.identifier, #PREFIX + 1, string.find(v.identifier, ":") - 1)) characters[id] = { id = id, bank = accounts.bank, money = accounts.money, job = job, job_grade = grade, firstname = v.firstname, lastname = v.lastname, dateofbirth = v.dateofbirth, skin = v.skin and json.decode(v.skin) or {}, disabled = v.disabled, sex = v.sex == "m" and TranslateCap("male") or TranslateCap("female"), } end end TriggerClientEvent("ui_multicharacter:SetupUI", source, characters, slots) end AddEventHandler("playerConnecting", function(_, _, deferrals) deferrals.defer() local identifier = GetIdentifier(source) if oneSyncState == "off" or oneSyncState == "legacy" then return deferrals.done(("[ESX] ESX Requires Onesync Infinity to work. This server currently has Onesync set to: %s"):format(oneSyncState)) end if not databaseFound then deferrals.done("[ESX] Cannot Find the servers mysql_connection_string. Please make sure it is correctly configured in your server.cfg") end if not databaseConnected then deferrals.done("[ESX] OxMySQL Was Unable To Connect to your database. Please make sure it is turned on and correctly configured in your server.cfg") end if identifier then if not ESX.GetConfig().EnableDebug then if ESX.Players[identifier] then deferrals.done(("[ESX Multicharacter] A player is already connected to the server with this identifier.\nYour identifier: %s:%s"):format(PRIMARY_IDENTIFIER, identifier)) else deferrals.done() end else deferrals.done() end else deferrals.done(("Unable to retrieve player identifier.\nIdentifier type: %s"):format(PRIMARY_IDENTIFIER)) end end) local function DeleteCharacter(source, charid) local identifier = ("%s%s:%s"):format(PREFIX, charid, GetIdentifier(source)) local query = "DELETE FROM `%s` WHERE %s = ?" local queries = {} local count = 0 for table, column in pairs(DB_TABLES) do count = count + 1 queries[count] = { query = query:format(table, column), values = { identifier } } end MySQL.transaction(queries, function(result) if result then print(("[^2INFO^7] Player ^5%s %s^7 has deleted a character ^5(%s)^7"):format(GetPlayerName(source), source, identifier)) Wait(50) SetupCharacters(source) else error("\n^1Transaction failed while trying to delete " .. identifier .. "^0") end end) end MySQL.ready(function() local length = 42 + #PREFIX local DB_COLUMNS = MySQL.query.await(('SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "%s" AND DATA_TYPE = "varchar" AND COLUMN_NAME IN (?)'):format(DATABASE, length), { { "identifier", "owner" }, }) if DB_COLUMNS then local columns = {} local count = 0 for i = 1, #DB_COLUMNS do local column = DB_COLUMNS[i] DB_TABLES[column.TABLE_NAME] = column.COLUMN_NAME if column?.CHARACTER_MAXIMUM_LENGTH ~= length then count = count + 1 columns[column.TABLE_NAME] = column.COLUMN_NAME end end if next(columns) then local query = "ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(%s)" local queries = table.create(count, 0) for k, v in pairs(columns) do queries[#queries + 1] = { query = query:format(k, v, length) } end if MySQL.transaction.await(queries) then print(("[^2INFO^7] Updated ^5%s^7 columns to use ^5VARCHAR(%s)^7"):format(count, length)) else print(("[^2INFO^7] Unable to update ^5%s^7 columns to use ^5VARCHAR(%s)^7"):format(count, length)) end end databaseConnected = true while not next(ESX.Jobs) do Wait(500) ESX.Jobs = ESX.GetJobs() end end end) RegisterNetEvent("ui_multicharacter:SetupCharacters", function() SetupCharacters(source) end) local awaitingRegistration = {} RegisterNetEvent("ui_multicharacter:CharacterChosen", function(charid, isNew) if type(charid) == "number" and string.len(charid) <= 2 and type(isNew) == "boolean" then if isNew then awaitingRegistration[source] = charid else if not ESX.GetConfig().EnableDebug then local identifier = PREFIX .. charid .. ":" .. GetIdentifier(source) if ESX.GetPlayerFromIdentifier(identifier) then DropPlayer(source, "Your identifier " .. identifier .. " is already on the server!") return end end TriggerEvent("esx:onPlayerJoined", source, PREFIX .. charid) ESX.Players[GetIdentifier(source)] = true end end end) AddEventHandler("ui_identity:completedRegistration", function(source, data) TriggerEvent("esx:onPlayerJoined", source, PREFIX .. awaitingRegistration[source], data) awaitingRegistration[source] = nil ESX.Players[GetIdentifier(source)] = true end) AddEventHandler("playerDropped", function() awaitingRegistration[source] = nil ESX.Players[GetIdentifier(source)] = nil end) RegisterNetEvent("ui_multicharacter:DeleteCharacter", function(charid) if Config.CanDelete and type(charid) == "number" and string.len(charid) <= 2 then DeleteCharacter(source, charid) end end) RegisterNetEvent("ui_multicharacter:relog", function() local source = source TriggerEvent("esx:playerLogout", source) end)