mirror of
https://github.com/qbcore-fivem/qb-core.git
synced 2026-08-31 10:18:58 +00:00
reset branch
This commit is contained in:
+629
-306
@@ -1,297 +1,93 @@
|
||||
-- Backwards Compat
|
||||
QBCore.Players = {}
|
||||
QBCore.Player = {}
|
||||
|
||||
local function extractData(source, prefix)
|
||||
local state = Player(source).state
|
||||
local data = {}
|
||||
|
||||
for key, value in pairs(state) do
|
||||
if key:find('^' .. prefix) then
|
||||
local shortKey = key:gsub(prefix, '')
|
||||
data[shortKey] = value or 'Unknown'
|
||||
end
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
local function CreateLegacyPlayerData(player)
|
||||
local source = player.source
|
||||
local state = Player(source).state
|
||||
local legacy = {}
|
||||
legacy.license = QBCore.Functions.GetIdentifier(source, 'license')
|
||||
legacy.source = source
|
||||
legacy.name = GetPlayerName(source)
|
||||
legacy.citizenid = state['citizenid']
|
||||
legacy.money = extractData(source, 'money:')
|
||||
legacy.job = extractData(source, 'job:')
|
||||
legacy.gang = extractData(source, 'gang:')
|
||||
legacy.metadata = extractData(source, 'metadata:')
|
||||
legacy.charinfo = extractData(source, 'charinfo:')
|
||||
legacy.position = extractData(source, 'position:')
|
||||
|
||||
setmetatable(legacy, {
|
||||
__index = function(tbl, key)
|
||||
if key == 'money' then
|
||||
return extractData(source, 'money:')
|
||||
elseif key == 'job' then
|
||||
return extractData(source, 'job:')
|
||||
elseif key == 'gang' then
|
||||
return extractData(source, 'gang:')
|
||||
elseif key == 'metadata' then
|
||||
return extractData(source, 'metadata:')
|
||||
elseif key == 'charinfo' then
|
||||
return extractData(source, 'charinfo:')
|
||||
elseif key == 'position' then
|
||||
return extractData(source, 'position:')
|
||||
else
|
||||
return rawget(tbl, key)
|
||||
end
|
||||
end,
|
||||
__newindex = function(tbl, key, value)
|
||||
local state = Player(source).state
|
||||
if key == 'money' and type(value) == 'table' then
|
||||
for subkey, subvalue in pairs(value) do
|
||||
state:set('money:' .. subkey, subvalue, true)
|
||||
end
|
||||
elseif key == 'job' and type(value) == 'table' then
|
||||
for subkey, subvalue in pairs(value) do
|
||||
state:set('job:' .. subkey, subvalue, true)
|
||||
end
|
||||
elseif key == 'gang' and type(value) == 'table' then
|
||||
for subkey, subvalue in pairs(value) do
|
||||
state:set('gang:' .. subkey, subvalue, true)
|
||||
end
|
||||
elseif key == 'metadata' and type(value) == 'table' then
|
||||
for subkey, subvalue in pairs(value) do
|
||||
state:set('metadata:' .. subkey, subvalue, true)
|
||||
end
|
||||
elseif key == 'charinfo' and type(value) == 'table' then
|
||||
for subkey, subvalue in pairs(value) do
|
||||
state:set('charinfo:' .. subkey, subvalue, true)
|
||||
end
|
||||
elseif key == 'position' and type(value) == 'table' then
|
||||
for subkey, subvalue in pairs(value) do
|
||||
state:set('position:' .. subkey, subvalue, true)
|
||||
end
|
||||
else
|
||||
rawset(tbl, key, value)
|
||||
end
|
||||
end,
|
||||
})
|
||||
return legacy
|
||||
end
|
||||
|
||||
-- JSON STUFF
|
||||
|
||||
local DynamicDefaults = {
|
||||
['citizenid'] = QBCore.Functions.CreateCitizenId,
|
||||
['charinfo.phone'] = QBCore.Functions.CreatePhoneNumber,
|
||||
['charinfo.account'] = QBCore.Functions.CreateAccountNumber,
|
||||
['metadata.bloodtype'] = function() return QBCore.Functions.GetRandomElement(QBCore.Config.Player.Bloodtypes) end,
|
||||
['metadata.fingerprint'] = QBCore.Functions.CreateFingerId,
|
||||
['metadata.walletid'] = QBCore.Functions.CreateWalletId
|
||||
}
|
||||
|
||||
local function ApplyDynamicDefaults(target)
|
||||
for field, func in pairs(DynamicDefaults) do
|
||||
local ref = target
|
||||
local keys = {}
|
||||
for key in field:gmatch('[^.]+') do table.insert(keys, key) end
|
||||
|
||||
for i = 1, #keys - 1 do
|
||||
ref[keys[i]] = ref[keys[i]] or {}
|
||||
ref = ref[keys[i]]
|
||||
end
|
||||
|
||||
ref[keys[#keys]] = ref[keys[#keys]] or func()
|
||||
end
|
||||
end
|
||||
|
||||
local function LoadPlayerDefaults()
|
||||
local success, defaults = pcall(function()
|
||||
return json.decode(LoadResourceFile(resourceName, 'shared/player_defaults.json'))
|
||||
end)
|
||||
|
||||
if not success or not defaults or not next(defaults) then
|
||||
print('^1[ERROR]^0 Could not load player_defaults.json. Ensure the file is valid JSON and not empty.')
|
||||
return {}
|
||||
end
|
||||
|
||||
ApplyDynamicDefaults(defaults)
|
||||
return defaults
|
||||
end
|
||||
|
||||
local function MergePlayerData(target, defaults, debug)
|
||||
for key, value in pairs(defaults) do
|
||||
if type(value) == 'table' then
|
||||
target[key] = target[key] or {}
|
||||
MergePlayerData(target[key], value, debug)
|
||||
else
|
||||
if not debug then
|
||||
if target[key] == nil then
|
||||
print(('^2[INFO]^0 Added new data field: %s = %s'):format(key, tostring(value)))
|
||||
elseif target[key] ~= value then
|
||||
print(('^3[INFO]^0 Updated data field: %s = %s'):format(key, tostring(value)))
|
||||
end
|
||||
end
|
||||
target[key] = (target[key] == nil or target[key] == '') and value or target[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Player Creation
|
||||
|
||||
local function InitializePlayerStateBag(source, playerData)
|
||||
local state = Player(source).state
|
||||
local defaults = LoadPlayerDefaults()
|
||||
|
||||
for _, key in pairs(GetStateBagKeys('player:' .. source)) do
|
||||
if not defaults[key] then
|
||||
print(('^1[INFO]^0 Removed outdated data field: %s'):format(key))
|
||||
end
|
||||
state:set(key, nil, true)
|
||||
end
|
||||
|
||||
MergePlayerData(playerData, defaults)
|
||||
|
||||
local function populatePlayerState(data, prefix)
|
||||
for key, value in pairs(data) do
|
||||
if type(value) == 'table' then
|
||||
populatePlayerState(value, prefix .. key .. ':')
|
||||
else
|
||||
state:set(prefix .. key, value, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
populatePlayerState(playerData, '')
|
||||
end
|
||||
-- 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 not source or source == '' then
|
||||
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
|
||||
|
||||
if citizenid then
|
||||
local license = QBCore.Functions.GetIdentifier(source, 'license')
|
||||
local data = MySQL.prepare.await('SELECT * FROM players WHERE citizenid = ?', { citizenid })
|
||||
|
||||
if not data then
|
||||
print('^1[ERROR]^0 Failed to load data for Citizen ID:', citizenid)
|
||||
DropPlayer(source, 'Failed to load your character data. Please contact staff.')
|
||||
return false
|
||||
end
|
||||
|
||||
if data and license == data.license then
|
||||
local success, playerData = pcall(function()
|
||||
return {
|
||||
money = json.decode(data.money) or {},
|
||||
job = json.decode(data.job) or {},
|
||||
gang = json.decode(data.gang) or {},
|
||||
position = json.decode(data.position) or {},
|
||||
metadata = json.decode(data.metadata) or {},
|
||||
charinfo = json.decode(data.charinfo) or {}
|
||||
}
|
||||
end)
|
||||
|
||||
if not success then
|
||||
print('^1[ERROR]^0 Failed to decode player data for Citizen ID:', citizenid)
|
||||
DropPlayer(source, 'Failed to decode your character data. Please contact staff.')
|
||||
return false
|
||||
end
|
||||
|
||||
InitializePlayerStateBag(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
|
||||
InitializePlayerStateBag(source, newData or {})
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function QBCore.Player.CreatePlayer(PlayerData, Offline)
|
||||
local self = {}
|
||||
self.Offline = Offline
|
||||
self.Functions = {}
|
||||
|
||||
function self.Functions.Notify(text, type, length)
|
||||
TriggerClientEvent('QBCore:Notify', PlayerData.source, text, type, length)
|
||||
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 self.Functions.AddMethod(methodName, handler)
|
||||
self.Functions[methodName] = handler
|
||||
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 self.Functions.AddField(fieldName, data)
|
||||
self[fieldName] = data
|
||||
local state = Player(PlayerData.source).state
|
||||
state:set(fieldName, data, true)
|
||||
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 CreateDynamicGetSet(prefix)
|
||||
return {
|
||||
Get = function(key)
|
||||
local state = Player(PlayerData.source).state
|
||||
return state[prefix .. key] or nil
|
||||
end,
|
||||
|
||||
Set = function(key, value)
|
||||
local state = Player(PlayerData.source).state
|
||||
state:set(prefix .. key, value, true)
|
||||
return true
|
||||
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
|
||||
|
||||
local function CreateDynamicIncremental(prefix)
|
||||
return {
|
||||
Get = function(key)
|
||||
local state = Player(PlayerData.source).state
|
||||
return state[prefix .. key] or 0
|
||||
end,
|
||||
|
||||
Set = function(key, value)
|
||||
local state = Player(PlayerData.source).state
|
||||
state:set(prefix .. key, value, true)
|
||||
return true
|
||||
end,
|
||||
|
||||
Add = function(key, value)
|
||||
local state = Player(PlayerData.source).state
|
||||
local currentValue = state[prefix .. key] or 0
|
||||
state:set(prefix .. key, currentValue + value, true)
|
||||
return true
|
||||
end,
|
||||
|
||||
Remove = function(key, value)
|
||||
local state = Player(PlayerData.source).state
|
||||
local currentValue = state[prefix .. key] or 0
|
||||
if currentValue < value then return false end
|
||||
state:set(prefix .. key, currentValue - value, true)
|
||||
return true
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
self.Metadata = CreateDynamicGetSet('metadata:')
|
||||
self.Job = CreateDynamicGetSet('job:')
|
||||
self.Gang = CreateDynamicGetSet('gang:')
|
||||
self.Money = CreateDynamicIncremental('money:')
|
||||
self.CharInfo = CreateDynamicGetSet('charinfo:')
|
||||
self.Position = CreateDynamicGetSet('position:')
|
||||
self.PlayerData = CreateLegacyPlayerData(PlayerData)
|
||||
|
||||
if not self.Offline then
|
||||
QBCore.Players[PlayerData.source] = self
|
||||
QBCore.Player.Save(PlayerData.source)
|
||||
TriggerEvent('QBCore:Server:PlayerLoaded', self)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function QBCore.Player.CheckPlayerData(source, PlayerData)
|
||||
@@ -304,40 +100,567 @@ function QBCore.Player.CheckPlayerData(source, PlayerData)
|
||||
PlayerData.name = GetPlayerName(source)
|
||||
end
|
||||
|
||||
InitializePlayerStateBag(source, PlayerData)
|
||||
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 GetResourceState('qb-inventory') ~= 'missing' then
|
||||
PlayerData.items = exports['qb-inventory']:LoadInventory(PlayerData.source, PlayerData.citizenid)
|
||||
end
|
||||
|
||||
return QBCore.Player.CreatePlayer(PlayerData, Offline)
|
||||
end
|
||||
|
||||
local function BuildPlayerSaveData(source)
|
||||
local state = Player(source).state
|
||||
-- On player logout
|
||||
|
||||
return {
|
||||
citizenid = state['citizenid'],
|
||||
money = json.encode(extractData(source, 'money:')),
|
||||
metadata = json.encode(extractData(source, 'metadata:')),
|
||||
position = json.encode(extractData(source, 'position:')),
|
||||
job = json.encode(extractData(source, 'job:')),
|
||||
gang = json.encode(extractData(source, 'gang:'))
|
||||
}
|
||||
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()
|
||||
if self.Offline then return end
|
||||
TriggerEvent('QBCore:Player:SetPlayerData', self.PlayerData)
|
||||
TriggerClientEvent('QBCore:Player:SetPlayerData', self.PlayerData.source, self.PlayerData)
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
end
|
||||
|
||||
function self.Functions.SetPlayerData(key, val)
|
||||
if not key or type(key) ~= 'string' then return end
|
||||
self.PlayerData[key] = val
|
||||
self.Functions.UpdatePlayerData()
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
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 saveData = BuildPlayerSaveData(source)
|
||||
|
||||
if not saveData.citizenid or not saveData.license then
|
||||
print('^1[ERROR]^0 Skipped saving data for player [%s] due to missing critical data.'):format(GetPlayerName(source))
|
||||
return
|
||||
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
|
||||
|
||||
MySQL.insert(
|
||||
'INSERT INTO players (citizenid, money, metadata, position, job, gang) VALUES (:citizenid, :money, :metadata, :position, :job, :gang) ON DUPLICATE KEY UPDATE money = :money, metadata = :metadata, position = :position, job = :job, gang = :gang',
|
||||
saveData
|
||||
)
|
||||
|
||||
if GetResourceState('qb-inventory') ~= 'missing' then
|
||||
exports['qb-inventory']:SaveInventory(source)
|
||||
end
|
||||
|
||||
QBCore.ShowSuccess(resourceName, 'Player ' .. GetPlayerName(source) .. ' data saved successfully.')
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user