675 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			675 lines
		
	
	
	
		
			28 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 GetResourceState('qs-inventory') ~= 'missing' then
 | 
						|
        PlayerData.items = exports['qs-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()
 | 
						|
        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 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('qs-inventory') ~= 'missing' then exports['qs-inventory']:SaveInventory(source) end
 | 
						|
        QBCore.ShowSuccess(resourceName, PlayerData.name .. ' PLAYER SAVED!')
 | 
						|
    else
 | 
						|
        QBCore.ShowError(resourceName, 'ERROR QBCORE.PLAYER.SAVE - PLAYERDATA IS EMPTY!')
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
AddEventHandler('onResourceStop', function(resourceName)
 | 
						|
    if (resourceName == 'qs-inventory') then
 | 
						|
        local players = QBCore.Functions.GetPlayers()
 | 
						|
        for _, player in ipairs(players) do
 | 
						|
            QBCore.Player.Save(player)
 | 
						|
        end
 | 
						|
    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('qs-inventory') ~= 'missing' then exports['qs-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('qs-inventory') == 'missing' then return end
 | 
						|
    exports['qs-inventory']:SaveInventory(source, false)
 | 
						|
end
 | 
						|
 | 
						|
function QBCore.Player.SaveOfflineInventory(PlayerData)
 | 
						|
    if GetResourceState('qs-inventory') == 'missing' then return end
 | 
						|
    exports['qs-inventory']:SaveInventory(PlayerData, true)
 | 
						|
end
 | 
						|
 | 
						|
function QBCore.Player.GetTotalWeight(items)
 | 
						|
    if GetResourceState('qs-inventory') == 'missing' then return end
 | 
						|
    return exports['qs-inventory']:GetTotalWeight(items)
 | 
						|
end
 | 
						|
 | 
						|
function QBCore.Player.GetSlotsByItem(items, itemName)
 | 
						|
    if GetResourceState('qs-inventory') == 'missing' then return end
 | 
						|
    return exports['qs-inventory']:GetSlotsByItem(items, itemName)
 | 
						|
end
 | 
						|
 | 
						|
function QBCore.Player.GetFirstSlotByItem(items, itemName)
 | 
						|
    if GetResourceState('qs-inventory') == 'missing' then return end
 | 
						|
    return exports['qs-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
 |