mirror of
https://github.com/qbcore-fivem/qb-core.git
synced 2026-08-28 17:01:16 +00:00
1cee09a394
Add support for sending single-field player data updates to clients to reduce bandwidth. Added a new client event QBCore:Player:UpdatePlayerDataField that updates a single key on QBCore.PlayerData. Modified server QBCore.Player.CreatePlayer UpdatePlayerData to accept an optional key and value; when provided it triggers the new per-field client event, otherwise it falls back to sending the full player data. Updated various player functions to call UpdatePlayerData with specific keys (job, gang, metadata, money, etc.) instead of always triggering a full sync. Files changed: client/events.lua, server/player.lua.
677 lines
29 KiB
Lua
677 lines
29 KiB
Lua
QBCore.Players = {}
|
|
QBCore.Player = {}
|
|
|
|
-- On player login get their data or set defaults
|
|
-- Don't touch any of this unless you know what you are doing
|
|
-- Will cause major issues!
|
|
|
|
local resourceName = GetCurrentResourceName()
|
|
function QBCore.Player.Login(source, citizenid, newData)
|
|
if source and source ~= '' then
|
|
if citizenid then
|
|
local license = QBCore.Functions.GetIdentifier(source, 'license')
|
|
local PlayerData = MySQL.prepare.await('SELECT * FROM players where citizenid = ?', { citizenid })
|
|
if PlayerData and license == PlayerData.license then
|
|
PlayerData.money = json.decode(PlayerData.money)
|
|
PlayerData.job = json.decode(PlayerData.job)
|
|
PlayerData.gang = json.decode(PlayerData.gang)
|
|
PlayerData.position = json.decode(PlayerData.position)
|
|
PlayerData.metadata = json.decode(PlayerData.metadata)
|
|
PlayerData.charinfo = json.decode(PlayerData.charinfo)
|
|
QBCore.Player.CheckPlayerData(source, PlayerData)
|
|
else
|
|
DropPlayer(source, Lang:t('info.exploit_dropped'))
|
|
TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(source) .. ' Has Been Dropped For Character Joining Exploit', false)
|
|
end
|
|
else
|
|
QBCore.Player.CheckPlayerData(source, newData)
|
|
end
|
|
return true
|
|
else
|
|
QBCore.ShowError(resourceName, 'ERROR QBCORE.PLAYER.LOGIN - NO SOURCE GIVEN!')
|
|
return false
|
|
end
|
|
end
|
|
|
|
function QBCore.Player.GetOfflinePlayer(citizenid)
|
|
if citizenid then
|
|
local PlayerData = MySQL.prepare.await('SELECT * FROM players where citizenid = ?', { citizenid })
|
|
if PlayerData then
|
|
PlayerData.money = json.decode(PlayerData.money)
|
|
PlayerData.job = json.decode(PlayerData.job)
|
|
PlayerData.gang = json.decode(PlayerData.gang)
|
|
PlayerData.position = json.decode(PlayerData.position)
|
|
PlayerData.metadata = json.decode(PlayerData.metadata)
|
|
PlayerData.charinfo = json.decode(PlayerData.charinfo)
|
|
return QBCore.Player.CheckPlayerData(nil, PlayerData)
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function QBCore.Player.GetPlayerByLicense(license)
|
|
if license then
|
|
local source = QBCore.Functions.GetSource(license)
|
|
if source > 0 then
|
|
return QBCore.Players[source]
|
|
else
|
|
return QBCore.Player.GetOfflinePlayerByLicense(license)
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function QBCore.Player.GetOfflinePlayerByLicense(license)
|
|
if license then
|
|
local PlayerData = MySQL.prepare.await('SELECT * FROM players where license = ?', { license })
|
|
if PlayerData then
|
|
PlayerData.money = json.decode(PlayerData.money)
|
|
PlayerData.job = json.decode(PlayerData.job)
|
|
PlayerData.gang = json.decode(PlayerData.gang)
|
|
PlayerData.position = json.decode(PlayerData.position)
|
|
PlayerData.metadata = json.decode(PlayerData.metadata)
|
|
PlayerData.charinfo = json.decode(PlayerData.charinfo)
|
|
return QBCore.Player.CheckPlayerData(nil, PlayerData)
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function applyDefaults(playerData, defaults)
|
|
for key, value in pairs(defaults) do
|
|
if type(value) == 'function' then
|
|
playerData[key] = playerData[key] or value()
|
|
elseif type(value) == 'table' then
|
|
playerData[key] = playerData[key] or {}
|
|
applyDefaults(playerData[key], value)
|
|
else
|
|
playerData[key] = playerData[key] or value
|
|
end
|
|
end
|
|
end
|
|
|
|
function QBCore.Player.CheckPlayerData(source, PlayerData)
|
|
PlayerData = PlayerData or {}
|
|
local Offline = not source
|
|
|
|
if source then
|
|
PlayerData.source = source
|
|
PlayerData.license = PlayerData.license or QBCore.Functions.GetIdentifier(source, 'license')
|
|
PlayerData.name = GetPlayerName(source)
|
|
end
|
|
|
|
local validatedJob = false
|
|
if PlayerData.job and PlayerData.job.name ~= nil and PlayerData.job.grade and PlayerData.job.grade.level ~= nil then
|
|
local jobInfo = QBCore.Shared.Jobs[PlayerData.job.name]
|
|
|
|
if jobInfo then
|
|
local jobGradeInfo = jobInfo.grades[tostring(PlayerData.job.grade.level)]
|
|
if jobGradeInfo then
|
|
PlayerData.job.label = jobInfo.label
|
|
PlayerData.job.grade.name = jobGradeInfo.name
|
|
PlayerData.job.payment = jobGradeInfo.payment
|
|
PlayerData.job.grade.isboss = jobGradeInfo.isboss or false
|
|
PlayerData.job.isboss = jobGradeInfo.isboss or false
|
|
validatedJob = true
|
|
end
|
|
end
|
|
end
|
|
|
|
if validatedJob == false then
|
|
-- set to nil, as the default job (unemployed) will be added by `applyDefaults`
|
|
PlayerData.job = nil
|
|
end
|
|
|
|
local validatedGang = false
|
|
if PlayerData.gang and PlayerData.gang.name ~= nil and PlayerData.gang.grade and PlayerData.gang.grade.level ~= nil then
|
|
local gangInfo = QBCore.Shared.Gangs[PlayerData.gang.name]
|
|
|
|
if gangInfo then
|
|
local gangGradeInfo = gangInfo.grades[tostring(PlayerData.gang.grade.level)]
|
|
if gangGradeInfo then
|
|
PlayerData.gang.label = gangInfo.label
|
|
PlayerData.gang.grade.name = gangGradeInfo.name
|
|
PlayerData.gang.payment = gangGradeInfo.payment
|
|
PlayerData.gang.grade.isboss = gangGradeInfo.isboss or false
|
|
PlayerData.gang.isboss = gangGradeInfo.isboss or false
|
|
validatedGang = true
|
|
end
|
|
end
|
|
end
|
|
|
|
if validatedGang == false then
|
|
-- set to nil, as the default gang (unemployed) will be added by `applyDefaults`
|
|
PlayerData.gang = nil
|
|
end
|
|
|
|
applyDefaults(PlayerData, QBCore.Config.Player.PlayerDefaults)
|
|
if PlayerData.job and QBCore.Shared.ForceJobDefaultDutyAtLogin then
|
|
local jobInfo = QBCore.Shared.Jobs[PlayerData.job.name]
|
|
if jobInfo then
|
|
PlayerData.job.onduty = jobInfo.defaultDuty
|
|
end
|
|
end
|
|
|
|
if GetResourceState('qb-inventory') ~= 'missing' then
|
|
PlayerData.items = exports['qb-inventory']:LoadInventory(PlayerData.source, PlayerData.citizenid)
|
|
end
|
|
|
|
return QBCore.Player.CreatePlayer(PlayerData, Offline)
|
|
end
|
|
|
|
-- On player logout
|
|
|
|
function QBCore.Player.Logout(source)
|
|
TriggerClientEvent('QBCore:Client:OnPlayerUnload', source)
|
|
TriggerEvent('QBCore:Server:OnPlayerUnload', source)
|
|
TriggerClientEvent('QBCore:Player:UpdatePlayerData', source)
|
|
Wait(200)
|
|
QBCore.Players[source] = nil
|
|
end
|
|
|
|
-- Create a new character
|
|
-- Don't touch any of this unless you know what you are doing
|
|
-- Will cause major issues!
|
|
|
|
function QBCore.Player.CreatePlayer(PlayerData, Offline)
|
|
local self = {}
|
|
self.Functions = {}
|
|
self.PlayerData = PlayerData
|
|
self.Offline = Offline
|
|
|
|
function self.Functions.UpdatePlayerData(key, val)
|
|
if self.Offline then return end
|
|
TriggerEvent('QBCore:Player:SetPlayerData', self.PlayerData)
|
|
if key and val then
|
|
TriggerClientEvent('QBCore:Player:UpdatePlayerDataField', self.PlayerData.source, key, val)
|
|
else
|
|
TriggerClientEvent('QBCore:Player:SetPlayerData', self.PlayerData.source, self.PlayerData)
|
|
end
|
|
end
|
|
|
|
function self.Functions.SetJob(job, grade)
|
|
job = job:lower()
|
|
grade = grade or '0'
|
|
if not QBCore.Shared.Jobs[job] then return false end
|
|
self.PlayerData.job = {
|
|
name = job,
|
|
label = QBCore.Shared.Jobs[job].label,
|
|
onduty = QBCore.Shared.Jobs[job].defaultDuty,
|
|
type = QBCore.Shared.Jobs[job].type or 'none',
|
|
grade = {
|
|
name = 'No Grades',
|
|
level = 0,
|
|
payment = 30,
|
|
isboss = false
|
|
}
|
|
}
|
|
local gradeKey = tostring(grade)
|
|
local jobGradeInfo = QBCore.Shared.Jobs[job].grades[gradeKey]
|
|
if jobGradeInfo then
|
|
self.PlayerData.job.grade.name = jobGradeInfo.name
|
|
self.PlayerData.job.grade.level = tonumber(gradeKey)
|
|
self.PlayerData.job.grade.payment = jobGradeInfo.payment
|
|
self.PlayerData.job.grade.isboss = jobGradeInfo.isboss or false
|
|
self.PlayerData.job.isboss = jobGradeInfo.isboss or false
|
|
end
|
|
|
|
if not self.Offline then
|
|
self.Functions.UpdatePlayerData('job', self.PlayerData.job)
|
|
TriggerEvent('QBCore:Server:OnJobUpdate', self.PlayerData.source, self.PlayerData.job)
|
|
TriggerClientEvent('QBCore:Client:OnJobUpdate', self.PlayerData.source, self.PlayerData.job)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function self.Functions.SetGang(gang, grade)
|
|
gang = gang:lower()
|
|
grade = grade or '0'
|
|
if not QBCore.Shared.Gangs[gang] then return false end
|
|
self.PlayerData.gang = {
|
|
name = gang,
|
|
label = QBCore.Shared.Gangs[gang].label,
|
|
grade = {
|
|
name = 'No Grades',
|
|
level = 0,
|
|
isboss = false
|
|
}
|
|
}
|
|
local gradeKey = tostring(grade)
|
|
local gangGradeInfo = QBCore.Shared.Gangs[gang].grades[gradeKey]
|
|
if gangGradeInfo then
|
|
self.PlayerData.gang.grade.name = gangGradeInfo.name
|
|
self.PlayerData.gang.grade.level = tonumber(gradeKey)
|
|
self.PlayerData.gang.grade.isboss = gangGradeInfo.isboss or false
|
|
self.PlayerData.gang.isboss = gangGradeInfo.isboss or false
|
|
end
|
|
|
|
if not self.Offline then
|
|
self.Functions.UpdatePlayerData('gang', self.PlayerData.gang)
|
|
TriggerEvent('QBCore:Server:OnGangUpdate', self.PlayerData.source, self.PlayerData.gang)
|
|
TriggerClientEvent('QBCore:Client:OnGangUpdate', self.PlayerData.source, self.PlayerData.gang)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function self.Functions.Notify(text, type, length)
|
|
TriggerClientEvent('QBCore:Notify', self.PlayerData.source, text, type, length)
|
|
end
|
|
|
|
function self.Functions.HasItem(items, amount)
|
|
return QBCore.Functions.HasItem(self.PlayerData.source, items, amount)
|
|
end
|
|
|
|
function self.Functions.GetName()
|
|
local charinfo = self.PlayerData.charinfo
|
|
return charinfo.firstname .. ' ' .. charinfo.lastname
|
|
end
|
|
|
|
function self.Functions.SetJobDuty(onDuty)
|
|
self.PlayerData.job.onduty = not not onDuty
|
|
TriggerEvent('QBCore:Server:OnJobUpdate', self.PlayerData.source, self.PlayerData.job)
|
|
TriggerClientEvent('QBCore:Client:OnJobUpdate', self.PlayerData.source, self.PlayerData.job)
|
|
self.Functions.UpdatePlayerData('job', self.PlayerData.job)
|
|
end
|
|
|
|
function self.Functions.SetPlayerData(key, val)
|
|
if not key or type(key) ~= 'string' then return end
|
|
self.PlayerData[key] = val
|
|
self.Functions.UpdatePlayerData(key, val)
|
|
end
|
|
|
|
function self.Functions.SetMetaData(meta, val)
|
|
if not meta or type(meta) ~= 'string' then return end
|
|
if meta == 'hunger' or meta == 'thirst' then
|
|
val = val > 100 and 100 or val
|
|
end
|
|
self.PlayerData.metadata[meta] = val
|
|
self.Functions.UpdatePlayerData('metadata', self.PlayerData.metadata)
|
|
end
|
|
|
|
function self.Functions.GetMetaData(meta)
|
|
if not meta or type(meta) ~= 'string' then return end
|
|
return self.PlayerData.metadata[meta]
|
|
end
|
|
|
|
function self.Functions.AddRep(rep, amount)
|
|
if not rep or not amount then return end
|
|
local addAmount = tonumber(amount)
|
|
local currentRep = self.PlayerData.metadata['rep'][rep] or 0
|
|
self.PlayerData.metadata['rep'][rep] = currentRep + addAmount
|
|
self.Functions.UpdatePlayerData('metadata', self.PlayerData.metadata)
|
|
end
|
|
|
|
function self.Functions.RemoveRep(rep, amount)
|
|
if not rep or not amount then return end
|
|
local removeAmount = tonumber(amount)
|
|
local currentRep = self.PlayerData.metadata['rep'][rep] or 0
|
|
if currentRep - removeAmount < 0 then
|
|
self.PlayerData.metadata['rep'][rep] = 0
|
|
else
|
|
self.PlayerData.metadata['rep'][rep] = currentRep - removeAmount
|
|
end
|
|
self.Functions.UpdatePlayerData('metadata', self.PlayerData.metadata)
|
|
end
|
|
|
|
function self.Functions.GetRep(rep)
|
|
if not rep then return end
|
|
return self.PlayerData.metadata['rep'][rep] or 0
|
|
end
|
|
|
|
function self.Functions.AddMoney(moneytype, amount, reason)
|
|
reason = reason or 'unknown'
|
|
moneytype = moneytype:lower()
|
|
amount = tonumber(amount)
|
|
if amount < 0 then return end
|
|
if not self.PlayerData.money[moneytype] then return false end
|
|
self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] + amount
|
|
|
|
if not self.Offline then
|
|
self.Functions.UpdatePlayerData('money', self.PlayerData.money)
|
|
if amount > 100000 then
|
|
TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'AddMoney', 'lightgreen', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') added, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason, true)
|
|
else
|
|
TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'AddMoney', 'lightgreen', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') added, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason)
|
|
end
|
|
TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, amount, false)
|
|
TriggerClientEvent('QBCore:Client:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'add', reason)
|
|
TriggerEvent('QBCore:Server:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'add', reason)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function self.Functions.RemoveMoney(moneytype, amount, reason)
|
|
reason = reason or 'unknown'
|
|
moneytype = moneytype:lower()
|
|
amount = tonumber(amount)
|
|
if amount < 0 then return end
|
|
if not self.PlayerData.money[moneytype] then return false end
|
|
for _, mtype in pairs(QBCore.Config.Money.DontAllowMinus) do
|
|
if mtype == moneytype then
|
|
if (self.PlayerData.money[moneytype] - amount) < 0 then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
if self.PlayerData.money[moneytype] - amount < QBCore.Config.Money.MinusLimit then return false end
|
|
self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] - amount
|
|
|
|
if not self.Offline then
|
|
self.Functions.UpdatePlayerData('money', self.PlayerData.money)
|
|
if amount > 100000 then
|
|
TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'RemoveMoney', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') removed, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason, true)
|
|
else
|
|
TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'RemoveMoney', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') removed, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason)
|
|
end
|
|
TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, amount, true)
|
|
if moneytype == 'bank' then
|
|
TriggerClientEvent('qb-phone:client:RemoveBankMoney', self.PlayerData.source, amount)
|
|
end
|
|
TriggerClientEvent('QBCore:Client:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'remove', reason)
|
|
TriggerEvent('QBCore:Server:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'remove', reason)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function self.Functions.SetMoney(moneytype, amount, reason)
|
|
reason = reason or 'unknown'
|
|
moneytype = moneytype:lower()
|
|
amount = tonumber(amount)
|
|
if amount < 0 then return false end
|
|
if not self.PlayerData.money[moneytype] then return false end
|
|
local difference = amount - self.PlayerData.money[moneytype]
|
|
self.PlayerData.money[moneytype] = amount
|
|
|
|
if not self.Offline then
|
|
self.Functions.UpdatePlayerData('money', self.PlayerData.money)
|
|
TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'SetMoney', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') set, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason)
|
|
TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, math.abs(difference), difference < 0)
|
|
TriggerClientEvent('QBCore:Client:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'set', reason)
|
|
TriggerEvent('QBCore:Server:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'set', reason)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function self.Functions.GetMoney(moneytype)
|
|
if not moneytype then return false end
|
|
moneytype = moneytype:lower()
|
|
return self.PlayerData.money[moneytype]
|
|
end
|
|
|
|
function self.Functions.Save()
|
|
if self.Offline then
|
|
QBCore.Player.SaveOffline(self.PlayerData)
|
|
else
|
|
QBCore.Player.Save(self.PlayerData.source)
|
|
end
|
|
end
|
|
|
|
function self.Functions.Logout()
|
|
if self.Offline then return end
|
|
QBCore.Player.Logout(self.PlayerData.source)
|
|
end
|
|
|
|
function self.Functions.AddMethod(methodName, handler)
|
|
self.Functions[methodName] = handler
|
|
end
|
|
|
|
function self.Functions.AddField(fieldName, data)
|
|
self[fieldName] = data
|
|
end
|
|
|
|
if self.Offline then
|
|
return self
|
|
else
|
|
QBCore.Players[self.PlayerData.source] = self
|
|
QBCore.Player.Save(self.PlayerData.source)
|
|
TriggerEvent('QBCore:Server:PlayerLoaded', self)
|
|
self.Functions.UpdatePlayerData()
|
|
end
|
|
end
|
|
|
|
-- Add a new function to the Functions table of the player class
|
|
-- Use-case:
|
|
--[[
|
|
AddEventHandler('QBCore:Server:PlayerLoaded', function(Player)
|
|
QBCore.Functions.AddPlayerMethod(Player.PlayerData.source, "functionName", function(oneArg, orMore)
|
|
-- do something here
|
|
end)
|
|
end)
|
|
]]
|
|
|
|
function QBCore.Functions.AddPlayerMethod(ids, methodName, handler)
|
|
local idType = type(ids)
|
|
if idType == 'number' then
|
|
if ids == -1 then
|
|
for _, v in pairs(QBCore.Players) do
|
|
v.Functions.AddMethod(methodName, handler)
|
|
end
|
|
else
|
|
if not QBCore.Players[ids] then return end
|
|
|
|
QBCore.Players[ids].Functions.AddMethod(methodName, handler)
|
|
end
|
|
elseif idType == 'table' and table.type(ids) == 'array' then
|
|
for i = 1, #ids do
|
|
QBCore.Functions.AddPlayerMethod(ids[i], methodName, handler)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Add a new field table of the player class
|
|
-- Use-case:
|
|
--[[
|
|
AddEventHandler('QBCore:Server:PlayerLoaded', function(Player)
|
|
QBCore.Functions.AddPlayerField(Player.PlayerData.source, "fieldName", "fieldData")
|
|
end)
|
|
]]
|
|
|
|
function QBCore.Functions.AddPlayerField(ids, fieldName, data)
|
|
local idType = type(ids)
|
|
if idType == 'number' then
|
|
if ids == -1 then
|
|
for _, v in pairs(QBCore.Players) do
|
|
v.Functions.AddField(fieldName, data)
|
|
end
|
|
else
|
|
if not QBCore.Players[ids] then return end
|
|
|
|
QBCore.Players[ids].Functions.AddField(fieldName, data)
|
|
end
|
|
elseif idType == 'table' and table.type(ids) == 'array' then
|
|
for i = 1, #ids do
|
|
QBCore.Functions.AddPlayerField(ids[i], fieldName, data)
|
|
end
|
|
end
|
|
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', {
|
|
citizenid = PlayerData.citizenid,
|
|
cid = tonumber(PlayerData.cid),
|
|
license = PlayerData.license,
|
|
name = PlayerData.name,
|
|
money = json.encode(PlayerData.money),
|
|
charinfo = json.encode(PlayerData.charinfo),
|
|
job = json.encode(PlayerData.job),
|
|
gang = json.encode(PlayerData.gang),
|
|
position = json.encode(pcoords),
|
|
metadata = json.encode(PlayerData.metadata)
|
|
})
|
|
if GetResourceState('qb-inventory') ~= 'missing' then exports['qb-inventory']:SaveInventory(source) end
|
|
QBCore.ShowSuccess(resourceName, PlayerData.name .. ' PLAYER SAVED!')
|
|
else
|
|
QBCore.ShowError(resourceName, 'ERROR QBCORE.PLAYER.SAVE - PLAYERDATA IS EMPTY!')
|
|
end
|
|
end
|
|
|
|
function QBCore.Player.SaveOffline(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', {
|
|
citizenid = PlayerData.citizenid,
|
|
cid = tonumber(PlayerData.cid),
|
|
license = PlayerData.license,
|
|
name = PlayerData.name,
|
|
money = json.encode(PlayerData.money),
|
|
charinfo = json.encode(PlayerData.charinfo),
|
|
job = json.encode(PlayerData.job),
|
|
gang = json.encode(PlayerData.gang),
|
|
position = json.encode(PlayerData.position),
|
|
metadata = json.encode(PlayerData.metadata)
|
|
})
|
|
if GetResourceState('qb-inventory') ~= 'missing' then exports['qb-inventory']:SaveInventory(PlayerData, true) end
|
|
QBCore.ShowSuccess(resourceName, PlayerData.name .. ' OFFLINE PLAYER SAVED!')
|
|
else
|
|
QBCore.ShowError(resourceName, 'ERROR QBCORE.PLAYER.SAVEOFFLINE - PLAYERDATA IS EMPTY!')
|
|
end
|
|
end
|
|
|
|
-- Delete character
|
|
|
|
local playertables = { -- Add tables as needed
|
|
{ table = 'players' },
|
|
{ table = 'apartments' },
|
|
{ table = 'bank_accounts' },
|
|
{ table = 'crypto_transactions' },
|
|
{ table = 'phone_invoices' },
|
|
{ table = 'phone_messages' },
|
|
{ table = 'playerskins' },
|
|
{ table = 'player_contacts' },
|
|
{ table = 'player_houses' },
|
|
{ table = 'player_mails' },
|
|
{ table = 'player_outfits' },
|
|
{ table = 'player_vehicles' }
|
|
}
|
|
|
|
function QBCore.Player.DeleteCharacter(source, citizenid)
|
|
local license = QBCore.Functions.GetIdentifier(source, 'license')
|
|
local result = MySQL.scalar.await('SELECT license FROM players where citizenid = ?', { citizenid })
|
|
if license == result then
|
|
local query = 'DELETE FROM %s WHERE citizenid = ?'
|
|
local tableCount = #playertables
|
|
local queries = table.create(tableCount, 0)
|
|
|
|
for i = 1, tableCount do
|
|
local v = playertables[i]
|
|
queries[i] = { query = query:format(v.table), values = { citizenid } }
|
|
end
|
|
|
|
MySQL.transaction(queries, function(result2)
|
|
if result2 then
|
|
TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Character Deleted', 'red', '**' .. GetPlayerName(source) .. '** ' .. license .. ' deleted **' .. citizenid .. '**..')
|
|
end
|
|
end)
|
|
else
|
|
DropPlayer(source, Lang:t('info.exploit_dropped'))
|
|
TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(source) .. ' Has Been Dropped For Character Deletion Exploit', true)
|
|
end
|
|
end
|
|
|
|
function QBCore.Player.ForceDeleteCharacter(citizenid)
|
|
local result = MySQL.scalar.await('SELECT license FROM players where citizenid = ?', { citizenid })
|
|
if result then
|
|
local query = 'DELETE FROM %s WHERE citizenid = ?'
|
|
local tableCount = #playertables
|
|
local queries = table.create(tableCount, 0)
|
|
local Player = QBCore.Functions.GetPlayerByCitizenId(citizenid)
|
|
|
|
if Player then
|
|
DropPlayer(Player.PlayerData.source, 'An admin deleted the character which you are currently using')
|
|
end
|
|
for i = 1, tableCount do
|
|
local v = playertables[i]
|
|
queries[i] = { query = query:format(v.table), values = { citizenid } }
|
|
end
|
|
|
|
MySQL.transaction(queries, function(result2)
|
|
if result2 then
|
|
TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Character Force Deleted', 'red', 'Character **' .. citizenid .. '** got deleted')
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- Inventory Backwards Compatibility
|
|
|
|
function QBCore.Player.SaveInventory(source)
|
|
if GetResourceState('qb-inventory') == 'missing' then return end
|
|
exports['qb-inventory']:SaveInventory(source, false)
|
|
end
|
|
|
|
function QBCore.Player.SaveOfflineInventory(PlayerData)
|
|
if GetResourceState('qb-inventory') == 'missing' then return end
|
|
exports['qb-inventory']:SaveInventory(PlayerData, true)
|
|
end
|
|
|
|
function QBCore.Player.GetTotalWeight(items)
|
|
if GetResourceState('qb-inventory') == 'missing' then return end
|
|
return exports['qb-inventory']:GetTotalWeight(items)
|
|
end
|
|
|
|
function QBCore.Player.GetSlotsByItem(items, itemName)
|
|
if GetResourceState('qb-inventory') == 'missing' then return end
|
|
return exports['qb-inventory']:GetSlotsByItem(items, itemName)
|
|
end
|
|
|
|
function QBCore.Player.GetFirstSlotByItem(items, itemName)
|
|
if GetResourceState('qb-inventory') == 'missing' then return end
|
|
return exports['qb-inventory']:GetFirstSlotByItem(items, itemName)
|
|
end
|
|
|
|
-- Util Functions
|
|
|
|
function QBCore.Player.CreateCitizenId()
|
|
local CitizenId = tostring(QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(5)):upper()
|
|
local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE citizenid = ?) AS uniqueCheck', { CitizenId })
|
|
if result == 0 then return CitizenId end
|
|
return QBCore.Player.CreateCitizenId()
|
|
end
|
|
|
|
function QBCore.Functions.CreateAccountNumber()
|
|
local AccountNumber = 'US0' .. math.random(1, 9) .. 'QBCore' .. math.random(1111, 9999) .. math.random(1111, 9999) .. math.random(11, 99)
|
|
local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.account")) = ?) AS uniqueCheck', { AccountNumber })
|
|
if result == 0 then return AccountNumber end
|
|
return QBCore.Functions.CreateAccountNumber()
|
|
end
|
|
|
|
function QBCore.Functions.CreatePhoneNumber()
|
|
local PhoneNumber = math.random(100, 999) .. math.random(1000000, 9999999)
|
|
local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.phone")) = ?) AS uniqueCheck', { PhoneNumber })
|
|
if result == 0 then return PhoneNumber end
|
|
return QBCore.Functions.CreatePhoneNumber()
|
|
end
|
|
|
|
function QBCore.Player.CreateFingerId()
|
|
local FingerId = tostring(QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(1) .. QBCore.Shared.RandomInt(2) .. QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(4))
|
|
local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(metadata, "$.fingerprint")) = ?) AS uniqueCheck', { FingerId })
|
|
if result == 0 then return FingerId end
|
|
return QBCore.Player.CreateFingerId()
|
|
end
|
|
|
|
function QBCore.Player.CreateWalletId()
|
|
local WalletId = 'QB-' .. math.random(11111111, 99999999)
|
|
local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(metadata, "$.walletid")) = ?) AS uniqueCheck', { WalletId })
|
|
if result == 0 then return WalletId end
|
|
return QBCore.Player.CreateWalletId()
|
|
end
|
|
|
|
function QBCore.Player.CreateSerialNumber()
|
|
local SerialNumber = math.random(11111111, 99999999)
|
|
local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(metadata, "$.phonedata.SerialNumber")) = ?) AS uniqueCheck', { SerialNumber })
|
|
if result == 0 then return SerialNumber end
|
|
return QBCore.Player.CreateSerialNumber()
|
|
end
|
|
|
|
PaycheckInterval() -- This starts the paycheck system
|