249 lines
8.7 KiB
Lua
249 lines
8.7 KiB
Lua
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)
|