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
+25 -13
View File
@@ -42,7 +42,9 @@ end
ESX.SetPlayerData = function(key, val)
ESX.PlayerData[key] = val
TriggerEvent('esx:setPlayerData', key, val)
if key ~= 'inventory' and key ~= 'loadout' then
TriggerEvent('esx:setPlayerData', key, val)
end
end
ESX.ShowNotification = function(msg)
@@ -141,6 +143,13 @@ ESX.UI.HUD.RemoveElement = function(name)
})
end
ESX.UI.HUD.Reset = function()
SendNUIMessage({
action = 'resetHUDElements'
})
ESX.UI.HUD.RegisteredElements = {}
end
ESX.UI.HUD.UpdateElement = function(name, data)
SendNUIMessage({
action = 'updateHUDElement',
@@ -314,7 +323,7 @@ ESX.Game.Teleport = function(entity, coords, cb)
if DoesEntityExist(entity) then
RequestCollisionAtCoord(vector.xyz)
while not HasCollisionLoadedAroundEntity(entity) do
Wait(0)
Citizen.Wait(0)
end
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
dynamic = dynamic ~= nil and true or false
CreateThread(function()
Citizen.CreateThread(function()
ESX.Streaming.RequestModel(model)
-- 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 vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z)
networked = networked == nil and true or false
CreateThread(function()
Citizen.CreateThread(function()
ESX.Streaming.RequestModel(model)
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)
while not HasCollisionLoadedAroundEntity(vehicle) do
Wait(0)
Citizen.Wait(0)
end
if cb then
@@ -699,14 +708,20 @@ ESX.Game.SetVehicleProperties = function(vehicle, props)
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)
if scale then scale = scale + 0.0 end
if not scale then scale = 0.35 end
if not font then font = 4 end
local camCoords = GetGameplayCamCoords()
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)
SetTextProportional(1)
SetTextColour(255, 255, 255, 215)
@@ -715,9 +730,6 @@ ESX.Game.Utils.DrawText3D = function(coords, text, scale, font)
AddTextComponentString(text)
SetDrawOrigin(vector.xyz, 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()
end
+59 -36
View File
@@ -1,23 +1,52 @@
local isPaused, isDead, pickups = false, false, {}
local pickups = {}
Citizen.CreateThread(function()
while NetworkIsPlayerActive(PlayerId()) do
while NetworkIsPlayerActive(PlayerId()) and not Config.Multichar do
Citizen.Wait(5)
DoScreenFadeOut(0)
TriggerServerEvent('esx:onPlayerJoined')
break
end
end)
RegisterNetEvent('esx:playerLoaded')
AddEventHandler('esx:playerLoaded', function(playerData, isNew)
AddEventHandler('esx:playerLoaded', function(playerData, isNew, skin)
ESX.PlayerLoaded = true
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
if Config.EnablePVP then
SetCanAttackFriendly(ESX.PlayerData.ped, true, false)
@@ -41,26 +70,13 @@ AddEventHandler('esx:playerLoaded', function(playerData, isNew)
grade_label = playerData.job.grade_label
})
end
StartServerSyncLoops()
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()
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)
RegisterNetEvent('esx:setMaxWeight')
@@ -70,14 +86,12 @@ AddEventHandler('esx:onPlayerSpawn', function()
local playerPed = PlayerPedId()
if ESX.PlayerData.ped ~= playerPed then ESX.SetPlayerData('ped', playerPed) end
ESX.SetPlayerData('dead', false)
isDead = false
end)
AddEventHandler('esx:onPlayerDeath', function()
local playerPed = PlayerPedId()
if ESX.PlayerData.ped ~= playerPed then ESX.SetPlayerData('ped', playerPed) end
ESX.SetPlayerData('dead', true)
isDead = true
end)
AddEventHandler('skinchanger:modelLoaded', function()
@@ -335,6 +349,7 @@ end)
-- Pause menu disables HUD display
if Config.EnableHud then
Citizen.CreateThread(function()
local isPaused = false
while true do
Citizen.Wait(300)
@@ -356,10 +371,17 @@ end
function StartServerSyncLoops()
-- keep track of ammo
Citizen.CreateThread(function()
local currentWeapon = {timer=0}
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 IsPedShooting(ESX.PlayerData.ped) then
@@ -367,14 +389,15 @@ function StartServerSyncLoops()
local weapon = ESX.GetWeaponFromHash(weaponHash)
if weapon then
local ammoCount = GetAmmoInPedWeapon(ESX.PlayerData.ped, weaponHash)
TriggerServerEvent('esx:updateWeaponAmmo', weapon.name, ammoCount)
currentWeapon.name = weapon.name
currentWeapon.hash = weaponHash
currentWeapon.timer = 100 * sleep
end
end
else
sleep = 200
end
if letSleep then
Citizen.Wait(500)
end
Citizen.Wait(sleep)
end
end)
@@ -404,7 +427,7 @@ end
if Config.EnableDefaultInventory then
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()
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.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)
+3 -2
View File
@@ -101,5 +101,6 @@ server_exports {
dependencies {
'mysql-async',
'async'
}
'async',
'spawnmanager',
}
+10
View File
@@ -41,6 +41,11 @@
ESX.refreshHUD();
};
ESX.resetHUDElements = function () {
ESX.HUDElements = [];
ESX.refreshHUD();
};
ESX.refreshHUD = function () {
$('#hud').html('');
@@ -95,6 +100,11 @@
break;
}
case 'resetHUDElements': {
ESX.resetHUDElements();
break;
}
case 'inventoryNotification': {
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.weight = weight
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, ...)
TriggerClientEvent(eventName, self.source, ...)
@@ -66,9 +67,9 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
end
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
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
self.getGroup = function()
+2 -16
View File
@@ -1,4 +1,3 @@
local crash = false
ESX.RegisterCommand('setcoords', 'admin', function(xPlayer, args, showError)
xPlayer.setCoords({x = args.x, y = args.y, z = args.z})
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)
for k,v in ipairs(args.playerId.loadout) do
args.playerId.removeWeapon(v.name)
for i=#args.playerId.loadout, 1, -1 do
args.playerId.removeWeapon(args.playerId.loadout[i].name)
end
end, true, {help = _U('command_clearloadout'), validate = true, arguments = {
{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))
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)
xPlayer.triggerEvent("esx:tpm")
end, true)
+8 -6
View File
@@ -30,27 +30,29 @@ MySQL.ready(function()
end
end)
local Jobs = {}
MySQL.Async.fetchAll('SELECT * FROM jobs', {}, function(jobs)
for k,v in ipairs(jobs) do
ESX.Jobs[v.name] = v
ESX.Jobs[v.name].grades = {}
Jobs[v.name] = v
Jobs[v.name].grades = {}
end
MySQL.Async.fetchAll('SELECT * FROM job_grades', {}, function(jobGrades)
for k,v in ipairs(jobGrades) do
if ESX.Jobs[v.job_name] then
ESX.Jobs[v.job_name].grades[tostring(v.grade)] = v
if Jobs[v.job_name] then
Jobs[v.job_name].grades[tostring(v.grade)] = v
else
print(('[^3WARNING^7] Ignoring job grades for ^5"%s"^0 due to missing job'):format(v.job_name))
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
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))
end
end
ESX.Jobs = Jobs
end)
end)
+19 -9
View File
@@ -163,19 +163,25 @@ ESX.TriggerServerCallback = function(name, requestId, source, cb, ...)
end
end
local savePlayers = -1
Citizen.CreateThread(function()
savePlayers = MySQL.Sync.store("UPDATE users SET ? WHERE ?")
end)
ESX.SavePlayer = function(xPlayer, cb)
local asyncTasks = {}
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', {
['@accounts'] = json.encode(xPlayer.getAccounts(true)),
['@job'] = xPlayer.job.name,
['@job_grade'] = xPlayer.job.grade,
['@group'] = xPlayer.getGroup(),
['@loadout'] = json.encode(xPlayer.getLoadout(true)),
['@position'] = json.encode(xPlayer.getCoords()),
['@identifier'] = xPlayer.getIdentifier(),
['@inventory'] = json.encode(xPlayer.getInventory(true))
MySQL.Async.execute(savePlayers, {
{
['accounts'] = json.encode(xPlayer.getAccounts(true)),
['job'] = xPlayer.job.name,
['job_grade'] = xPlayer.job.grade,
['group'] = xPlayer.getGroup(),
['loadout'] = json.encode(xPlayer.getLoadout(true)),
['position'] = json.encode(xPlayer.getCoords()),
['inventory'] = json.encode(xPlayer.getInventory(true))
}, {['identifier'] = xPlayer.getIdentifier()}
}, function(rowsChanged)
cb2()
end)
@@ -227,6 +233,10 @@ ESX.GetPlayers = function()
return sources
end
ESX.GetExtendedPlayers = function()
return ESX.Players
end
ESX.GetPlayerFromId = function(source)
return ESX.Players[tonumber(source)]
end
+131 -36
View File
@@ -1,49 +1,54 @@
local NewPlayer, LoadPlayer = -1, -1
Citizen.CreateThread(function()
SetMapName('San Andreas')
SetGameType('ESX Roleplay')
MySQL.Async.store("INSERT INTO users SET ?", function(storeId)
NewPlayer = storeId
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')
AddEventHandler('esx:onPlayerJoined', function()
if not ESX.Players[source] then
onPlayerJoined(source)
end
end)
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()
if not ESX.Players[source] then
onPlayerJoined(source)
end
end)
end
function onPlayerJoined(playerId)
function onPlayerJoined(playerId, char, isNew)
local identifier = ESX.GetIdentifier(playerId)
if char then identifier = char..':'..identifier end
if 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))
elseif Config.Multichar and isNew then
createESXPlayer(identifier, playerId)
else
MySQL.Async.fetchScalar('SELECT 1 FROM users WHERE identifier = @identifier', {
['@identifier'] = identifier
}, function(result)
if result then
loadESXPlayer(identifier, playerId, false)
else
local accounts = {}
for account,money in pairs(Config.StartingAccountMoney) do
accounts[account] = money
end
if IsPlayerAceAllowed(playerId, "command") then
print(('^2[INFO] ^0 Player ^5%s ^0Has been granted admin permissions via ^5Ace Perms.^7'):format(playerId))
defaultGroup = "admin"
else
defaultGroup = "user"
end
MySQL.Async.execute('INSERT INTO users (`accounts`, `identifier`, `group`) VALUES (@accounts, @identifier, @group)', {
['@accounts'] = json.encode(accounts),
['@identifier'] = identifier,
['@group'] = defaultGroup
}, function(rowsChanged)
loadESXPlayer(identifier, playerId, true)
end)
end
else createESXPlayer(identifier, playerId) end
end)
end
else
@@ -51,6 +56,55 @@ function onPlayerJoined(playerId)
end
end
function createESXPlayer(identifier, playerId)
local accounts = {}
for account,money in pairs(Config.StartingAccountMoney) do
accounts[account] = money
end
if IsPlayerAceAllowed(playerId, "command") then
print(('^2[INFO] ^0 Player ^5%s ^0Has been granted admin permissions via ^5Ace Perms.^7'):format(playerId))
defaultGroup = "admin"
else
defaultGroup = "user"
end
if not Config.Multichar then
MySQL.Async.execute(NewPlayer, {
{
['accounts'] = json.encode(accounts),
['identifier'] = identifier,
['group'] = defaultGroup,
}
}, function(rowsChanged)
loadESXPlayer(identifier, playerId, true)
end)
else
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
AddEventHandler('playerConnecting', function(name, setCallback, deferrals)
deferrals.defer()
local playerId = source
@@ -81,8 +135,7 @@ function loadESXPlayer(identifier, playerId, isNew)
}
table.insert(tasks, function(cb)
MySQL.Async.fetchAll('SELECT accounts, job, job_grade, `group`, loadout, position, inventory FROM users WHERE identifier = @identifier', {
['@identifier'] = identifier
MySQL.Async.fetchAll(LoadPlayer, {'identifier', {identifier}
}, function(result)
local job, grade, jobObject, gradeObject = result[1].job, tostring(result[1].job_grade)
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}
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)
@@ -210,6 +280,15 @@ function loadESXPlayer(identifier, playerId, isNew)
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
end
TriggerEvent('esx:playerLoaded', playerId, xPlayer, isNew)
xPlayer.triggerEvent('esx:playerLoaded', {
@@ -220,8 +299,9 @@ function loadESXPlayer(identifier, playerId, isNew)
job = xPlayer.getJob(),
loadout = xPlayer.getLoadout(),
maxWeight = xPlayer.getMaxWeight(),
money = xPlayer.getMoney()
}, isNew)
money = xPlayer.getMoney(),
dead = 0
}, isNew, userData.skin)
xPlayer.triggerEvent('esx:createMissingPickups', ESX.Pickups)
xPlayer.triggerEvent('esx:registerSuggestions', ESX.RegisteredCommands)
@@ -250,6 +330,21 @@ AddEventHandler('playerDropped', function(reason)
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')
AddEventHandler('esx:updateCoords', function(coords)
local xPlayer = ESX.GetPlayerFromId(source)
@@ -525,7 +620,7 @@ Citizen.CreateThread(
([[
^1----------------------------------------------------------------------
^1URGENT: YOUR ES-EXTENDED IS OUTDATED!
^1URGENT: YOUR ES_EXTENDED IS OUTDATED!
^1COMMIT UPDATE: ^5%s AVAILABLE
^1DOWNLOAD:^5 https://github.com/esx-framework/es_extended/tree/legacy
^1CHANGELOG:^5 %s
@@ -540,7 +635,7 @@ Citizen.CreateThread(
([[
^8-------------------------------------------------------
^2Your Es-extended is the latest version!
^2Your es_extended is the latest version!
^5Version:^0 %s
^5COMMIT:^0 %s
^5CHANGELOG:^0 %s
@@ -556,7 +651,7 @@ Citizen.CreateThread(
print(
([[
^1----------------------------------------------------------------------
^1URGENT: YOUR ES-EXTENDED IS OUTDATATED!!!
^1URGENT: YOUR ES_EXTENDED IS OUTDATATED!!!
^1COMMIT UPDATE: ^5%s AVAILABLE
^1DOWNLOAD:^5 https://github.com/esx-framework/es_extended/tree/legacy
^1CHANGELOG:^5 %s
@@ -568,7 +663,7 @@ Citizen.CreateThread(
)
end
else
print('[^1ERROR^0] Es-Extended unable to check version!')
print('[^1ERROR^0] es_extended unable to check version!')
end
end,
'GET'
+2 -2
View File
@@ -1,5 +1,5 @@
{
"version": "legacy",
"commit" : "1.2.0",
"changelog": "Preserve compatibility for getting hashes; store player ped and death in PlayerData; resolve typos and outdated prints"
"commit" : "1.3.1",
"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"
}