mirror of
https://github.com/esx-framework/esx_core.git
synced 2026-08-29 17:28:53 +00:00
fca8927b38
Can result in some errors. Still keeping the other optimisations from previous commit (namely adjusting all identifier fields to the correct varchar length).
222 lines
6.8 KiB
Lua
222 lines
6.8 KiB
Lua
if not ESX then
|
|
error('\n^1Unable to start Multicharacter - you must be using ESX Legacy^0')
|
|
elseif ESX.GetConfig().Multichar == true then
|
|
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]*$', '')
|
|
else
|
|
connectionString = {string.strsplit(';', connectionString)}
|
|
for i = 1, #connectionString do
|
|
local v = connectionString[i]
|
|
if v:match('database') then
|
|
DATABASE = v:sub(10, #v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local DB_TABLES = {users = 'identifier'}
|
|
local FETCH = nil
|
|
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 Config.Identifier
|
|
|
|
local function GetIdentifier(source)
|
|
local identifier = PRIMARY_IDENTIFIER..':'
|
|
for _, v in pairs(GetPlayerIdentifiers(source)) do
|
|
if string.match(v, identifier) then
|
|
identifier = string.gsub(v, identifier, '')
|
|
return identifier
|
|
end
|
|
end
|
|
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 FETCH do Citizen.Wait(100) end
|
|
local identifier = GetIdentifier(source)
|
|
ESX.Players[identifier] = true
|
|
|
|
local slots = MySQL.scalar('SELECT slots FROM multicharacter_slots WHERE identifier = ?', { identifier }) or SLOTS
|
|
identifier = PREFIX..'%:'..identifier
|
|
|
|
local result = MySQL.query.await(FETCH, {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 _('male') or _('female')
|
|
}
|
|
end
|
|
end
|
|
|
|
TriggerClientEvent('esx_multicharacter:SetupUI', source, characters, slots)
|
|
end
|
|
|
|
AddEventHandler('playerConnecting', function(playerName, setKickReason, deferrals)
|
|
deferrals.defer()
|
|
local identifier = GetIdentifier(source)
|
|
|
|
if identifier then
|
|
if ESX.Players[identifier] then
|
|
deferrals.done(('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(('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 += 1
|
|
queries[count] = {query = query:format(table, column), values = {identifier}}
|
|
end
|
|
|
|
MySQL.transaction(queries, function(result)
|
|
if result then
|
|
print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(source), source, identifier))
|
|
Citizen.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 v = DB_COLUMNS[i]
|
|
|
|
if v?.CHARACTER_MAXIMUM_LENGTH ~= length then
|
|
count += 1
|
|
columns[v.TABLE_NAME] = v.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 VARCHAR(%s)'):format(count, length))
|
|
else
|
|
print(('[^2INFO^7] Unable to update ^5%s^7 columns to use VARCHAR(%s)'):format(count, length))
|
|
end
|
|
end
|
|
|
|
ESX.Jobs = {}
|
|
|
|
local function GetJobs()
|
|
if ESX.GetJobs then
|
|
return ESX.GetJobs()
|
|
end
|
|
return exports.es_extended:getSharedObject().Jobs
|
|
end
|
|
|
|
repeat
|
|
ESX.Jobs = GetJobs()
|
|
Citizen.Wait(50)
|
|
until next(ESX.Jobs)
|
|
|
|
FETCH = 'SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?'
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('esx_multicharacter:SetupCharacters', function()
|
|
SetupCharacters(source)
|
|
end)
|
|
|
|
local awaitingRegistration = {}
|
|
|
|
RegisterNetEvent('esx_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
|
|
TriggerEvent('esx:onPlayerJoined', source, PREFIX..charid)
|
|
ESX.Players[GetIdentifier(source)] = true
|
|
end
|
|
end
|
|
end)
|
|
|
|
AddEventHandler('esx_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(reason)
|
|
awaitingRegistration[source] = nil
|
|
ESX.Players[GetIdentifier(source)] = nil
|
|
end)
|
|
|
|
RegisterNetEvent('esx_multicharacter:DeleteCharacter', function(charid)
|
|
if Config.CanDelete and type(charid) == 'number' and string.len(charid) <= 2 then
|
|
DeleteCharacter(source, charid)
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('esx_multicharacter:relog', function()
|
|
TriggerEvent('esx:playerLogout', source)
|
|
end)
|
|
|
|
else
|
|
assert(nil, '^3WARNING: Multicharacter is disabled - please check your ESX configuration^0')
|
|
end
|