From 2b66054567b44fb66a906a00d99cbee0de1d14a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Burak=20G=C3=BCne=C5=9F?= Date: Sat, 13 Sep 2025 15:06:23 +0300 Subject: [PATCH] Add player health and armor persistence on join/leave Introduces a new config option to keep and restore the player's last saved health and armor values when they join the game. Updates client and server event handling to save these stats on disconnect and restore them securely on reconnect, improving player experience and preventing stat loss or abuse. --- client/events.lua | 13 +++++++++++++ config.lua | 4 +++- server/events.lua | 13 +++++++++++-- server/player.lua | 10 +++++++--- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/client/events.lua b/client/events.lua index f08e0195e..018d9ac80 100644 --- a/client/events.lua +++ b/client/events.lua @@ -1,3 +1,5 @@ +local lastStatsLoaded = false + -- Player load and unload handling -- New method for checking if logged in across all scripts (optional) -- if LocalPlayer.state['isLoggedIn'] then @@ -10,7 +12,18 @@ RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() end) RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() + if not LocalPlayer.state.isLoggedIn then return end LocalPlayer.state:set('isLoggedIn', false, false) + lastStatsLoaded = false +end) + +RegisterNetEvent('QBCore:Client:LoadLastStats', function(data) + if not QBConfig.Player.KeepLastHealthArmor then return end + if lastStatsLoaded then return end -- Prevents loading multiple times (We are making this event more secure for cheaters) + local ped = PlayerPedId() + SetEntityHealth(ped, data.health) + SetPedArmour(ped, data.armor) + lastStatsLoaded = true end) RegisterNetEvent('QBCore:Client:PvpHasToggled', function(pvp_state) diff --git a/config.lua b/config.lua index 3c773097b..0ad506141 100644 --- a/config.lua +++ b/config.lua @@ -8,7 +8,7 @@ QBConfig.StatusInterval = 5000 -- how often to check hu QBConfig.Money = {} QBConfig.Money.MoneyTypes = { cash = 500, bank = 5000, crypto = 0 } -- type = startamount - Add or remove money types for your server (for ex. blackmoney = 0), remember once added it will not be removed from the database! QBConfig.Money.DontAllowMinus = { 'cash', 'crypto' } -- Money that is not allowed going in minus -QBConfig.Money.MinusLimit = -5000 -- The maximum amount you can be negative +QBConfig.Money.MinusLimit = -5000 -- The maximum amount you can be negative QBConfig.Money.PayCheckTimeOut = 10 -- The time in minutes that it will give the paycheck QBConfig.Money.PayCheckSociety = false -- If true paycheck will come from the society account that the player is employed at, requires qb-management @@ -18,6 +18,7 @@ QBConfig.Player.ThirstRate = 3.8 -- Rate at which thirst goes down. QBConfig.Player.Bloodtypes = { 'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-', } +QBConfig.Player.KeepLastHealthArmor = true -- If true, restores the player's last saved health and armor values when they join the game. QBConfig.Player.PlayerDefaults = { citizenid = function() return QBCore.Player.CreateCitizenId() end, @@ -67,6 +68,7 @@ QBConfig.Player.PlayerDefaults = { isdead = false, inlaststand = false, armor = 0, + health = 200, ishandcuffed = false, tracker = false, injail = 0, diff --git a/server/events.lua b/server/events.lua index 00b0fa5a9..74295f1b4 100644 --- a/server/events.lua +++ b/server/events.lua @@ -10,16 +10,25 @@ end) AddEventHandler('playerDropped', function(reason) local src = source if not QBCore.Players[src] then return end + local Player = QBCore.Players[src] + local playerPed = GetPlayerPed(src) + local playerCoords = GetEntityCoords(playerPed) + + QBCore.Players[src].metadata.armor = GetPedArmour(playerPed) + QBCore.Players[src].metadata.health = GetEntityHealth(playerPed) + QBCore.Players[src].position = vector4(playerCoords.x, playerCoords.y, playerCoords.z, GetEntityHeading(playerPed)) + TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Dropped', 'red', '**' .. GetPlayerName(src) .. '** (' .. Player.PlayerData.license .. ') left..' .. '\n **Reason:** ' .. reason) TriggerEvent('QBCore:Server:PlayerDropped', Player) + Player.Functions.Save() QBCore.Player_Buckets[Player.PlayerData.license] = nil QBCore.Players[src] = nil end) AddEventHandler("onResourceStop", function(resName) - for i,v in pairs(QBCore.UsableItems) do + for i, v in pairs(QBCore.UsableItems) do if v.resource == resName then QBCore.UsableItems[i] = nil end @@ -36,7 +45,7 @@ if readyFunction ~= nil then local DatabaseInfo = QBCore.Functions.GetDatabaseInfo() if not DatabaseInfo or not DatabaseInfo.exists then return end - local result = MySQL.query.await('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = "bans";', {DatabaseInfo.database}) + local result = MySQL.query.await('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = "bans";', { DatabaseInfo.database }) if result and result[1] then bansTableExists = true end diff --git a/server/player.lua b/server/player.lua index 4f73c0d30..cf3dcf628 100644 --- a/server/player.lua +++ b/server/player.lua @@ -420,6 +420,12 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) QBCore.Players[self.PlayerData.source] = self QBCore.Player.Save(self.PlayerData.source) TriggerEvent('QBCore:Server:PlayerLoaded', self) + if QBConfig.Player.KeepLastHealthArmor then + TriggerClientEvent('QBCore:Client:LoadLastStats', self.PlayerData.source, { + health = self.PlayerData.metadata.health, + armor = self.PlayerData.metadata.armor + }) + end self.Functions.UpdatePlayerData() end end @@ -483,8 +489,6 @@ end -- Save player info to database (make sure citizenid is the primary key in your database) function QBCore.Player.Save(source) - local ped = GetPlayerPed(source) - local pcoords = GetEntityCoords(ped) local PlayerData = QBCore.Players[source].PlayerData if PlayerData then MySQL.insert('INSERT INTO players (citizenid, cid, license, name, money, charinfo, job, gang, position, metadata) VALUES (:citizenid, :cid, :license, :name, :money, :charinfo, :job, :gang, :position, :metadata) ON DUPLICATE KEY UPDATE cid = :cid, name = :name, money = :money, charinfo = :charinfo, job = :job, gang = :gang, position = :position, metadata = :metadata', { @@ -496,7 +500,7 @@ function QBCore.Player.Save(source) charinfo = json.encode(PlayerData.charinfo), job = json.encode(PlayerData.job), gang = json.encode(PlayerData.gang), - position = json.encode(pcoords), + position = json.encode(PlayerData.position), metadata = json.encode(PlayerData.metadata) }) if GetResourceState('qb-inventory') ~= 'missing' then exports['qb-inventory']:SaveInventory(source) end