mirror of
https://github.com/qbcore-fivem/qb-core.git
synced 2026-08-28 23:01:29 +00:00
wip player creation with state and backward compat
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
-- Backwards Compat
|
||||
|
||||
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
|
||||
|
||||
function QBCore.Player.Login(source, citizenid, newData)
|
||||
if not source or source == '' then
|
||||
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)
|
||||
end
|
||||
|
||||
function self.Functions.AddMethod(methodName, handler)
|
||||
self.Functions[methodName] = handler
|
||||
end
|
||||
|
||||
function self.Functions.AddField(fieldName, data)
|
||||
self[fieldName] = data
|
||||
local state = Player(PlayerData.source).state
|
||||
state:set(fieldName, data, true)
|
||||
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
|
||||
}
|
||||
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)
|
||||
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
|
||||
|
||||
InitializePlayerStateBag(source, PlayerData)
|
||||
|
||||
return QBCore.Player.CreatePlayer(PlayerData, Offline)
|
||||
end
|
||||
|
||||
local function BuildPlayerSaveData(source)
|
||||
local state = Player(source).state
|
||||
|
||||
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:'))
|
||||
}
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
Reference in New Issue
Block a user