refactor(esx): various changes

Minor improvements to spawning.
Moved internal variables out of the ESX namespace.
SavePlayer function now uses a prepared statement.
Replaced bulk SavePlayers query with prepared statements.
Cleaned up newPlayer/loadPlayer queries.
Player load and creation functions use prepared statements.
Removed async dependency.
This commit is contained in:
Linden
2022-01-19 20:58:20 +11:00
parent ed6d2b3b14
commit f4d7991b10
9 changed files with 315 additions and 346 deletions
-1
View File
@@ -21,7 +21,6 @@ Many more resources are included in this repository, or you can browse the [ESX
#### Optimisation
- Utilise compile-time jenkins hashing over the GetHashKey native
- Update old MySQL queries to use MySQL.store to improve performance, especially during player saving
- Several loops will now sleep when their tasks are not necessary to perform
- Improved support when using ESX Identity to reduce events and queries during player login
- Support for the latest weapons and components
+2 -2
View File
@@ -2,6 +2,6 @@ AddEventHandler('esx:getSharedObject', function(cb)
cb(ESX)
end)
function getSharedObject()
exports('getSharedObject', function()
return ESX
end
end)
+20 -19
View File
@@ -1,9 +1,10 @@
ESX = {}
Core = {}
ESX.PlayerData = {}
ESX.PlayerLoaded = false
ESX.CurrentRequestId = 0
ESX.ServerCallbacks = {}
ESX.TimeoutCallbacks = {}
Core.CurrentRequestId = 0
Core.ServerCallbacks = {}
Core.TimeoutCallbacks = {}
ESX.UI = {}
ESX.UI.HUD = {}
@@ -21,15 +22,15 @@ ESX.Scaleform.Utils = {}
ESX.Streaming = {}
ESX.SetTimeout = function(msec, cb)
table.insert(ESX.TimeoutCallbacks, {
table.insert(Core.TimeoutCallbacks, {
time = GetGameTimer() + msec,
cb = cb
})
return #ESX.TimeoutCallbacks
return #Core.TimeoutCallbacks
end
ESX.ClearTimeout = function(i)
ESX.TimeoutCallbacks[i] = nil
Core.TimeoutCallbacks[i] = nil
end
ESX.IsPlayerLoaded = function()
@@ -86,14 +87,14 @@ ESX.ShowFloatingHelpNotification = function(msg, coords)
end
ESX.TriggerServerCallback = function(name, cb, ...)
ESX.ServerCallbacks[ESX.CurrentRequestId] = cb
Core.ServerCallbacks[Core.CurrentRequestId] = cb
TriggerServerEvent('esx:triggerServerCallback', name, ESX.CurrentRequestId, ...)
TriggerServerEvent('esx:triggerServerCallback', name, Core.CurrentRequestId, ...)
if ESX.CurrentRequestId < 65535 then
ESX.CurrentRequestId = ESX.CurrentRequestId + 1
if Core.CurrentRequestId < 65535 then
Core.CurrentRequestId = Core.CurrentRequestId + 1
else
ESX.CurrentRequestId = 0
Core.CurrentRequestId = 0
end
end
@@ -849,7 +850,7 @@ ESX.ShowInventory = function()
players[GetPlayerServerId(playerNearby)] = true
end
ESX.TriggerServerCallback('esx:getPlayerNames', function(returnedPlayers)
Core.TriggerServerCallback('esx:getPlayerNames', function(returnedPlayers)
for playerId,playerName in pairs(returnedPlayers) do
table.insert(elements, {
label = playerName,
@@ -989,8 +990,8 @@ end
RegisterNetEvent('esx:serverCallback')
AddEventHandler('esx:serverCallback', function(requestId, ...)
ESX.ServerCallbacks[requestId](...)
ESX.ServerCallbacks[requestId] = nil
Core.ServerCallbacks[requestId](...)
Core.ServerCallbacks[requestId] = nil
end)
RegisterNetEvent('esx:showNotification')
@@ -1012,13 +1013,13 @@ end)
Citizen.CreateThread(function()
while true do
local sleep = 100
if #ESX.TimeoutCallbacks > 0 then
if #Core.TimeoutCallbacks > 0 then
local currTime = GetGameTimer()
sleep = 0
for i=1, #ESX.TimeoutCallbacks, 1 do
if currTime >= ESX.TimeoutCallbacks[i].time then
ESX.TimeoutCallbacks[i].cb()
ESX.TimeoutCallbacks[i] = nil
for i=1, #Core.TimeoutCallbacks, 1 do
if currTime >= Core.TimeoutCallbacks[i].time then
Core.TimeoutCallbacks[i].cb()
Core.TimeoutCallbacks[i] = nil
end
end
end
+17 -13
View File
@@ -32,15 +32,14 @@ AddEventHandler('esx:playerLoaded', function(xPlayer, isNew, skin)
}, function()
TriggerServerEvent('esx:onPlayerSpawn')
TriggerEvent('esx:onPlayerSpawn')
TriggerEvent('playerSpawned') -- compatibility with old scripts
TriggerEvent('esx:restoreLoadout')
if isNew then
if skin.sex == 0 then
TriggerEvent('skinchanger:loadDefaultModel', true)
else
TriggerEvent('skinchanger:loadDefaultModel', false)
end
elseif skin then TriggerEvent('skinchanger:loadSkin', skin) end
TriggerEvent('skinchanger:loadDefaultModel', skin.sex == 0)
elseif skin then
TriggerEvent('skinchanger:loadSkin', skin)
end
TriggerEvent('esx:loadingScreenOff')
ShutdownLoadingScreen()
ShutdownLoadingScreenNui()
@@ -65,7 +64,7 @@ AddEventHandler('esx:playerLoaded', function(xPlayer, isNew, skin)
local gradeLabel = ESX.PlayerData.job.grade_label ~= ESX.PlayerData.job.label and ESX.PlayerData.job.grade_label or ''
if gradeLabel ~= '' then gradeLabel = ' - '..gradeLabel end
ESX.UI.HUD.RegisterElement('job', #ESX.PlayerData.accounts, 0, jobTpl, {
job_label = ESX.PlayerData.job.label,
grade_label = gradeLabel
@@ -83,10 +82,15 @@ end)
RegisterNetEvent('esx:setMaxWeight')
AddEventHandler('esx:setMaxWeight', function(newMaxWeight) ESX.PlayerData.maxWeight = newMaxWeight end)
AddEventHandler('esx:onPlayerSpawn', function()
ESX.SetPlayerData('ped', PlayerPedId())
ESX.SetPlayerData('dead', false)
end)
local function onPlayerSpawn()
if ESX.PlayerLoaded then
ESX.SetPlayerData('ped', PlayerPedId())
ESX.SetPlayerData('dead', false)
end
end
AddEventHandler('playerSpawned', onPlayerSpawn)
AddEventHandler('esx:onPlayerSpawn', onPlayerSpawn)
AddEventHandler('esx:onPlayerDeath', function()
ESX.SetPlayerData('ped', PlayerPedId())
@@ -282,7 +286,7 @@ end)
RegisterNetEvent('esx:createMissingPickups')
AddEventHandler('esx:createMissingPickups', function(missingPickups)
for pickupId,pickup in pairs(missingPickups) do
for pickupId, pickup in pairs(missingPickups) do
TriggerEvent('esx:createPickup', pickupId, pickup.label, pickup.coords, pickup.type, pickup.name, pickup.components, pickup.tintIndex)
end
end)
+1 -10
View File
@@ -24,7 +24,6 @@ shared_scripts {
}
server_scripts {
'@async/async.lua',
'@mysql-async/lib/MySQL.lua',
'server/common.lua',
@@ -78,16 +77,8 @@ files {
'html/img/accounts/money.png'
}
exports {
'getSharedObject'
}
server_exports {
'getSharedObject'
}
dependencies {
'mysql-async',
'oxmysql',
'async',
'spawnmanager',
}
+2 -2
View File
@@ -134,13 +134,13 @@ end, true, {help = _U('command_setgroup'), validate = true, arguments = {
}})
ESX.RegisterCommand('save', 'admin', function(xPlayer, args, showError)
ESX.SavePlayer(args.playerId)
Core.SavePlayer(args.playerId)
end, true, {help = _U('command_save'), validate = true, arguments = {
{name = 'playerId', help = _U('commandgeneric_playerid'), type = 'player'}
}})
ESX.RegisterCommand('saveall', 'admin', function(xPlayer, args, showError)
ESX.SavePlayers()
Core.SavePlayers()
end, true, {help = _U('command_saveall')})
ESX.RegisterCommand('group', {"user", "admin"}, function(xPlayer, args, showError)
+16 -15
View File
@@ -1,34 +1,35 @@
ESX = {}
ESX.Players = {}
ESX.UsableItemsCallbacks = {}
ESX.Items = {}
ESX.ServerCallbacks = {}
ESX.TimeoutCount = -1
ESX.CancelledTimeouts = {}
ESX.Pickups = {}
ESX.PickupId = 0
ESX.Jobs = {}
ESX.RegisteredCommands = {}
ESX.Items = {}
Core = {}
Core.UsableItemsCallbacks = {}
Core.ServerCallbacks = {}
Core.TimeoutCount = -1
Core.CancelledTimeouts = {}
Core.RegisteredCommands = {}
Core.Pickups = {}
Core.PickupId = 0
AddEventHandler('esx:getSharedObject', function(cb)
cb(ESX)
end)
function getSharedObject()
exports('getSharedObject', function()
return ESX
end
end)
local function StartDBSync()
Citizen.CreateThread(function()
while true do
Citizen.Wait(10 * 60 * 1000)
ESX.SavePlayers()
Core.SavePlayers()
end
end)
end
MySQL.ready(function()
MySQL.Async.fetchAll('SELECT * FROM items', {}, function(result)
MySQL.query('SELECT * FROM items', {}, function(result)
for k,v in ipairs(result) do
ESX.Items[v.name] = {
label = v.label,
@@ -40,13 +41,13 @@ MySQL.ready(function()
end)
local Jobs = {}
MySQL.Async.fetchAll('SELECT * FROM jobs', {}, function(jobs)
MySQL.query('SELECT * FROM jobs', {}, function(jobs)
for k,v in ipairs(jobs) do
Jobs[v.name] = v
Jobs[v.name].grades = {}
end
MySQL.Async.fetchAll('SELECT * FROM job_grades', {}, function(jobGrades)
MySQL.query('SELECT * FROM job_grades', {}, function(jobGrades)
for k,v in ipairs(jobGrades) do
if Jobs[v.job_name] then
Jobs[v.job_name].grades[tostring(v.grade)] = v
@@ -80,7 +81,7 @@ RegisterServerEvent('esx:triggerServerCallback')
AddEventHandler('esx:triggerServerCallback', function(name, requestId, ...)
local playerId = source
ESX.TriggerServerCallback(name, requestId, playerId, function(...)
Core.TriggerServerCallback(name, requestId, playerId, function(...)
TriggerClientEvent('esx:serverCallback', playerId, requestId, ...)
end, ...)
end)
+75 -80
View File
@@ -5,17 +5,17 @@ ESX.Trace = function(msg)
end
ESX.SetTimeout = function(msec, cb)
local id = ESX.TimeoutCount + 1
local id = Core.TimeoutCount + 1
SetTimeout(msec, function()
if ESX.CancelledTimeouts[id] then
ESX.CancelledTimeouts[id] = nil
if Core.CancelledTimeouts[id] then
Core.CancelledTimeouts[id] = nil
else
cb()
end
end)
ESX.TimeoutCount = id
Core.TimeoutCount = id
return id
end
@@ -29,10 +29,10 @@ ESX.RegisterCommand = function(name, group, cb, allowConsole, suggestion)
return
end
if ESX.RegisteredCommands[name] then
if Core.RegisteredCommands[name] then
print(('[^3WARNING^7] Command ^5"%s" already registered, overriding command'):format(name))
if ESX.RegisteredCommands[name].suggestion then
if Core.RegisteredCommands[name].suggestion then
TriggerClientEvent('chat:removeSuggestion', -1, ('/%s'):format(name))
end
end
@@ -44,10 +44,10 @@ ESX.RegisterCommand = function(name, group, cb, allowConsole, suggestion)
TriggerClientEvent('chat:addSuggestion', -1, ('/%s'):format(name), suggestion.help, suggestion.arguments)
end
ESX.RegisteredCommands[name] = {group = group, cb = cb, allowConsole = allowConsole, suggestion = suggestion}
Core.RegisteredCommands[name] = {group = group, cb = cb, allowConsole = allowConsole, suggestion = suggestion}
RegisterCommand(name, function(playerId, args, rawCommand)
local command = ESX.RegisteredCommands[name]
local command = Core.RegisteredCommands[name]
if not command.allowConsole and playerId == 0 then
print(('[^3WARNING^7] ^5%s'):format(_U('commanderror_console')))
@@ -148,92 +148,60 @@ ESX.RegisterCommand = function(name, group, cb, allowConsole, suggestion)
end
ESX.ClearTimeout = function(id)
ESX.CancelledTimeouts[id] = true
Core.CancelledTimeouts[id] = true
end
ESX.RegisterServerCallback = function(name, cb)
ESX.ServerCallbacks[name] = cb
Core.ServerCallbacks[name] = cb
end
ESX.TriggerServerCallback = function(name, requestId, source, cb, ...)
if ESX.ServerCallbacks[name] then
ESX.ServerCallbacks[name](source, cb, ...)
Core.TriggerServerCallback = function(name, requestId, source, cb, ...)
if Core.ServerCallbacks[name] then
Core.ServerCallbacks[name](source, cb, ...)
else
print(('[^3WARNING^7] Server callback ^5"%s"^0 does not exist. ^1Please Check The Server File for Errors!'):format(name))
end
end
local savePlayers = -1
Citizen.CreateThread(function()
savePlayers = MySQL.Sync.store("UPDATE users SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ? WHERE `identifier` = ?")
end)
ESX.SavePlayer = function(xPlayer, cb)
local asyncTasks = {}
table.insert(asyncTasks, function(cb2)
MySQL.Async.execute(savePlayers, {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.getGroup(),
json.encode(xPlayer.getCoords()),
json.encode(xPlayer.getInventory(true)),
json.encode(xPlayer.getLoadout(true)),
xPlayer.getIdentifier()
}, function(rowsChanged)
cb2()
end)
end)
Async.parallel(asyncTasks, function(results)
print(('[^2INFO^7] Saved player ^5"%s^7"'):format(xPlayer.getName()))
if cb then
cb()
Core.SavePlayer = function(xPlayer, cb)
MySQL.prepare('UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ? WHERE `identifier` = ?'', {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.group,
json.encode(xPlayer.getCoords()),
json.encode(xPlayer.getInventory(true)),
xPlayer.identifier
}, function(affectedRows)
if affectedRows == 1 then
print(('[^2INFO^7] Saved player ^5"%s^7"'):format(xPlayer.name))
end
if cb then cb() end
end)
end
ESX.SavePlayers = function(cb)
Core.SavePlayers = function(cb)
local xPlayers = ESX.GetExtendedPlayers()
if #xPlayers > 0 then
local count = #xPlayers
if count > 0 then
local parameters = {}
local time = os.time()
local selectListWithNames = "SELECT '%s' AS identifier, '%s' AS new_accounts, '%s' AS new_job, %s AS new_job_grade, '%s' AS new_group, '%s' AS new_loadout, '%s' AS new_position, '%s' AS new_inventory "
local selectListNoNames = "SELECT '%s', '%s', '%s' , %s, '%s', '%s', '%s', '%s' "
local updateCommand = 'UPDATE users u JOIN ('
local selectList = selectListNoNames
local first = true
for k, xPlayer in pairs(xPlayers) do
if first == false then
updateCommand = updateCommand .. ' UNION '
else
selectList = selectListWithNames
end
updateCommand = updateCommand .. string.format(selectList,
xPlayer.identifier,
for i=1, count do
local xPlayer = xPlayers[i]
parameters[#parameters+1] = {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.getGroup(),
json.encode(xPlayer.getLoadout(true)),
xPlayer.group,
json.encode(xPlayer.getCoords()),
json.encode(xPlayer.getInventory(true))
)
first = false
json.encode(xPlayer.getInventory(true)),
xPlayer.identifier
}
end
updateCommand = updateCommand .. ' ) vals ON u.identifier = vals.identifier SET accounts = new_accounts, job = new_job, job_grade = new_job_grade, `group` = new_group, loadout = new_loadout, `position` = new_position, inventory = new_inventory'
MySQL.Async.fetchAll(updateCommand, {},
function(result)
if result then
if cb then cb() else print(('[^2INFO^7] Saved %s of %s player(s) over %s seconds'):format(result.affectedRows, #xPlayers, os.time() - time)) end
MySQL.prepare("UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ? WHERE `identifier` = ?", parameters,
function(results)
if results then
if type(cb) == 'function' then cb() else print(('[^2INFO^7] Saved %s %s over %s ms'):format(count, count > 1 and 'players' or 'player', (os.time() - time) / 1000000)) end
end
end)
end
@@ -285,11 +253,11 @@ ESX.GetIdentifier = function(playerId)
end
ESX.RegisterUsableItem = function(item, cb)
ESX.UsableItemsCallbacks[item] = cb
Core.UsableItemsCallbacks[item] = cb
end
ESX.UseItem = function(source, item)
ESX.UsableItemsCallbacks[item](source, item)
Core.UsableItemsCallbacks[item](source, item)
end
ESX.GetItemLabel = function(item)
@@ -298,24 +266,36 @@ ESX.GetItemLabel = function(item)
end
end
ESX.GetJobs = function()
return ESX.Jobs
end
ESX.GetUsableItems = function()
local Usables = {}
for k in pairs(Core.UsableItemsCallbacks) do
Usables[k] = true
end
return Usables
end
ESX.CreatePickup = function(type, name, count, label, playerId, components, tintIndex)
local pickupId = (ESX.PickupId == 65635 and 0 or ESX.PickupId + 1)
local pickupId = (Core.PickupId == 65635 and 0 or Core.PickupId + 1)
local xPlayer = ESX.GetPlayerFromId(playerId)
local coords = xPlayer.getCoords()
ESX.Pickups[pickupId] = {
Core.Pickups[pickupId] = {
type = type, name = name,
count = count, label = label,
coords = coords
}
if type == 'item_weapon' then
ESX.Pickups[pickupId].components = components
ESX.Pickups[pickupId].tintIndex = tintIndex
Core.Pickups[pickupId].components = components
Core.Pickups[pickupId].tintIndex = tintIndex
end
TriggerClientEvent('esx:createPickup', -1, pickupId, label, coords, type, name, components, tintIndex)
ESX.PickupId = pickupId
Core.PickupId = pickupId
end
ESX.DoesJobExist = function(job, grade)
@@ -329,3 +309,18 @@ ESX.DoesJobExist = function(job, grade)
return false
end
Core.IsPlayerAdmin = function(playerId)
if (IsPlayerAceAllowed(playerId, 'command') or GetConvar('sv_lan', '') == 'true') and true or false then
return true
end
local xPlayer = ESX.GetPlayerFromId(playerId)
if xPlayer then
if xPlayer.group == 'admin' or xPlayer.group == 'superadmin' then
return true
end
end
return false
end
+182 -204
View File
@@ -1,26 +1,16 @@
local NewPlayer, LoadPlayer = -1, -1
local newPlayer = 'INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `group` = ?'
local loadPlayer = 'SELECT `accounts`, `job`, `job_grade`, `group`, `position`, `inventory`, `skin`, `loadout`'
Citizen.CreateThread(function()
SetMapName('San Andreas')
SetGameType('ESX Legacy')
local query = '`accounts`, `job`, `job_grade`, `group`, `position`, `inventory`, `skin`, `loadout`' -- Select these fields from the database
if Config.Multichar or Config.Identity then -- append these fields to the select query
query = query..', `firstname`, `lastname`, `dateofbirth`, `sex`, `height`'
if Config.Multichar or Config.Identity then
newPlayer = newPlayer..', `firstname` = ?, `lastname` = ?, `dateofbirth` = ?, `sex` = ?, `height` = ?"'
loadPlayer = loadPlayer..', `firstname`, `lastname`, `dateofbirth`, `sex`, `height`'
end
if Config.Multichar then -- insert identity data with creation
MySQL.Async.store("INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `group` = ?, `firstname` = ?, `lastname` = ?, `dateofbirth` = ?, `sex` = ?, `height` = ?", function(storeId)
NewPlayer = storeId
end)
else
MySQL.Async.store("INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `group` = ?", function(storeId)
NewPlayer = storeId
end)
end
MySQL.Async.store("SELECT "..query.." FROM `users` WHERE identifier = ?", function(storeId)
LoadPlayer = storeId
end)
loadPlayer = loadPlayer..' FROM `users` WHERE identifier = ?'
end)
if Config.Multichar then
@@ -49,13 +39,12 @@ function onPlayerJoined(playerId)
if ESX.GetPlayerFromIdentifier(identifier) then
DropPlayer(playerId, ('there was an error loading your character!\nError code: identifier-active-ingame\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same Rockstar account.\n\nYour Rockstar identifier: %s'):format(identifier))
else
MySQL.Async.fetchScalar('SELECT 1 FROM users WHERE identifier = @identifier', {
['@identifier'] = identifier
}, function(result)
if result then
loadESXPlayer(identifier, playerId, false)
else createESXPlayer(identifier, playerId) end
end)
local result = MySQL.scalar.await('SELECT 1 FROM users WHERE identifier = ?', { identifier })
if result then
loadESXPlayer(identifier, playerId, false)
else
createESXPlayer(identifier, playerId)
end
end
else
DropPlayer(playerId, 'there was an error loading your character!\nError code: identifier-missing-ingame\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.')
@@ -69,7 +58,7 @@ function createESXPlayer(identifier, playerId, data)
accounts[account] = money
end
if IsPlayerAceAllowed(playerId, "command") then
if Core.IsPlayerAdmin(playerId) then
print(('^2[INFO] ^0 Player ^5%s ^0Has been granted admin permissions via ^5Ace Perms.^7'):format(playerId))
defaultGroup = "admin"
else
@@ -77,15 +66,15 @@ function createESXPlayer(identifier, playerId, data)
end
if not Config.Multichar then
MySQL.Async.execute(NewPlayer, {
MySQL.prepare(newPlayer, {
json.encode(accounts),
identifier,
defaultGroup,
}, function(rowsChanged)
}, function()
loadESXPlayer(identifier, playerId, true)
end)
else
MySQL.Async.execute(NewPlayer, {
MySQL.prepare(newPlayer, {
json.encode(accounts),
identifier,
defaultGroup,
@@ -94,7 +83,7 @@ function createESXPlayer(identifier, playerId, data)
data.dateofbirth,
data.sex,
data.height,
}, function(rowsChanged)
}, function()
loadESXPlayer(identifier, playerId, true)
end)
end
@@ -104,7 +93,6 @@ AddEventHandler('playerConnecting', function(name, setCallback, deferrals)
deferrals.defer()
local playerId = source
local identifier = ESX.GetIdentifier(playerId)
Citizen.Wait(100)
if identifier then
if ESX.GetPlayerFromIdentifier(identifier) then
@@ -118,8 +106,6 @@ AddEventHandler('playerConnecting', function(name, setCallback, deferrals)
end)
function loadESXPlayer(identifier, playerId, isNew)
local tasks = {}
local userData = {
accounts = {},
inventory = {},
@@ -129,179 +115,171 @@ function loadESXPlayer(identifier, playerId, isNew)
weight = 0
}
table.insert(tasks, function(cb)
MySQL.Async.fetchAll(LoadPlayer, { identifier
}, function(result)
local job, grade, jobObject, gradeObject = result[1].job, tostring(result[1].job_grade)
local foundAccounts, foundItems = {}, {}
local result = MySQL.prepare.await(loadPlayer, { identifier })
local job, grade, jobObject, gradeObject = result.job, tostring(result.job_grade)
local foundAccounts, foundItems = {}, {}
-- Accounts
if result[1].accounts and result[1].accounts ~= '' then
local accounts = json.decode(result[1].accounts)
-- Accounts
if result.accounts and result.accounts ~= '' then
local accounts = json.decode(result.accounts)
for account,money in pairs(accounts) do
foundAccounts[account] = money
end
end
for account,label in pairs(Config.Accounts) do
table.insert(userData.accounts, {
name = account,
money = foundAccounts[account] or Config.StartingAccountMoney[account] or 0,
label = label
})
end
-- Job
if ESX.DoesJobExist(job, grade) then
jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade]
else
print(('[^3WARNING^7] Ignoring invalid job for %s [job: %s, grade: %s]'):format(identifier, job, grade))
job, grade = 'unemployed', '0'
jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade]
end
userData.job.id = jobObject.id
userData.job.name = jobObject.name
userData.job.label = jobObject.label
userData.job.grade = tonumber(grade)
userData.job.grade_name = gradeObject.name
userData.job.grade_label = gradeObject.label
userData.job.grade_salary = gradeObject.salary
userData.job.skin_male = {}
userData.job.skin_female = {}
if gradeObject.skin_male then userData.job.skin_male = json.decode(gradeObject.skin_male) end
if gradeObject.skin_female then userData.job.skin_female = json.decode(gradeObject.skin_female) end
-- Inventory
if result[1].inventory and result[1].inventory ~= '' then
local inventory = json.decode(result[1].inventory)
for name,count in pairs(inventory) do
local item = ESX.Items[name]
if item then
foundItems[name] = count
else
print(('[^3WARNING^7] Ignoring invalid item "%s" for "%s"'):format(name, identifier))
end
end
end
for name,item in pairs(ESX.Items) do
local count = foundItems[name] or 0
if count > 0 then userData.weight = userData.weight + (item.weight * count) end
table.insert(userData.inventory, {
name = name,
count = count,
label = item.label,
weight = item.weight,
usable = ESX.UsableItemsCallbacks[name] ~= nil,
rare = item.rare,
canRemove = item.canRemove
})
end
table.sort(userData.inventory, function(a, b)
return a.label < b.label
end)
-- Group
if result[1].group then
if result[1].group == "superadmin" then
userData.group = "admin"
else
userData.group = result[1].group
end
else
userData.group = 'user'
end
-- Loadout
if result[1].loadout and result[1].loadout ~= '' then
local loadout = json.decode(result[1].loadout)
for name,weapon in pairs(loadout) do
local label = ESX.GetWeaponLabel(name)
if label then
if not weapon.components then weapon.components = {} end
if not weapon.tintIndex then weapon.tintIndex = 0 end
table.insert(userData.loadout, {
name = name,
ammo = weapon.ammo,
label = label,
components = weapon.components,
tintIndex = weapon.tintIndex
})
end
end
end
-- Position
if result[1].position and result[1].position ~= '' then
userData.coords = json.decode(result[1].position)
else
print('[^3WARNING^7] Column ^5"position"^0 in ^5"users"^0 table is missing required default value. Using backup coords, fix your database.')
userData.coords = {x = -269.4, y = -955.3, z = 31.2, heading = 205.8}
end
-- Skin
if result[1].skin and result[1].skin ~= '' then
userData.skin = json.decode(result[1].skin)
else
if userData.sex == 'f' then userData.skin = {sex=1} else userData.skin = {sex=0} end
end
-- Identity
if result[1].firstname and result[1].firstname ~= '' then
userData.firstname = result[1].firstname
userData.lastname = result[1].lastname
userData.playerName = userData.firstname..' '..userData.lastname
if result[1].dateofbirth then userData.dateofbirth = result[1].dateofbirth end
if result[1].sex then userData.sex = result[1].sex end
if result[1].height then userData.height = result[1].height end
end
cb()
end)
end)
Async.parallel(tasks, function(results)
local xPlayer = CreateExtendedPlayer(playerId, identifier, userData.group, userData.accounts, userData.inventory, userData.weight, userData.job, userData.loadout, userData.playerName, userData.coords)
ESX.Players[playerId] = xPlayer
if userData.firstname then
xPlayer.set('firstName', userData.firstname)
xPlayer.set('lastName', userData.lastname)
if userData.dateofbirth then xPlayer.set('dateofbirth', userData.dateofbirth) end
if userData.sex then xPlayer.set('sex', userData.sex) end
if userData.height then xPlayer.set('height', userData.height) end
for account,money in pairs(accounts) do
foundAccounts[account] = money
end
end
TriggerEvent('esx:playerLoaded', playerId, xPlayer, isNew)
for account,label in pairs(Config.Accounts) do
table.insert(userData.accounts, {
name = account,
money = foundAccounts[account] or Config.StartingAccountMoney[account] or 0,
label = label
})
end
xPlayer.triggerEvent('esx:playerLoaded', {
accounts = xPlayer.getAccounts(),
coords = xPlayer.getCoords(),
identifier = xPlayer.getIdentifier(),
inventory = xPlayer.getInventory(),
job = xPlayer.getJob(),
loadout = xPlayer.getLoadout(),
maxWeight = xPlayer.getMaxWeight(),
money = xPlayer.getMoney(),
dead = false
}, isNew, userData.skin)
-- Job
if ESX.DoesJobExist(job, grade) then
jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade]
else
print(('[^3WARNING^7] Ignoring invalid job for %s [job: %s, grade: %s]'):format(identifier, job, grade))
job, grade = 'unemployed', '0'
jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade]
end
xPlayer.triggerEvent('esx:createMissingPickups', ESX.Pickups)
xPlayer.triggerEvent('esx:registerSuggestions', ESX.RegisteredCommands)
print(('[^2INFO^0] Player ^5"%s" ^0has connected to the server. ID: ^5%s^7'):format(xPlayer.getName(), playerId))
userData.job.id = jobObject.id
userData.job.name = jobObject.name
userData.job.label = jobObject.label
userData.job.grade = tonumber(grade)
userData.job.grade_name = gradeObject.name
userData.job.grade_label = gradeObject.label
userData.job.grade_salary = gradeObject.salary
userData.job.skin_male = {}
userData.job.skin_female = {}
if gradeObject.skin_male then userData.job.skin_male = json.decode(gradeObject.skin_male) end
if gradeObject.skin_female then userData.job.skin_female = json.decode(gradeObject.skin_female) end
-- Inventory
if result.inventory and result.inventory ~= '' then
local inventory = json.decode(result.inventory)
for name,count in pairs(inventory) do
local item = ESX.Items[name]
if item then
foundItems[name] = count
else
print(('[^3WARNING^7] Ignoring invalid item "%s" for "%s"'):format(name, identifier))
end
end
end
for name,item in pairs(ESX.Items) do
local count = foundItems[name] or 0
if count > 0 then userData.weight = userData.weight + (item.weight * count) end
table.insert(userData.inventory, {
name = name,
count = count,
label = item.label,
weight = item.weight,
usable = Core.UsableItemsCallbacks[name] ~= nil,
rare = item.rare,
canRemove = item.canRemove
})
end
table.sort(userData.inventory, function(a, b)
return a.label < b.label
end)
-- Group
if result.group then
if result.group == "superadmin" then
userData.group = "admin"
else
userData.group = result.group
end
else
userData.group = 'user'
end
-- Loadout
if result.loadout and result.loadout ~= '' then
local loadout = json.decode(result.loadout)
for name,weapon in pairs(loadout) do
local label = ESX.GetWeaponLabel(name)
if label then
if not weapon.components then weapon.components = {} end
if not weapon.tintIndex then weapon.tintIndex = 0 end
table.insert(userData.loadout, {
name = name,
ammo = weapon.ammo,
label = label,
components = weapon.components,
tintIndex = weapon.tintIndex
})
end
end
end
-- Position
if result.position and result.position ~= '' then
userData.coords = json.decode(result.position)
else
print('[^3WARNING^7] Column ^5"position"^0 in ^5"users"^0 table is missing required default value. Using backup coords, fix your database.')
userData.coords = {x = -269.4, y = -955.3, z = 31.2, heading = 205.8}
end
-- Skin
if result.skin and result.skin ~= '' then
userData.skin = json.decode(result.skin)
else
if userData.sex == 'f' then userData.skin = {sex=1} else userData.skin = {sex=0} end
end
-- Identity
if result.firstname and result.firstname ~= '' then
userData.firstname = result.firstname
userData.lastname = result.lastname
userData.playerName = userData.firstname..' '..userData.lastname
if result.dateofbirth then userData.dateofbirth = result.dateofbirth end
if result.sex then userData.sex = result.sex end
if result.height then userData.height = result.height end
end
local xPlayer = CreateExtendedPlayer(playerId, identifier, userData.group, userData.accounts, userData.inventory, userData.weight, userData.job, userData.loadout, userData.playerName, userData.coords)
ESX.Players[playerId] = xPlayer
if userData.firstname then
xPlayer.set('firstName', userData.firstname)
xPlayer.set('lastName', userData.lastname)
if userData.dateofbirth then xPlayer.set('dateofbirth', userData.dateofbirth) end
if userData.sex then xPlayer.set('sex', userData.sex) end
if userData.height then xPlayer.set('height', userData.height) end
end
TriggerEvent('esx:playerLoaded', playerId, xPlayer, isNew)
xPlayer.triggerEvent('esx:playerLoaded', {
accounts = xPlayer.getAccounts(),
coords = xPlayer.getCoords(),
identifier = xPlayer.getIdentifier(),
inventory = xPlayer.getInventory(),
job = xPlayer.getJob(),
loadout = xPlayer.getLoadout(),
maxWeight = xPlayer.getMaxWeight(),
money = xPlayer.getMoney(),
dead = false
}, isNew, userData.skin)
xPlayer.triggerEvent('esx:createMissingPickups', Core.Pickups)
xPlayer.triggerEvent('esx:registerSuggestions', Core.RegisteredCommands)
print(('[^2INFO^0] Player ^5"%s" ^0has connected to the server. ID: ^5%s^7'):format(xPlayer.getName(), playerId))
end
AddEventHandler('chatMessage', function(playerId, author, message)
@@ -319,7 +297,7 @@ AddEventHandler('playerDropped', function(reason)
if xPlayer then
TriggerEvent('esx:playerDropped', playerId, reason)
ESX.SavePlayer(xPlayer, function()
Core.SavePlayer(xPlayer, function()
ESX.Players[playerId] = nil
end)
end
@@ -331,7 +309,7 @@ if Config.Multichar then
if xPlayer then
TriggerEvent('esx:playerDropped', playerId, reason)
ESX.SavePlayer(xPlayer, function()
Core.SavePlayer(xPlayer, function()
ESX.Players[playerId] = nil
end)
end
@@ -512,7 +490,7 @@ end)
RegisterNetEvent('esx:onPickup')
AddEventHandler('esx:onPickup', function(pickupId)
local pickup, xPlayer, success = ESX.Pickups[pickupId], ESX.GetPlayerFromId(source)
local pickup, xPlayer, success = Core.Pickups[pickupId], ESX.GetPlayerFromId(source)
if pickup then
if pickup.type == 'item_standard' then
@@ -540,7 +518,7 @@ AddEventHandler('esx:onPickup', function(pickupId)
end
if success then
ESX.Pickups[pickupId] = nil
Core.Pickups[pickupId] = nil
TriggerClientEvent('esx:removePickup', -1, pickupId)
end
end
@@ -592,7 +570,7 @@ AddEventHandler('txAdmin:events:scheduledRestart', function(eventData)
if eventData.secondsRemaining == 60 then
Citizen.CreateThread(function()
Citizen.Wait(50000)
ESX.SavePlayers()
Core.SavePlayers()
end)
end
end)