Merge pull request #1169 from thelindat/multichar

feat+improvements(legacy): Multicharacter support; bug fixes and optimisation; retrieve player identity and death status on load
This commit is contained in:
Taso
2021-06-04 21:17:32 -04:00
committed by GitHub
11 changed files with 265 additions and 123 deletions
+24 -12
View File
@@ -42,8 +42,10 @@ end
ESX.SetPlayerData = function(key, val) ESX.SetPlayerData = function(key, val)
ESX.PlayerData[key] = val ESX.PlayerData[key] = val
if key ~= 'inventory' and key ~= 'loadout' then
TriggerEvent('esx:setPlayerData', key, val) TriggerEvent('esx:setPlayerData', key, val)
end end
end
ESX.ShowNotification = function(msg) ESX.ShowNotification = function(msg)
SetNotificationTextEntry('STRING') SetNotificationTextEntry('STRING')
@@ -141,6 +143,13 @@ ESX.UI.HUD.RemoveElement = function(name)
}) })
end end
ESX.UI.HUD.Reset = function()
SendNUIMessage({
action = 'resetHUDElements'
})
ESX.UI.HUD.RegisteredElements = {}
end
ESX.UI.HUD.UpdateElement = function(name, data) ESX.UI.HUD.UpdateElement = function(name, data)
SendNUIMessage({ SendNUIMessage({
action = 'updateHUDElement', action = 'updateHUDElement',
@@ -314,7 +323,7 @@ ESX.Game.Teleport = function(entity, coords, cb)
if DoesEntityExist(entity) then if DoesEntityExist(entity) then
RequestCollisionAtCoord(vector.xyz) RequestCollisionAtCoord(vector.xyz)
while not HasCollisionLoadedAroundEntity(entity) do while not HasCollisionLoadedAroundEntity(entity) do
Wait(0) Citizen.Wait(0)
end end
SetEntityCoords(entity, vector.xyz, false, false, false, false) SetEntityCoords(entity, vector.xyz, false, false, false, false)
@@ -332,7 +341,7 @@ ESX.Game.SpawnObject = function(object, coords, cb, networked, dynamic)
networked = networked == nil and true or false networked = networked == nil and true or false
dynamic = dynamic ~= nil and true or false dynamic = dynamic ~= nil and true or false
CreateThread(function() Citizen.CreateThread(function()
ESX.Streaming.RequestModel(model) ESX.Streaming.RequestModel(model)
-- The below has to be done just for CreateObject since for some reason CreateObjects model argument is set -- The below has to be done just for CreateObject since for some reason CreateObjects model argument is set
@@ -364,7 +373,7 @@ ESX.Game.SpawnVehicle = function(vehicle, coords, heading, cb, networked)
local model = (type(vehicle) == 'number' and vehicle or GetHashKey(vehicle)) local model = (type(vehicle) == 'number' and vehicle or GetHashKey(vehicle))
local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z) local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z)
networked = networked == nil and true or false networked = networked == nil and true or false
CreateThread(function() Citizen.CreateThread(function()
ESX.Streaming.RequestModel(model) ESX.Streaming.RequestModel(model)
local vehicle = CreateVehicle(model, vector.xyz, heading, networked, false) local vehicle = CreateVehicle(model, vector.xyz, heading, networked, false)
@@ -381,7 +390,7 @@ ESX.Game.SpawnVehicle = function(vehicle, coords, heading, cb, networked)
RequestCollisionAtCoord(vector.xyz) RequestCollisionAtCoord(vector.xyz)
while not HasCollisionLoadedAroundEntity(vehicle) do while not HasCollisionLoadedAroundEntity(vehicle) do
Wait(0) Citizen.Wait(0)
end end
if cb then if cb then
@@ -699,14 +708,20 @@ ESX.Game.SetVehicleProperties = function(vehicle, props)
end end
end end
ESX.Game.Utils.DrawText3D = function(coords, text, scale, font) ESX.Game.Utils.DrawText3D = function(coords, text, size, font)
local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z) local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z)
if scale then scale = scale + 0.0 end
if not scale then scale = 0.35 end local camCoords = GetGameplayCamCoords()
if not font then font = 4 end local distance = #(vector - camCoords)
SetTextScale(scale, scale) if not size then size = 1 end
if not font then font = 0 end
local scale = (size / distance) * 2
local fov = (1 / GetGameplayCamFov()) * 100
scale = scale * fov
SetTextScale(0.0 * scale, 0.55 * scale)
SetTextFont(font) SetTextFont(font)
SetTextProportional(1) SetTextProportional(1)
SetTextColour(255, 255, 255, 215) SetTextColour(255, 255, 255, 215)
@@ -715,9 +730,6 @@ ESX.Game.Utils.DrawText3D = function(coords, text, scale, font)
AddTextComponentString(text) AddTextComponentString(text)
SetDrawOrigin(vector.xyz, 0) SetDrawOrigin(vector.xyz, 0)
DrawText(0.0, 0.0) DrawText(0.0, 0.0)
local scaleFactor = scale / 0.35
local length = string.len(string.gsub(text, "(~[rbgypcmuosh]~)", ""))
DrawRect(0.0, 0.0 + 0.0111 * scaleFactor, 0.027 + (length / 370) * scaleFactor, 0.03 * scaleFactor, 0, 0, 0, 75)
ClearDrawOrigin() ClearDrawOrigin()
end end
+57 -34
View File
@@ -1,23 +1,52 @@
local isPaused, isDead, pickups = false, false, {} local pickups = {}
Citizen.CreateThread(function() Citizen.CreateThread(function()
while NetworkIsPlayerActive(PlayerId()) do while NetworkIsPlayerActive(PlayerId()) and not Config.Multichar do
Citizen.Wait(5) Citizen.Wait(5)
DoScreenFadeOut(0)
TriggerServerEvent('esx:onPlayerJoined') TriggerServerEvent('esx:onPlayerJoined')
break break
end end
end) end)
RegisterNetEvent('esx:playerLoaded') RegisterNetEvent('esx:playerLoaded')
AddEventHandler('esx:playerLoaded', function(playerData, isNew) AddEventHandler('esx:playerLoaded', function(playerData, isNew, skin)
ESX.PlayerLoaded = true ESX.PlayerLoaded = true
ESX.PlayerData = playerData ESX.PlayerData = playerData
ESX.PlayerData.ped = ESX.PlayerData.ped
FreezeEntityPosition(PlayerPedId(), true)
FreezeEntityPosition(ESX.PlayerData.ped, true) if Config.Multichar then
TriggerEvent('esx_multicharacter:SpawnCharacter', playerData.coords, isNew)
Citizen.Wait(3000)
else
exports.spawnmanager:spawnPlayer({
x = playerData.coords.x,
y = playerData.coords.y,
z = playerData.coords.z + 0.25,
heading = playerData.coords.heading,
model = `mp_m_freemode_01`,
skipFade = false
}, 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('esx:loadingScreenOff')
ShutdownLoadingScreen()
ShutdownLoadingScreenNui()
FreezeEntityPosition(ESX.PlayerData.ped, false)
end)
end
while ESX.PlayerData.ped == nil do Citizen.Wait(20) end
-- enable PVP -- enable PVP
if Config.EnablePVP then if Config.EnablePVP then
SetCanAttackFriendly(ESX.PlayerData.ped, true, false) SetCanAttackFriendly(ESX.PlayerData.ped, true, false)
@@ -41,26 +70,13 @@ AddEventHandler('esx:playerLoaded', function(playerData, isNew)
grade_label = playerData.job.grade_label grade_label = playerData.job.grade_label
}) })
end end
exports.spawnmanager:spawnPlayer({
x = playerData.coords.x,
y = playerData.coords.y,
z = playerData.coords.z + 0.25,
heading = playerData.coords.heading,
model = `mp_m_freemode_01`,
skipFade = false
}, function()
TriggerEvent('esx:onPlayerSpawn')
TriggerEvent('playerSpawned') -- compatibility with old scripts, will be removed soon.
TriggerEvent('esx:restoreLoadout')
Citizen.Wait(4000)
ShutdownLoadingScreen()
ShutdownLoadingScreenNui()
FreezeEntityPosition(ESX.PlayerData.ped, false)
StartServerSyncLoops() StartServerSyncLoops()
end) end)
TriggerEvent('esx:loadingScreenOff') -- compatibility with old scripts, will be removed soon.
RegisterNetEvent('esx:onPlayerLogout')
AddEventHandler('esx:onPlayerLogout', function()
ESX.PlayerLoaded = false
if Config.EnableHud then ESX.UI.HUD.Reset() end
end) end)
RegisterNetEvent('esx:setMaxWeight') RegisterNetEvent('esx:setMaxWeight')
@@ -70,14 +86,12 @@ AddEventHandler('esx:onPlayerSpawn', function()
local playerPed = PlayerPedId() local playerPed = PlayerPedId()
if ESX.PlayerData.ped ~= playerPed then ESX.SetPlayerData('ped', playerPed) end if ESX.PlayerData.ped ~= playerPed then ESX.SetPlayerData('ped', playerPed) end
ESX.SetPlayerData('dead', false) ESX.SetPlayerData('dead', false)
isDead = false
end) end)
AddEventHandler('esx:onPlayerDeath', function() AddEventHandler('esx:onPlayerDeath', function()
local playerPed = PlayerPedId() local playerPed = PlayerPedId()
if ESX.PlayerData.ped ~= playerPed then ESX.SetPlayerData('ped', playerPed) end if ESX.PlayerData.ped ~= playerPed then ESX.SetPlayerData('ped', playerPed) end
ESX.SetPlayerData('dead', true) ESX.SetPlayerData('dead', true)
isDead = true
end) end)
AddEventHandler('skinchanger:modelLoaded', function() AddEventHandler('skinchanger:modelLoaded', function()
@@ -335,6 +349,7 @@ end)
-- Pause menu disables HUD display -- Pause menu disables HUD display
if Config.EnableHud then if Config.EnableHud then
Citizen.CreateThread(function() Citizen.CreateThread(function()
local isPaused = false
while true do while true do
Citizen.Wait(300) Citizen.Wait(300)
@@ -356,10 +371,17 @@ end
function StartServerSyncLoops() function StartServerSyncLoops()
-- keep track of ammo -- keep track of ammo
Citizen.CreateThread(function() Citizen.CreateThread(function()
local currentWeapon = {timer=0}
while ESX.PlayerLoaded do while ESX.PlayerLoaded do
Citizen.Wait(1000) local sleep = 5
local letSleep = true if currentWeapon.timer == sleep then
local ammoCount = GetAmmoInPedWeapon(ESX.PlayerData.ped, currentWeapon.hash)
TriggerServerEvent('esx:updateWeaponAmmo', currentWeapon.name, ammoCount)
currentWeapon.timer = 0
elseif currentWeapon.timer > sleep then
currentWeapon.timer = currentWeapon.timer - sleep
end
if IsPedArmed(ESX.PlayerData.ped, 4) then if IsPedArmed(ESX.PlayerData.ped, 4) then
if IsPedShooting(ESX.PlayerData.ped) then if IsPedShooting(ESX.PlayerData.ped) then
@@ -367,14 +389,15 @@ function StartServerSyncLoops()
local weapon = ESX.GetWeaponFromHash(weaponHash) local weapon = ESX.GetWeaponFromHash(weaponHash)
if weapon then if weapon then
local ammoCount = GetAmmoInPedWeapon(ESX.PlayerData.ped, weaponHash) currentWeapon.name = weapon.name
TriggerServerEvent('esx:updateWeaponAmmo', weapon.name, ammoCount) currentWeapon.hash = weaponHash
currentWeapon.timer = 100 * sleep
end end
end end
else
sleep = 200
end end
if letSleep then Citizen.Wait(sleep)
Citizen.Wait(500)
end
end end
end) end)
@@ -404,7 +427,7 @@ end
if Config.EnableDefaultInventory then if Config.EnableDefaultInventory then
RegisterCommand('showinv', function() RegisterCommand('showinv', function()
if not isDead and not ESX.UI.Menu.IsOpen('default', 'es_extended', 'inventory') then if not ESX.PlayerData.dead and not ESX.UI.Menu.IsOpen('default', 'es_extended', 'inventory') then
ESX.ShowInventory() ESX.ShowInventory()
end end
end) end)
+2
View File
@@ -18,3 +18,5 @@ Config.EnableDefaultInventory = true -- Display the default Inventory ( F2 )
Config.EnableWantedLevel = false -- Use Normal GTA wanted Level? Config.EnableWantedLevel = false -- Use Normal GTA wanted Level?
Config.EnablePVP = true -- Allow Player to player combat Config.EnablePVP = true -- Allow Player to player combat
Config.Multichar = false -- Enable support for esx_multicharacter
Config.Identity = true -- Select a characters identity data before they have loaded in (this happens by default with multichar)
+2 -1
View File
@@ -101,5 +101,6 @@ server_exports {
dependencies { dependencies {
'mysql-async', 'mysql-async',
'async' 'async',
'spawnmanager',
} }
+10
View File
@@ -41,6 +41,11 @@
ESX.refreshHUD(); ESX.refreshHUD();
}; };
ESX.resetHUDElements = function () {
ESX.HUDElements = [];
ESX.refreshHUD();
};
ESX.refreshHUD = function () { ESX.refreshHUD = function () {
$('#hud').html(''); $('#hud').html('');
@@ -95,6 +100,11 @@
break; break;
} }
case 'resetHUDElements': {
ESX.resetHUDElements();
break;
}
case 'inventoryNotification': { case 'inventoryNotification': {
ESX.inventoryNotification(data.add, data.item, data.count); ESX.inventoryNotification(data.add, data.item, data.count);
} }
+4 -3
View File
@@ -14,8 +14,9 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
self.variables = {} self.variables = {}
self.weight = weight self.weight = weight
self.maxWeight = Config.MaxWeight self.maxWeight = Config.MaxWeight
if Config.Multichar then self.license = 'license'..string.sub(identifier, 6) else self.license = 'license:'..identifier end
ExecuteCommand(('add_principal identifier.license:%s group.%s'):format(self.identifier, self.group)) ExecuteCommand(('add_principal identifier.%s group.%s'):format(self.license, self.group))
self.triggerEvent = function(eventName, ...) self.triggerEvent = function(eventName, ...)
TriggerClientEvent(eventName, self.source, ...) TriggerClientEvent(eventName, self.source, ...)
@@ -66,9 +67,9 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
end end
self.setGroup = function(newGroup) self.setGroup = function(newGroup)
ExecuteCommand(('remove_principal identifier.license:%s group.%s'):format(self.identifier, self.group)) ExecuteCommand(('remove_principal identifier.%s group.%s'):format(self.license, self.group))
self.group = newGroup self.group = newGroup
ExecuteCommand(('add_principal identifier.license:%s group.%s'):format(self.identifier, self.group)) ExecuteCommand(('add_principal identifier.%s group.%s'):format(self.license, self.group))
end end
self.getGroup = function() self.getGroup = function()
+2 -16
View File
@@ -1,4 +1,3 @@
local crash = false
ESX.RegisterCommand('setcoords', 'admin', function(xPlayer, args, showError) ESX.RegisterCommand('setcoords', 'admin', function(xPlayer, args, showError)
xPlayer.setCoords({x = args.x, y = args.y, z = args.z}) xPlayer.setCoords({x = args.x, y = args.y, z = args.z})
end, false, {help = _U('command_setcoords'), validate = true, arguments = { end, false, {help = _U('command_setcoords'), validate = true, arguments = {
@@ -118,8 +117,8 @@ end, true, {help = _U('command_clearinventory'), validate = true, arguments = {
}}) }})
ESX.RegisterCommand('clearloadout', 'admin', function(xPlayer, args, showError) ESX.RegisterCommand('clearloadout', 'admin', function(xPlayer, args, showError)
for k,v in ipairs(args.playerId.loadout) do for i=#args.playerId.loadout, 1, -1 do
args.playerId.removeWeapon(v.name) args.playerId.removeWeapon(args.playerId.loadout[i].name)
end end
end, true, {help = _U('command_clearloadout'), validate = true, arguments = { end, true, {help = _U('command_clearloadout'), validate = true, arguments = {
{name = 'playerId', help = _U('commandgeneric_playerid'), type = 'player'} {name = 'playerId', help = _U('commandgeneric_playerid'), type = 'player'}
@@ -162,19 +161,6 @@ ESX.RegisterCommand('coords', "admin", function(xPlayer, args, showError)
print("".. xPlayer.getName().. ": ^5".. xPlayer.getCoords(true)) print("".. xPlayer.getName().. ": ^5".. xPlayer.getCoords(true))
end, true) end, true)
ESX.RegisterCommand('sv_restart', "admin", function(xPlayer, args, showError)
Citizen.CreateThread(function()
ESX.SavePlayers()
for _, playerId in ipairs(GetPlayers()) do
DropPlayer(playerId, "Server Restart")
end
print('^2[INFO] ^1 Server Restarting, Please Wait :)')
crash = true
while crash do
end
end)
end, true)
ESX.RegisterCommand('tpm', "admin", function(xPlayer, args, showError) ESX.RegisterCommand('tpm', "admin", function(xPlayer, args, showError)
xPlayer.triggerEvent("esx:tpm") xPlayer.triggerEvent("esx:tpm")
end, true) end, true)
+8 -6
View File
@@ -30,27 +30,29 @@ MySQL.ready(function()
end end
end) end)
local Jobs = {}
MySQL.Async.fetchAll('SELECT * FROM jobs', {}, function(jobs) MySQL.Async.fetchAll('SELECT * FROM jobs', {}, function(jobs)
for k,v in ipairs(jobs) do for k,v in ipairs(jobs) do
ESX.Jobs[v.name] = v Jobs[v.name] = v
ESX.Jobs[v.name].grades = {} Jobs[v.name].grades = {}
end end
MySQL.Async.fetchAll('SELECT * FROM job_grades', {}, function(jobGrades) MySQL.Async.fetchAll('SELECT * FROM job_grades', {}, function(jobGrades)
for k,v in ipairs(jobGrades) do for k,v in ipairs(jobGrades) do
if ESX.Jobs[v.job_name] then if Jobs[v.job_name] then
ESX.Jobs[v.job_name].grades[tostring(v.grade)] = v Jobs[v.job_name].grades[tostring(v.grade)] = v
else else
print(('[^3WARNING^7] Ignoring job grades for ^5"%s"^0 due to missing job'):format(v.job_name)) print(('[^3WARNING^7] Ignoring job grades for ^5"%s"^0 due to missing job'):format(v.job_name))
end end
end end
for k2,v2 in pairs(ESX.Jobs) do for k2,v2 in pairs(Jobs) do
if ESX.Table.SizeOf(v2.grades) == 0 then if ESX.Table.SizeOf(v2.grades) == 0 then
ESX.Jobs[v2.name] = nil Jobs[v2.name] = nil
print(('[^3WARNING^7] Ignoring job ^5"%s"^0due to no job grades found'):format(v2.name)) print(('[^3WARNING^7] Ignoring job ^5"%s"^0due to no job grades found'):format(v2.name))
end end
end end
ESX.Jobs = Jobs
end) end)
end) end)
+19 -9
View File
@@ -163,19 +163,25 @@ ESX.TriggerServerCallback = function(name, requestId, source, cb, ...)
end end
end end
local savePlayers = -1
Citizen.CreateThread(function()
savePlayers = MySQL.Sync.store("UPDATE users SET ? WHERE ?")
end)
ESX.SavePlayer = function(xPlayer, cb) ESX.SavePlayer = function(xPlayer, cb)
local asyncTasks = {} local asyncTasks = {}
table.insert(asyncTasks, function(cb2) table.insert(asyncTasks, function(cb2)
MySQL.Async.execute('UPDATE users SET accounts = @accounts, job = @job, job_grade = @job_grade, `group` = @group, loadout = @loadout, position = @position, inventory = @inventory WHERE identifier = @identifier', { MySQL.Async.execute(savePlayers, {
['@accounts'] = json.encode(xPlayer.getAccounts(true)), {
['@job'] = xPlayer.job.name, ['accounts'] = json.encode(xPlayer.getAccounts(true)),
['@job_grade'] = xPlayer.job.grade, ['job'] = xPlayer.job.name,
['@group'] = xPlayer.getGroup(), ['job_grade'] = xPlayer.job.grade,
['@loadout'] = json.encode(xPlayer.getLoadout(true)), ['group'] = xPlayer.getGroup(),
['@position'] = json.encode(xPlayer.getCoords()), ['loadout'] = json.encode(xPlayer.getLoadout(true)),
['@identifier'] = xPlayer.getIdentifier(), ['position'] = json.encode(xPlayer.getCoords()),
['@inventory'] = json.encode(xPlayer.getInventory(true)) ['inventory'] = json.encode(xPlayer.getInventory(true))
}, {['identifier'] = xPlayer.getIdentifier()}
}, function(rowsChanged) }, function(rowsChanged)
cb2() cb2()
end) end)
@@ -227,6 +233,10 @@ ESX.GetPlayers = function()
return sources return sources
end end
ESX.GetExtendedPlayers = function()
return ESX.Players
end
ESX.GetPlayerFromId = function(source) ESX.GetPlayerFromId = function(source)
return ESX.Players[tonumber(source)] return ESX.Players[tonumber(source)]
end end
+112 -17
View File
@@ -1,28 +1,62 @@
local NewPlayer, LoadPlayer = -1, -1
Citizen.CreateThread(function() Citizen.CreateThread(function()
SetMapName('San Andreas') SetMapName('San Andreas')
SetGameType('ESX Roleplay') SetGameType('ESX Roleplay')
MySQL.Async.store("INSERT INTO users SET ?", function(storeId)
NewPlayer = storeId
end) end)
local query = '`accounts`, `job`, `job_grade`, `group`, `position`, `inventory`, `skin`'
if Config.Multichar or Config.Identity then query = query..', `firstname`, `lastname`, `dateofbirth`, `sex`, `height`' end
MySQL.Async.store("SELECT "..query.." FROM `users` WHERE ?? LIKE ?", function(storeId)
LoadPlayer = storeId
end)
end)
local awaitingRegistration = {}
RegisterNetEvent('esx:onPlayerJoined') RegisterNetEvent('esx:onPlayerJoined')
if Config.Multichar then
AddEventHandler('esx_identity:completedRegistration', function(playerId, data)
awaitingRegistration[playerId] = data
end)
AddEventHandler('esx:onPlayerJoined', function(src, char, isNew)
if not ESX.Players[src] then
onPlayerJoined(src, char, isNew)
end
end)
else
AddEventHandler('esx:onPlayerJoined', function() AddEventHandler('esx:onPlayerJoined', function()
if not ESX.Players[source] then if not ESX.Players[source] then
onPlayerJoined(source) onPlayerJoined(source)
end end
end) end)
end
function onPlayerJoined(playerId) function onPlayerJoined(playerId, char, isNew)
local identifier = ESX.GetIdentifier(playerId) local identifier = ESX.GetIdentifier(playerId)
if char then identifier = char..':'..identifier end
if identifier then if identifier then
if ESX.GetPlayerFromIdentifier(identifier) then 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)) 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))
elseif Config.Multichar and isNew then
createESXPlayer(identifier, playerId)
else else
MySQL.Async.fetchScalar('SELECT 1 FROM users WHERE identifier = @identifier', { MySQL.Async.fetchScalar('SELECT 1 FROM users WHERE identifier = @identifier', {
['@identifier'] = identifier ['@identifier'] = identifier
}, function(result) }, function(result)
if result then if result then
loadESXPlayer(identifier, playerId, false) loadESXPlayer(identifier, playerId, false)
else createESXPlayer(identifier, playerId) end
end)
end
else 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.')
end
end
function createESXPlayer(identifier, playerId)
local accounts = {} local accounts = {}
for account,money in pairs(Config.StartingAccountMoney) do for account,money in pairs(Config.StartingAccountMoney) do
@@ -36,18 +70,38 @@ function onPlayerJoined(playerId)
defaultGroup = "user" defaultGroup = "user"
end end
MySQL.Async.execute('INSERT INTO users (`accounts`, `identifier`, `group`) VALUES (@accounts, @identifier, @group)', { if not Config.Multichar then
['@accounts'] = json.encode(accounts), MySQL.Async.execute(NewPlayer, {
['@identifier'] = identifier, {
['@group'] = defaultGroup ['accounts'] = json.encode(accounts),
['identifier'] = identifier,
['group'] = defaultGroup,
}
}, function(rowsChanged) }, function(rowsChanged)
loadESXPlayer(identifier, playerId, true) loadESXPlayer(identifier, playerId, true)
end) end)
end
end)
end
else 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.') local data
awaitingRegistration[playerId] = true
while true do
Citizen.Wait(250)
if awaitingRegistration[playerId] ~= true then data = awaitingRegistration[playerId] break end
end
awaitingRegistration[playerId] = nil
MySQL.Async.execute(NewPlayer, {
{
['accounts'] = json.encode(accounts),
['identifier'] = identifier,
['group'] = defaultGroup,
['firstname'] = data.firstname,
['lastname'] = data.lastname,
['dateofbirth'] = data.dateofbirth,
['sex'] = data.sex,
['height'] = data.height,
}
}, function(rowsChanged)
loadESXPlayer(identifier, playerId, true)
end)
end end
end end
@@ -81,8 +135,7 @@ function loadESXPlayer(identifier, playerId, isNew)
} }
table.insert(tasks, function(cb) table.insert(tasks, function(cb)
MySQL.Async.fetchAll('SELECT accounts, job, job_grade, `group`, loadout, position, inventory FROM users WHERE identifier = @identifier', { MySQL.Async.fetchAll(LoadPlayer, {'identifier', {identifier}
['@identifier'] = identifier
}, function(result) }, function(result)
local job, grade, jobObject, gradeObject = result[1].job, tostring(result[1].job_grade) local job, grade, jobObject, gradeObject = result[1].job, tostring(result[1].job_grade)
local foundAccounts, foundItems = {}, {} local foundAccounts, foundItems = {}, {}
@@ -203,6 +256,23 @@ function loadESXPlayer(identifier, playerId, isNew)
userData.coords = {x = -269.4, y = -955.3, z = 31.2, heading = 205.8} userData.coords = {x = -269.4, y = -955.3, z = 31.2, heading = 205.8}
end 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() cb()
end) end)
end) end)
@@ -210,6 +280,15 @@ function loadESXPlayer(identifier, playerId, isNew)
Async.parallel(tasks, function(results) 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) 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 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) TriggerEvent('esx:playerLoaded', playerId, xPlayer, isNew)
xPlayer.triggerEvent('esx:playerLoaded', { xPlayer.triggerEvent('esx:playerLoaded', {
@@ -220,8 +299,9 @@ function loadESXPlayer(identifier, playerId, isNew)
job = xPlayer.getJob(), job = xPlayer.getJob(),
loadout = xPlayer.getLoadout(), loadout = xPlayer.getLoadout(),
maxWeight = xPlayer.getMaxWeight(), maxWeight = xPlayer.getMaxWeight(),
money = xPlayer.getMoney() money = xPlayer.getMoney(),
}, isNew) dead = 0
}, isNew, userData.skin)
xPlayer.triggerEvent('esx:createMissingPickups', ESX.Pickups) xPlayer.triggerEvent('esx:createMissingPickups', ESX.Pickups)
xPlayer.triggerEvent('esx:registerSuggestions', ESX.RegisteredCommands) xPlayer.triggerEvent('esx:registerSuggestions', ESX.RegisteredCommands)
@@ -250,6 +330,21 @@ AddEventHandler('playerDropped', function(reason)
end end
end) end)
if Config.Multichar then
AddEventHandler('esx:playerLogout', function(playerId)
local xPlayer = ESX.GetPlayerFromId(playerId)
awaitingRegistration[playerId] = nil
if xPlayer then
TriggerEvent('esx:playerDropped', playerId, reason)
ESX.SavePlayer(xPlayer, function()
ESX.Players[playerId] = nil
end)
end
TriggerClientEvent("esx:onPlayerLogout", playerId)
end)
end
RegisterNetEvent('esx:updateCoords') RegisterNetEvent('esx:updateCoords')
AddEventHandler('esx:updateCoords', function(coords) AddEventHandler('esx:updateCoords', function(coords)
local xPlayer = ESX.GetPlayerFromId(source) local xPlayer = ESX.GetPlayerFromId(source)
@@ -525,7 +620,7 @@ Citizen.CreateThread(
([[ ([[
^1---------------------------------------------------------------------- ^1----------------------------------------------------------------------
^1URGENT: YOUR ES-EXTENDED IS OUTDATED! ^1URGENT: YOUR ES_EXTENDED IS OUTDATED!
^1COMMIT UPDATE: ^5%s AVAILABLE ^1COMMIT UPDATE: ^5%s AVAILABLE
^1DOWNLOAD:^5 https://github.com/esx-framework/es_extended/tree/legacy ^1DOWNLOAD:^5 https://github.com/esx-framework/es_extended/tree/legacy
^1CHANGELOG:^5 %s ^1CHANGELOG:^5 %s
@@ -540,7 +635,7 @@ Citizen.CreateThread(
([[ ([[
^8------------------------------------------------------- ^8-------------------------------------------------------
^2Your Es-extended is the latest version! ^2Your es_extended is the latest version!
^5Version:^0 %s ^5Version:^0 %s
^5COMMIT:^0 %s ^5COMMIT:^0 %s
^5CHANGELOG:^0 %s ^5CHANGELOG:^0 %s
@@ -556,7 +651,7 @@ Citizen.CreateThread(
print( print(
([[ ([[
^1---------------------------------------------------------------------- ^1----------------------------------------------------------------------
^1URGENT: YOUR ES-EXTENDED IS OUTDATATED!!! ^1URGENT: YOUR ES_EXTENDED IS OUTDATATED!!!
^1COMMIT UPDATE: ^5%s AVAILABLE ^1COMMIT UPDATE: ^5%s AVAILABLE
^1DOWNLOAD:^5 https://github.com/esx-framework/es_extended/tree/legacy ^1DOWNLOAD:^5 https://github.com/esx-framework/es_extended/tree/legacy
^1CHANGELOG:^5 %s ^1CHANGELOG:^5 %s
@@ -568,7 +663,7 @@ Citizen.CreateThread(
) )
end end
else else
print('[^1ERROR^0] Es-Extended unable to check version!') print('[^1ERROR^0] es_extended unable to check version!')
end end
end, end,
'GET' 'GET'
+2 -2
View File
@@ -1,5 +1,5 @@
{ {
"version": "legacy", "version": "legacy",
"commit" : "1.2.0", "commit" : "1.3.1",
"changelog": "Preserve compatibility for getting hashes; store player ped and death in PlayerData; resolve typos and outdated prints" "changelog": "\n- Improved spawn event triggers\n- Improved support for relogging\n- Set PlayerData.dead initial value based on `is_dead` column (or 0)\n- Added a MySQL.store for the loadESXPlayer function\n- Ammo sync now waits for the player to stop shooting before sending a server event\n- /clearloadout now clears all player weapons on the first try\n- Minor fixes and optimisations"
} }