diff --git a/config.lua b/config.lua index 3c773097b..bef21784f 100644 --- a/config.lua +++ b/config.lua @@ -20,6 +20,7 @@ QBConfig.Player.Bloodtypes = { } QBConfig.Player.PlayerDefaults = { + userId = function() if QBConfig.Server.StaticId.Enabled then return QBCore.Player.CreatePlayerId() end end, citizenid = function() return QBCore.Player.CreateCitizenId() end, cid = 1, money = function() @@ -115,6 +116,11 @@ QBConfig.Server.Discord = '' -- Discord invite link QBConfig.Server.CheckDuplicateLicense = true -- Check for duplicate rockstar license on join QBConfig.Server.Permissions = { 'god', 'admin', 'mod' } -- Add as many groups as you want here after creating them in your server.cfg +QBConfig.Server.StaticId = { + Enabled = true, -- Enable or disable the use of static ID's + UseInCommands = false -- Use static ID's in commands like /givecash, /setjob etc. (Requires server restart after changing) ( Applys only if QBConfig.Server.StaticId.Enabled.Enabled is true | Applys only in commands from server/commands.lua ) +} + QBConfig.Commands = {} -- Command Configuration QBConfig.Commands.OOCColor = { 255, 151, 133 } -- RGB color code for the OOC command diff --git a/server/commands.lua b/server/commands.lua index 368736fdc..9fb3aab48 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -15,6 +15,27 @@ end) -- Register & Refresh Commands +local function ResolvePlayerArg(arg) + local id = tonumber(arg) + if not id then return nil end + if QBCore.Config.Server.StaticId.Enabled and QBCore.Config.Server.StaticId.UseInCommands then + return QBCore.Functions.GetPlayerById(id) + else + return QBCore.Functions.GetPlayer(id) + end +end + +local function ResolveSourceArg(arg) + local id = tonumber(arg) + if not id then return nil end + if QBCore.Config.Server.StaticId.Enabled and QBCore.Config.Server.StaticId.UseInCommands then + local p = QBCore.Functions.GetPlayerById(id) + return p and p.PlayerData.source or nil + else + return id + end +end + function QBCore.Commands.Add(name, help, arguments, argsrequired, callback, permission, ...) local restricted = true -- Default to restricted for all commands if not permission then permission = 'user' end -- some commands don't pass permission level @@ -84,7 +105,8 @@ end QBCore.Commands.Add('tp', Lang:t('command.tp.help'), { { name = Lang:t('command.tp.params.x.name'), help = Lang:t('command.tp.params.x.help') }, { name = Lang:t('command.tp.params.y.name'), help = Lang:t('command.tp.params.y.help') }, { name = Lang:t('command.tp.params.z.name'), help = Lang:t('command.tp.params.z.help') } }, false, function(source, args) if args[1] and not args[2] and not args[3] then if tonumber(args[1]) then - local target = GetPlayerPed(tonumber(args[1])) + local tgtSrc = ResolveSourceArg(args[1]) + local target = tgtSrc and GetPlayerPed(tgtSrc) or 0 if target ~= 0 then local coords = GetEntityCoords(target) TriggerClientEvent('QBCore:Command:TeleportToPlayer', source, coords) @@ -128,7 +150,7 @@ end, 'admin') -- Permissions QBCore.Commands.Add('addpermission', Lang:t('command.addpermission.help'), { { name = Lang:t('command.addpermission.params.id.name'), help = Lang:t('command.addpermission.params.id.help') }, { name = Lang:t('command.addpermission.params.permission.name'), help = Lang:t('command.addpermission.params.permission.help') } }, true, function(source, args) - local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) + local Player = ResolvePlayerArg(args[1]) local permission = tostring(args[2]):lower() if Player then QBCore.Functions.AddPermission(Player.PlayerData.source, permission) @@ -138,7 +160,7 @@ QBCore.Commands.Add('addpermission', Lang:t('command.addpermission.help'), { { n end, 'god') QBCore.Commands.Add('removepermission', Lang:t('command.removepermission.help'), { { name = Lang:t('command.removepermission.params.id.name'), help = Lang:t('command.removepermission.params.id.help') }, { name = Lang:t('command.removepermission.params.permission.name'), help = Lang:t('command.removepermission.params.permission.help') } }, true, function(source, args) - local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) + local Player = ResolvePlayerArg(args[1]) local permission = tostring(args[2]):lower() if Player then QBCore.Functions.RemovePermission(Player.PlayerData.source, permission) @@ -220,7 +242,7 @@ end, 'admin') -- Money QBCore.Commands.Add('givemoney', Lang:t('command.givemoney.help'), { { name = Lang:t('command.givemoney.params.id.name'), help = Lang:t('command.givemoney.params.id.help') }, { name = Lang:t('command.givemoney.params.moneytype.name'), help = Lang:t('command.givemoney.params.moneytype.help') }, { name = Lang:t('command.givemoney.params.amount.name'), help = Lang:t('command.givemoney.params.amount.help') } }, true, function(source, args) - local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) + local Player = ResolvePlayerArg(args[1]) if Player then Player.Functions.AddMoney(tostring(args[2]), tonumber(args[3]), 'Admin give money') else @@ -229,7 +251,7 @@ QBCore.Commands.Add('givemoney', Lang:t('command.givemoney.help'), { { name = La end, 'admin') QBCore.Commands.Add('setmoney', Lang:t('command.setmoney.help'), { { name = Lang:t('command.setmoney.params.id.name'), help = Lang:t('command.setmoney.params.id.help') }, { name = Lang:t('command.setmoney.params.moneytype.name'), help = Lang:t('command.setmoney.params.moneytype.help') }, { name = Lang:t('command.setmoney.params.amount.name'), help = Lang:t('command.setmoney.params.amount.help') } }, true, function(source, args) - local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) + local Player = ResolvePlayerArg(args[1]) if Player then Player.Functions.SetMoney(tostring(args[2]), tonumber(args[3])) else @@ -245,7 +267,7 @@ QBCore.Commands.Add('job', Lang:t('command.job.help'), {}, false, function(sourc end, 'user') QBCore.Commands.Add('setjob', Lang:t('command.setjob.help'), { { name = Lang:t('command.setjob.params.id.name'), help = Lang:t('command.setjob.params.id.help') }, { name = Lang:t('command.setjob.params.job.name'), help = Lang:t('command.setjob.params.job.help') }, { name = Lang:t('command.setjob.params.grade.name'), help = Lang:t('command.setjob.params.grade.help') } }, true, function(source, args) - local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) + local Player = ResolvePlayerArg(args[1]) if Player then Player.Functions.SetJob(tostring(args[2]), tonumber(args[3])) else @@ -261,7 +283,7 @@ QBCore.Commands.Add('gang', Lang:t('command.gang.help'), {}, false, function(sou end, 'user') QBCore.Commands.Add('setgang', Lang:t('command.setgang.help'), { { name = Lang:t('command.setgang.params.id.name'), help = Lang:t('command.setgang.params.id.help') }, { name = Lang:t('command.setgang.params.gang.name'), help = Lang:t('command.setgang.params.gang.help') }, { name = Lang:t('command.setgang.params.grade.name'), help = Lang:t('command.setgang.params.grade.help') } }, true, function(source, args) - local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) + local Player = ResolvePlayerArg(args[1]) if Player then Player.Functions.SetGang(tostring(args[2]), tonumber(args[3])) else diff --git a/server/functions.lua b/server/functions.lua index c01aed2c4..3121bcddc 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -63,6 +63,19 @@ function QBCore.Functions.GetPlayerByCitizenId(citizenid) return nil end +---Get player by id +---@param id number +---@return table? +function QBCore.Functions.GetPlayerById(id) + if not QBConfig.Server.StaticId.Enabled then return nil end + for src in pairs(QBCore.Players) do + if QBCore.Players[src].PlayerData.userId == id then + return QBCore.Players[src] + end + end + return nil +end + ---Get offline player by citizen id ---@param citizenid string ---@return table? diff --git a/server/main.lua b/server/main.lua index d6228e598..934823302 100644 --- a/server/main.lua +++ b/server/main.lua @@ -47,3 +47,28 @@ local function GetSharedGangs() return QBShared.Gangs end exports('GetSharedGangs', GetSharedGangs) + +CreateThread(function() + local col = 'userId' + local tbl = 'players' + local exists = MySQL.prepare.await('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?', { tbl, col }) + if QBConfig.Server.StaticId.Enabled then + if exists == 0 then + MySQL.query.await('ALTER TABLE `players` ADD COLUMN `userId` INT NULL DEFAULT NULL, ADD INDEX `userId_idx` (`userId`)') + end + local rows = MySQL.query.await('SELECT citizenid, userId FROM players') + if rows then + for i = 1, #rows do + local r = rows[i] + if not r.userId or r.userId == 0 then + local newId = QBCore.Player.CreatePlayerId() + MySQL.update.await('UPDATE players SET userId = ? WHERE citizenid = ?', { newId, r.citizenid }) + end + end + end + else + if exists > 0 then + MySQL.query.await('ALTER TABLE `players` DROP COLUMN `userId`') + end + end +end) diff --git a/server/player.lua b/server/player.lua index 4f73c0d30..5be9d3cf4 100644 --- a/server/player.lua +++ b/server/player.lua @@ -12,6 +12,13 @@ function QBCore.Player.Login(source, citizenid, newData) 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 + if QBConfig.Server.StaticId.Enabled and (not PlayerData.userId or PlayerData.userId == 0) then + local newId = QBCore.Player.CreatePlayerId() + if newId and newId > 0 then + PlayerData.userId = newId + MySQL.update.await('UPDATE players SET userId = ? WHERE citizenid = ?', { PlayerData.userId, PlayerData.citizenid }) + end + end PlayerData.money = json.decode(PlayerData.money) PlayerData.job = json.decode(PlayerData.job) PlayerData.gang = json.decode(PlayerData.gang) @@ -37,6 +44,13 @@ function QBCore.Player.GetOfflinePlayer(citizenid) if citizenid then local PlayerData = MySQL.prepare.await('SELECT * FROM players where citizenid = ?', { citizenid }) if PlayerData then + if QBConfig.Server.StaticId.Enabled and (not PlayerData.userId or PlayerData.userId == 0) then + local newId = QBCore.Player.CreatePlayerId() + if newId and newId > 0 then + PlayerData.userId = newId + MySQL.update.await('UPDATE players SET userId = ? WHERE citizenid = ?', { PlayerData.userId, PlayerData.citizenid }) + end + end PlayerData.money = json.decode(PlayerData.money) PlayerData.job = json.decode(PlayerData.job) PlayerData.gang = json.decode(PlayerData.gang) @@ -77,6 +91,25 @@ function QBCore.Player.GetOfflinePlayerByLicense(license) return nil end +function QBCore.Player.GetOfflinePlayerById(id) + if id then + if not QBConfig.Server.StaticId.Enabled then return nil end + local PlayerData = MySQL.prepare.await('SELECT * FROM players where userId = ?', { id }) + if PlayerData then + PlayerData.money = json.decode(PlayerData.money) + PlayerData.job = json.decode(PlayerData.job) + PlayerData.faction = json.decode(PlayerData.faction) + PlayerData.gang = json.decode(PlayerData.gang) + PlayerData.position = json.decode(PlayerData.position) + PlayerData.metadata = json.decode(PlayerData.metadata) + PlayerData.charinfo = json.decode(PlayerData.charinfo) + PlayerData.staff = json.decode(PlayerData.staff) + 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 @@ -98,6 +131,16 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData.source = source PlayerData.license = PlayerData.license or QBCore.Functions.GetIdentifier(source, 'license') PlayerData.name = GetPlayerName(source) + if QBConfig.Server.StaticId.Enabled then + if not PlayerData.userId then + local newId = QBCore.Player.CreatePlayerId() + if newId and newId > 0 then + PlayerData.userId = newId + end + end + else + PlayerData.userId = nil + end end local validatedJob = false @@ -144,6 +187,7 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData.gang = nil end + applyDefaults(PlayerData, QBCore.Config.Player.PlayerDefaults) if GetResourceState('qb-inventory') ~= 'missing' then @@ -487,18 +531,36 @@ function QBCore.Player.Save(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 QBConfig.Server.StaticId.Enabled then + MySQL.insert('INSERT INTO players (userId, citizenid, cid, license, name, staff, money, charinfo, job, gang, position, metadata) VALUES (:userId, :citizenid, :cid, :license, :name, :staff, :money, :charinfo, :job, :gang, :position, :metadata) ON DUPLICATE KEY UPDATE userId = :userId, cid = :cid, name = :name, staff = :staff, money = :money, charinfo = :charinfo, job = :job, gang = :gang, position = :position, metadata = :metadata', { + userId = PlayerData.userId, + citizenid = PlayerData.citizenid, + cid = tonumber(PlayerData.cid), + license = PlayerData.license, + name = PlayerData.name, + staff = json.encode(PlayerData.staff), + 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) + }) + else + MySQL.insert('INSERT INTO players (citizenid, cid, license, name, staff, money, charinfo, job, gang, position, metadata) VALUES (:citizenid, :cid, :license, :name, :staff, :money, :charinfo, :job, :gang, :position, :metadata) ON DUPLICATE KEY UPDATE cid = :cid, name = :name, staff = :staff, 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, + staff = json.encode(PlayerData.staff), + 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) + }) + end if GetResourceState('qb-inventory') ~= 'missing' then exports['qb-inventory']:SaveInventory(source) end QBCore.ShowSuccess(resourceName, PlayerData.name .. ' PLAYER SAVED!') else @@ -508,18 +570,36 @@ 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 QBConfig.Server.StaticId.Enabled then + MySQL.insert('INSERT INTO players (userId, citizenid, cid, license, name, staff, money, charinfo, job, gang, position, metadata) VALUES (:userId, :citizenid, :cid, :license, :name, :staff, :money, :charinfo, :job, :gang, :position, :metadata) ON DUPLICATE KEY UPDATE userId = :userId, cid = :cid, name = :name, staff = :staff, money = :money, charinfo = :charinfo, job = :job, gang = :gang, position = :position, metadata = :metadata', { + userId = PlayerData.userId, + citizenid = PlayerData.citizenid, + cid = tonumber(PlayerData.cid), + license = PlayerData.license, + name = PlayerData.name, + staff = json.encode(PlayerData.staff), + 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) + }) + else + MySQL.insert('INSERT INTO players (citizenid, cid, license, name, staff, money, charinfo, job, gang, position, metadata) VALUES (:citizenid, :cid, :license, :name, :staff, :money, :charinfo, :job, :gang, :position, :metadata) ON DUPLICATE KEY UPDATE cid = :cid, name = :name, staff = :staff, 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, + staff = json.encode(PlayerData.staff), + 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) + }) + end if GetResourceState('qb-inventory') ~= 'missing' then exports['qb-inventory']:SaveInventory(PlayerData, true) end QBCore.ShowSuccess(resourceName, PlayerData.name .. ' OFFLINE PLAYER SAVED!') else @@ -621,6 +701,15 @@ end -- Util Functions +function QBCore.Player.CreatePlayerId() + local col = 'userId' + local exists = MySQL.prepare.await('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_NAME = "players" AND COLUMN_NAME = ? AND TABLE_SCHEMA = DATABASE()', { col }) + if exists == 0 then return 0 end + local query = MySQL.query.await('SELECT MAX(userId) as maxid FROM players') + local maxid = (query and query[1] and query[1].maxid) or 0 + return (maxid or 0) + 1 +end + 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 })