This repository has been archived on 2025-12-11. You can view files and clone it, but cannot push or open issues or pull requests.

249 lines
8.7 KiB
Lua
Raw Normal View History

2025-02-02 10:40:42 +01:00
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)