From d88e51ee4fe5eb4baf642e99ea5452d25700ed13 Mon Sep 17 00:00:00 2001 From: BerkieBb <82737367+BerkieBb@users.noreply.github.com> Date: Fri, 19 Aug 2022 18:33:38 +0200 Subject: [PATCH 1/2] Remove inventory code from the core (#784) * tweak(config): remove inventory code * tweak(client/functions): remove inventory code * cleanup(server/commands): remove inventory command * cleanup(server/events): remove hasitem callback * cleanup(server/functions): remove inventory code * cleanup(server/player): remove inventory code * feat(server/player): QBCore:Player:SetPlayerData server side Removed QBCore:Server:PlayerLoaded for PR that will be merged before me that adds it * feat(server/player): QBCore:Server:OnGangUpdate * fix(shared/items): remove duplicate snowball * fix(server/events): warn for exploitable event * fix(server/functions): startup error * feat(server/player): some backwards compatibility * fix(server/events): warn for exploitable event * feat(server/player): Player.Functions.AddField * feat(server/exports): SetField and AddField * fix(server/player): inventory state check --- client/events.lua | 4 +- client/functions.lua | 38 +----- config.lua | 2 - server/commands.lua | 12 -- server/events.lua | 42 +----- server/exports.lua | 32 +++++ server/functions.lua | 51 +++---- server/player.lua | 319 ++++++++----------------------------------- shared/items.lua | 1 - 9 files changed, 119 insertions(+), 382 deletions(-) diff --git a/client/events.lua b/client/events.lua index aae8979..66f88e0 100644 --- a/client/events.lua +++ b/client/events.lua @@ -171,8 +171,10 @@ RegisterNetEvent('QBCore:Notify', function(text, type, length) QBCore.Functions.Notify(text, type, length) end) +-- This event is exploitable and should not be used. It has been deprecated, and will be removed soon. RegisterNetEvent('QBCore:Client:UseItem', function(item) - TriggerServerEvent('QBCore:Server:UseItem', item) + QBCore.Debug(string.format("%s triggered QBCore:Client:UseItem by ID %s with the following data. This event is deprecated due to exploitation, and will be removed soon. Check qb-inventory for the right use on this event.", GetInvokingResource(), source)) + QBCore.Debug(item) end) -- Callback Events -- diff --git a/client/functions.lua b/client/functions.lua index 6292a46..c98973f 100644 --- a/client/functions.lua +++ b/client/functions.lua @@ -8,38 +8,12 @@ function QBCore.Functions.GetPlayerData(cb) end function QBCore.Functions.GetCoords(entity) - return vector4(GetEntityCoords(entity), GetEntityHeading(entity)) + local coords = GetEntityCoords(entity) + return vector4(coords.x, coords.y, coords.z, GetEntityHeading(entity)) end function QBCore.Functions.HasItem(items, amount) - local isTable = type(items) == 'table' - local isArray = isTable and table.type(items) == 'array' or false - local totalItems = #items - local count = 0 - local kvIndex = 2 - if isTable and not isArray then - totalItems = 0 - for _ in pairs(items) do totalItems += 1 end - kvIndex = 1 - end - for _, itemData in pairs(QBCore.PlayerData.items) do - if isTable then - for k, v in pairs(items) do - local itemKV = {k, v} - if itemData and itemData.name == itemKV[kvIndex] and ((amount and itemData.amount >= amount) or (not isArray and itemData.amount >= v) or (not amount and isArray)) then - count += 1 - end - end - if count == totalItems then - return true - end - else -- Single item as string - if itemData and itemData.name == items and (not amount or (itemData and amount and itemData.amount >= amount)) then - return true - end - end - end - return false + return exports['qb-inventory']:HasItem(items, amount) end -- Utility @@ -84,8 +58,8 @@ function QBCore.Functions.RequestAnimDict(animDict) end function QBCore.Functions.PlayAnim(animDict, animName, upperbodyOnly, duration) - local flags = upperbodyOnly == true and 16 or 0 - local runTime = duration ~= nil and duration or -1 + local flags = upperbodyOnly and 16 or 0 + local runTime = duration or -1 QBCore.Functions.RequestAnimDict(animDict) TaskPlayAnim(PlayerPedId(), animDict, animName, 8.0, 1.0, runTime, flags, 0.0, false, false, true) RemoveAnimDict(animDict) @@ -158,8 +132,6 @@ function QBCore.Functions.TriggerCallback(name, cb, ...) TriggerServerEvent('QBCore:Server:TriggerCallback', name, ...) end - - function QBCore.Functions.Progressbar(name, label, duration, useWhileDead, canCancel, disableControls, animation, prop, propTwo, onFinish, onCancel) if GetResourceState('progressbar') ~= 'started' then error('progressbar needs to be started in order for QBCore.Functions.Progressbar to work') end exports['progressbar']:Progress({ diff --git a/config.lua b/config.lua index 186acb1..da61617 100644 --- a/config.lua +++ b/config.lua @@ -12,8 +12,6 @@ QBConfig.Money.PayCheckTimeOut = 10 -- The time in minutes that it will give the QBConfig.Money.PayCheckSociety = false -- If true paycheck will come from the society account that the player is employed at, requires qb-management QBConfig.Player = {} -QBConfig.Player.MaxWeight = 120000 -- Max weight a player can carry (currently 120kg, written in grams) -QBConfig.Player.MaxInvSlots = 41 -- Max inventory slots for a player QBConfig.Player.HungerRate = 4.2 -- Rate at which hunger goes down. QBConfig.Player.ThirstRate = 3.8 -- Rate at which thirst goes down. QBConfig.Player.Bloodtypes = { diff --git a/server/commands.lua b/server/commands.lua index a85acff..b4f5d82 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -235,18 +235,6 @@ QBCore.Commands.Add('setgang', 'Set A Players Gang (Admin Only)', { { name = 'id end end, 'admin') --- Inventory (should be in qb-inventory?) - -QBCore.Commands.Add('clearinv', 'Clear Players Inventory (Admin Only)', { { name = 'id', help = 'Player ID' } }, false, function(source, args) - local playerId = args[1] ~= '' and args[1] or source - local Player = QBCore.Functions.GetPlayer(tonumber(playerId)) - if Player then - Player.Functions.ClearInventory() - else - TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') - end -end, 'admin') - -- Out of Character Chat QBCore.Commands.Add('ooc', 'OOC Chat Message', {}, false, function(source, args) diff --git a/server/events.lua b/server/events.lua index 8526153..bb2995a 100644 --- a/server/events.lua +++ b/server/events.lua @@ -167,10 +167,10 @@ end) -- Items +-- This event is exploitable and should not be used. It has been deprecated, and will be removed soon. RegisterNetEvent('QBCore:Server:UseItem', function(item) - local src = source - if not item or item.amount <= 0 or not QBCore.Functions.CanUseItem(item.name) then return end - QBCore.Functions.UseItem(src, item) + print(string.format("%s triggered QBCore:Server:UseItem by ID %s with the following data. This event is deprecated due to exploitation, and will be removed soon. Check qb-inventory for the right use on this event.", GetInvokingResource(), source)) + QBCore.Debug(item) end) RegisterNetEvent('QBCore:Server:RemoveItem', function(itemName, amount, slot) @@ -206,42 +206,6 @@ RegisterNetEvent('QBCore:CallCommand', function(command, args) end end) --- Has Item Callback (can also use client function - QBCore.Functions.HasItem(item)) - -QBCore.Functions.CreateCallback('QBCore:HasItem', function(source, cb, items, amount) - local retval = false - local Player = QBCore.Functions.GetPlayer(source) - if not Player then return cb(false) end - local isTable = type(items) == 'table' - local isArray = isTable and table.type(items) == 'array' or false - local totalItems = #items - local count = 0 - local kvIndex = 2 - if isTable and not isArray then - totalItems = 0 - for _ in pairs(items) do totalItems += 1 end - kvIndex = 1 - end - if isTable then - for k, v in pairs(items) do - local itemKV = {k, v} - local item = Player.Functions.GetItemByName(itemKV[kvIndex]) - if item and ((amount and item.amount >= amount) or (not amount and not isArray and item.amount >= v) or (not amount and isArray)) then - count += 1 - end - end - if count == totalItems then - retval = true - end - else -- Single item as string - local item = Player.Functions.GetItemByName(items) - if item and not amount or (item and amount and item.amount >= amount) then - retval = true - end - end - cb(retval) -end) - -- Use this for player vehicle spawning -- Vehicle server-side spawning callback (netId) -- use the netid on the client with the NetworkGetEntityFromNetworkId native diff --git a/server/exports.lua b/server/exports.lua index f369d73..64d43c3 100644 --- a/server/exports.lua +++ b/server/exports.lua @@ -1,3 +1,35 @@ +-- Add or change (a) method(s) in the QBCore.Functions table +local function SetMethod(methodName, handler) + if type(methodName) ~= "string" then + return false, "invalid_method_name" + end + + QBCore.Functions[methodName] = handler + + TriggerEvent('QBCore:Server:UpdateObject') + + return true, "success" +end + +QBCore.Functions.SetMethod = SetMethod +exports("SetMethod", SetMethod) + +-- Add or change (a) field(s) in the QBCore table +local function SetField(fieldName, data) + if type(fieldName) ~= "string" then + return false, "invalid_field_name" + end + + QBCore[fieldName] = data + + TriggerEvent('QBCore:Server:UpdateObject') + + return true, "success" +end + +QBCore.Functions.SetField = SetField +exports("SetField", SetField) + -- Single add job function which should only be used if you planning on adding a single job local function AddJob(jobName, job) if type(jobName) ~= "string" then diff --git a/server/functions.lua b/server/functions.lua index f921394..df55be2 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -253,15 +253,28 @@ end -- Items function QBCore.Functions.CreateUseableItem(item, cb) - QBCore.UseableItems[item] = cb + if GetResourceState('qb-inventory') == 'missing' then return end + + if GetResourceState('qb-inventory') ~= 'started' then + CreateThread(function() + repeat + Wait(1000) + until GetResourceState('qb-inventory') == 'started' + exports['qb-inventory']:CreateUsableItem(item, cb) + end) + else + exports['qb-inventory']:CreateUsableItem(item, cb) + end end function QBCore.Functions.CanUseItem(item) - return QBCore.UseableItems[item] + if GetResourceState('qb-inventory') == 'missing' then return end + return exports['qb-inventory']:GetUsableItem(item) end function QBCore.Functions.UseItem(source, item) - QBCore.UseableItems[item.name](source, item) + if GetResourceState('qb-inventory') == 'missing' then return end + exports['qb-inventory']:UseItem(source, item) end -- Kick Player @@ -402,36 +415,8 @@ end -- Utility functions function QBCore.Functions.HasItem(source, items, amount) - local Player = QBCore.Functions.GetPlayer(source) - if not Player then return false end - local isTable = type(items) == 'table' - local isArray = isTable and table.type(items) == 'array' or false - local totalItems = #items - local count = 0 - local kvIndex = 2 - if isTable and not isArray then - totalItems = 0 - for _ in pairs(items) do totalItems += 1 end - kvIndex = 1 - end - if isTable then - for k, v in pairs(items) do - local itemKV = {k, v} - local item = Player.Functions.GetItemByName(itemKV[kvIndex]) - if item and ((amount and item.amount >= amount) or (not isArray and item.amount >= v) or (not amount and isArray)) then - count += 1 - end - end - if count == totalItems then - return true - end - else -- Single item as string - local item = Player.Functions.GetItemByName(items) - if item and (not amount or (item and amount and item.amount >= amount)) then - return true - end - end - return false + if GetResourceState('qb-inventory') == 'missing' then return end + return exports['qb-inventory']:HasItem(source, items, amount) end function QBCore.Functions.Notify(source, text, type, length) diff --git a/server/player.lua b/server/player.lua index fddacbc..0c5064e 100644 --- a/server/player.lua +++ b/server/player.lua @@ -60,7 +60,7 @@ end function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData = PlayerData or {} local Offline = true - if source ~= nil then + if source then PlayerData.source = source PlayerData.license = PlayerData.license or QBCore.Functions.GetIdentifier(source, 'license') PlayerData.name = GetPlayerName(source) @@ -74,6 +74,7 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) for moneytype, startamount in pairs(QBCore.Config.Money.MoneyTypes) do PlayerData.money[moneytype] = PlayerData.money[moneytype] or startamount end + -- Charinfo PlayerData.charinfo = PlayerData.charinfo or {} PlayerData.charinfo.firstname = PlayerData.charinfo.firstname or 'Firstname' @@ -158,7 +159,7 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData.gang.grade.level = PlayerData.gang.grade.level or 0 -- Other PlayerData.position = PlayerData.position or QBConfig.DefaultSpawn - PlayerData = QBCore.Player.LoadInventory(PlayerData) + PlayerData.items = GetResourceState('qb-inventory') ~= 'missing' and exports['qb-inventory']:LoadInventory(PlayerData.source, PlayerData.citizenid) or {} return QBCore.Player.CreatePlayer(PlayerData, Offline) end @@ -183,6 +184,7 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) function self.Functions.UpdatePlayerData(dontUpdateChat) if self.Offline then return end -- Unsupported for Offline Players + TriggerEvent('QBCore:Player:SetPlayerData', self.PlayerData) TriggerClientEvent('QBCore:Player:SetPlayerData', self.PlayerData.source, self.PlayerData) if not dontUpdateChat then QBCore.Commands.Refresh(self.PlayerData.source) @@ -242,6 +244,7 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) 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 @@ -347,148 +350,6 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) return self.PlayerData.money[moneytype] end - function self.Functions.AddItem(item, amount, slot, info) - local totalWeight = QBCore.Player.GetTotalWeight(self.PlayerData.items) - local itemInfo = QBCore.Shared.Items[item:lower()] - if not itemInfo and not self.Offline then - TriggerClientEvent('QBCore:Notify', self.PlayerData.source, Lang:t('error.item_not_exist'), 'error') - return - end - amount = tonumber(amount) - slot = tonumber(slot) or QBCore.Player.GetFirstSlotByItem(self.PlayerData.items, item) - if itemInfo['type'] == 'weapon' and not info then - info = { - serie = tostring(QBCore.Shared.RandomInt(2) .. QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(1) .. QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(4)), - } - end - if (totalWeight + (itemInfo['weight'] * amount)) <= QBCore.Config.Player.MaxWeight then - if (slot and self.PlayerData.items[slot]) and (self.PlayerData.items[slot].name:lower() == item:lower()) and (itemInfo['type'] == 'item' and not itemInfo['unique']) then - self.PlayerData.items[slot].amount = self.PlayerData.items[slot].amount + amount - - if not self.Offline then - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'AddItem', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** got item: [slot:' .. slot .. '], itemname: ' .. self.PlayerData.items[slot].name .. ', added amount: ' .. amount .. ', new total amount: ' .. self.PlayerData.items[slot].amount) - end - - return true - elseif not itemInfo['unique'] and slot or slot and self.PlayerData.items[slot] == nil then - self.PlayerData.items[slot] = { name = itemInfo['name'], amount = amount, info = info or '', label = itemInfo['label'], description = itemInfo['description'] or '', weight = itemInfo['weight'], type = itemInfo['type'], unique = itemInfo['unique'], useable = itemInfo['useable'], image = itemInfo['image'], shouldClose = itemInfo['shouldClose'], slot = slot, combinable = itemInfo['combinable'] } - - if not self.Offline then - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'AddItem', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** got item: [slot:' .. slot .. '], itemname: ' .. self.PlayerData.items[slot].name .. ', added amount: ' .. amount .. ', new total amount: ' .. self.PlayerData.items[slot].amount) - end - - return true - elseif itemInfo['unique'] or (not slot or slot == nil) or itemInfo['type'] == 'weapon' then - for i = 1, QBConfig.Player.MaxInvSlots, 1 do - if self.PlayerData.items[i] == nil then - self.PlayerData.items[i] = { name = itemInfo['name'], amount = amount, info = info or '', label = itemInfo['label'], description = itemInfo['description'] or '', weight = itemInfo['weight'], type = itemInfo['type'], unique = itemInfo['unique'], useable = itemInfo['useable'], image = itemInfo['image'], shouldClose = itemInfo['shouldClose'], slot = i, combinable = itemInfo['combinable'] } - - if not self.Offline then - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'AddItem', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** got item: [slot:' .. i .. '], itemname: ' .. self.PlayerData.items[i].name .. ', added amount: ' .. amount .. ', new total amount: ' .. self.PlayerData.items[i].amount) - end - - return true - end - end - end - elseif not self.Offline then - TriggerClientEvent('QBCore:Notify', self.PlayerData.source, Lang:t('error.too_heavy'), 'error') - end - return false - end - - function self.Functions.RemoveItem(item, amount, slot) - amount = tonumber(amount) - slot = tonumber(slot) - if slot then - if self.PlayerData.items[slot].amount > amount then - self.PlayerData.items[slot].amount = self.PlayerData.items[slot].amount - amount - - if not self.Offline then - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'RemoveItem', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** lost item: [slot:' .. slot .. '], itemname: ' .. self.PlayerData.items[slot].name .. ', removed amount: ' .. amount .. ', new total amount: ' .. self.PlayerData.items[slot].amount) - end - - return true - elseif self.PlayerData.items[slot].amount == amount then - self.PlayerData.items[slot] = nil - - if not self.Offline then - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'RemoveItem', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** lost item: [slot:' .. slot .. '], itemname: ' .. item .. ', removed amount: ' .. amount .. ', item removed') - end - - return true - end - else - local slots = QBCore.Player.GetSlotsByItem(self.PlayerData.items, item) - local amountToRemove = amount - if slots then - for _, _slot in pairs(slots) do - if self.PlayerData.items[_slot].amount > amountToRemove then - self.PlayerData.items[_slot].amount = self.PlayerData.items[_slot].amount - amountToRemove - - if not self.Offline then - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'RemoveItem', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** lost item: [slot:' .. _slot .. '], itemname: ' .. self.PlayerData.items[_slot].name .. ', removed amount: ' .. amount .. ', new total amount: ' .. self.PlayerData.items[_slot].amount) - end - - return true - elseif self.PlayerData.items[_slot].amount == amountToRemove then - self.PlayerData.items[_slot] = nil - - if not self.Offline then - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'RemoveItem', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** lost item: [slot:' .. _slot .. '], itemname: ' .. item .. ', removed amount: ' .. amount .. ', item removed') - end - - return true - end - end - end - end - return false - end - - function self.Functions.SetInventory(items, dontUpdateChat) - self.PlayerData.items = items - - if not self.Offline then - self.Functions.UpdatePlayerData(dontUpdateChat) - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'SetInventory', 'blue', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** items set: ' .. json.encode(items)) - end - end - - function self.Functions.ClearInventory() - self.PlayerData.items = {} - - if not self.Offline then - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'ClearInventory', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** inventory cleared') - end - end - - function self.Functions.GetItemByName(item) - item = tostring(item):lower() - local slot = QBCore.Player.GetFirstSlotByItem(self.PlayerData.items, item) - return self.PlayerData.items[slot] - end - - function self.Functions.GetItemsByName(item) - item = tostring(item):lower() - local items = {} - local slots = QBCore.Player.GetSlotsByItem(self.PlayerData.items, item) - for _, slot in pairs(slots) do - if slot then - items[#items+1] = self.PlayerData.items[slot] - end - end - return items - end - function self.Functions.SetCreditCard(cardNumber) self.PlayerData.charinfo.card = cardNumber self.Functions.UpdatePlayerData() @@ -496,7 +357,7 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) function self.Functions.GetCardSlot(cardNumber, cardType) local item = tostring(cardType):lower() - local slots = QBCore.Player.GetSlotsByItem(self.PlayerData.items, item) + local slots = exports['qb-inventory']:GetSlotsByItem(self.PlayerData.items, item) for _, slot in pairs(slots) do if slot then if self.PlayerData.items[slot].info.cardNumber == cardNumber then @@ -507,15 +368,6 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) return nil end - function self.Functions.GetItemBySlot(slot) - slot = tonumber(slot) - return self.PlayerData.items[slot] - end - - function self.Functions.AddMethod(methodName, handler) - self.Functions[methodName] = handler - end - function self.Functions.Save() if self.Offline then QBCore.Player.SaveOffline(self.PlayerData) @@ -529,6 +381,14 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) 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 @@ -544,8 +404,8 @@ 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) + AddEventHandler('QBCore:Server:PlayerLoaded', function(Player) + QBCore.Functions.AddPlayerMethod(Player.PlayerData.source, "functionName", function(oneArg, orMore) -- do something here end) end) @@ -570,6 +430,33 @@ function QBCore.Functions.AddPlayerMethod(ids, methodName, handler) 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) @@ -589,7 +476,7 @@ function QBCore.Player.Save(source) position = json.encode(pcoords), metadata = json.encode(PlayerData.metadata) }) - QBCore.Player.SaveInventory(source) + if GetResourceState('qb-inventory') ~= 'missing' then exports['qb-inventory']:SaveInventory(source) end QBCore.ShowSuccess(GetCurrentResourceName(), PlayerData.name .. ' PLAYER SAVED!') else QBCore.ShowError(GetCurrentResourceName(), 'ERROR QBCORE.PLAYER.SAVE - PLAYERDATA IS EMPTY!') @@ -610,7 +497,7 @@ function QBCore.Player.SaveOffline(PlayerData) position = json.encode(PlayerData.position), metadata = json.encode(PlayerData.metadata) }) - QBCore.Player.SaveOfflineInventory(PlayerData) + if GetResourceState('qb-inventory') ~= 'missing' then exports['qb-inventory']:SaveInventory(PlayerData, true) end QBCore.ShowSuccess(GetCurrentResourceName(), PlayerData.name .. ' OFFLINE PLAYER SAVED!') else QBCore.ShowError(GetCurrentResourceName(), 'ERROR QBCORE.PLAYER.SAVEOFFLINE - PLAYERDATA IS EMPTY!') @@ -658,125 +545,35 @@ function QBCore.Player.DeleteCharacter(source, citizenid) end end --- Inventory - -function QBCore.Player.LoadInventory(PlayerData) - PlayerData.items = {} - local inventory = MySQL.prepare.await('SELECT inventory FROM players WHERE citizenid = ?', { PlayerData.citizenid }) - local missingItems = {} - if inventory then - inventory = json.decode(inventory) - if next(inventory) then - for _, item in pairs(inventory) do - if item then - local itemInfo = QBCore.Shared.Items[item.name:lower()] - if itemInfo then - PlayerData.items[item.slot] = { - name = itemInfo['name'], - amount = item.amount, - info = item.info or '', - label = itemInfo['label'], - description = itemInfo['description'] or '', - weight = itemInfo['weight'], - type = itemInfo['type'], - unique = itemInfo['unique'], - useable = itemInfo['useable'], - image = itemInfo['image'], - shouldClose = itemInfo['shouldClose'], - slot = item.slot, - combinable = itemInfo['combinable'] - } - else - missingItems[#missingItems+1] = item.name:lower() - end - - end - end - end - end - - if #missingItems > 0 then - print(("%s the following items removed as they no longer exist: %s"):format(GetPlayerName(PlayerData.source), json.encode(missingItems))) - end - return PlayerData -end +-- Inventory Backwards Compatibility function QBCore.Player.SaveInventory(source) - if not QBCore.Players[source] then return end - local PlayerData = QBCore.Players[source].PlayerData - local items = PlayerData.items - local ItemsJson = {} - if items and next(items) then - for slot, item in pairs(items) do - if items[slot] then - ItemsJson[#ItemsJson+1] = { - name = item.name, - amount = item.amount, - info = item.info, - type = item.type, - slot = slot, - } - end - end - MySQL.prepare('UPDATE players SET inventory = ? WHERE citizenid = ?', { json.encode(ItemsJson), PlayerData.citizenid }) - else - MySQL.prepare('UPDATE players SET inventory = ? WHERE citizenid = ?', { '[]', PlayerData.citizenid }) - end + if GetResourceState('qb-inventory') == 'missing' then return end + exports['qb-inventory']:SaveInventory(source, false) end function QBCore.Player.SaveOfflineInventory(PlayerData) - local items = PlayerData.items - local ItemsJson = {} - if items and next(items) then - for slot, item in pairs(items) do - if items[slot] then - ItemsJson[#ItemsJson+1] = { - name = item.name, - amount = item.amount, - info = item.info, - type = item.type, - slot = slot, - } - end - end - MySQL.Async.prepare('UPDATE players SET inventory = ? WHERE citizenid = ?', { json.encode(ItemsJson), PlayerData.citizenid }) - else - MySQL.Async.prepare('UPDATE players SET inventory = ? WHERE citizenid = ?', { '[]', PlayerData.citizenid }) - end + if GetResourceState('qb-inventory') == 'missing' then return end + exports['qb-inventory']:SaveInventory(PlayerData, true) end --- Util Functions - function QBCore.Player.GetTotalWeight(items) - local weight = 0 - if not items then return 0 end - for _, item in pairs(items) do - weight += item.weight * item.amount - end - return tonumber(weight) + if GetResourceState('qb-inventory') == 'missing' then return end + return exports['qb-inventory']:GetTotalWeight(items) end function QBCore.Player.GetSlotsByItem(items, itemName) - local slotsFound = {} - if not items then return slotsFound end - for slot, item in pairs(items) do - if item.name:lower() == itemName:lower() then - slotsFound[#slotsFound+1] = slot - end - end - return slotsFound + if GetResourceState('qb-inventory') == 'missing' then return end + return exports['qb-inventory']:GetSlotsByItem(items, itemName) end function QBCore.Player.GetFirstSlotByItem(items, itemName) - if not items then return nil end - for slot, item in pairs(items) do - if item.name:lower() == itemName:lower() then - return tonumber(slot) - end - end - return nil + if GetResourceState('qb-inventory') == 'missing' then return end + return exports['qb-inventory']:GetFirstSlotByItem(items, itemName) end +-- Util Functions + function QBCore.Player.CreateCitizenId() local UniqueFound = false local CitizenId = nil diff --git a/shared/items.lua b/shared/items.lua index d58cd32..3c5cee1 100644 --- a/shared/items.lua +++ b/shared/items.lua @@ -386,7 +386,6 @@ QBShared.Items = { ['moneybag'] = {['name'] = 'moneybag', ['label'] = 'Money Bag', ['weight'] = 0, ['type'] = 'item', ['image'] = 'moneybag.png', ['unique'] = true, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'A bag with cash'}, ['parachute'] = {['name'] = 'parachute', ['label'] = 'Parachute', ['weight'] = 30000, ['type'] = 'item', ['image'] = 'parachute.png', ['unique'] = true, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'The sky is the limit! Woohoo!'}, ['binoculars'] = {['name'] = 'binoculars', ['label'] = 'Binoculars', ['weight'] = 600, ['type'] = 'item', ['image'] = 'binoculars.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Sneaky Breaky...'}, - ['snowball'] = {['name'] = 'snowball', ['label'] = 'Snowball', ['weight'] = 0, ['type'] = 'item', ['image'] = 'snowball.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Should have catched it :D'}, ['lighter'] = {['name'] = 'lighter', ['label'] = 'Lighter', ['weight'] = 0, ['type'] = 'item', ['image'] = 'lighter.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'On new years eve a nice fire to stand next to'}, ['certificate'] = {['name'] = 'certificate', ['label'] = 'Certificate', ['weight'] = 0, ['type'] = 'item', ['image'] = 'certificate.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Certificate that proves you own certain stuff'}, ['markedbills'] = {['name'] = 'markedbills', ['label'] = 'Marked Money', ['weight'] = 1000, ['type'] = 'item', ['image'] = 'markedbills.png', ['unique'] = true, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Money?'}, From 8fffc421bbc85232c9ebff2b6e69aa6231f399e7 Mon Sep 17 00:00:00 2001 From: Kakarot <57848836+GhzGarage@users.noreply.github.com> Date: Fri, 19 Aug 2022 09:48:39 -0700 Subject: [PATCH 2/2] Merge Dev (#794) * [Update] Stuff and Things - Fix Exploit - Update doors on teleport so they don't get stuck (requires my doorlock PR that creates a updateDoors() export - Support for heading with TeleportToCoords event - Support for teleporting to location name with /tp and a default file with locations to use - Remove 'QBCore:Server:AddItem' event (requires PR to other resources that use it) - Support for an array of items that do the same thing with QBCore.Functions.CreateUsableItem - Player exists check before removing or adding permission - Trigger OnPlayerUnload as a server side event too - Player.Functions.GetMetaData function - Added reason to money remove logs - Set dirt level to 0 on /car vehicles * Export to Event * Deprecate instead of remove? * Player+License verification fix * Update events.lua * Export change for qb-management * [Hotfix] Errors after Merge * [Change] src to source * Prevent people using /me to troll This prevents people using that /me troll with the huge font size and different colors. * Remove DoorUpdate event * Update client/events.lua Co-authored-by: BerkieBb <82737367+BerkieBb@users.noreply.github.com> * Update server/functions.lua Co-authored-by: BerkieBb <82737367+BerkieBb@users.noreply.github.com> * Adjustments for BB * Linting * fix(server/functions): use indexed variables Co-authored-by: David Malchin * chore(server/events): remove unnecessary _ Co-authored-by: David Malchin * chore(server/events): remove unnecessary _'s Co-authored-by: David Malchin * chore(server/events): language Co-authored-by: David Malchin * chore(server/events): language Co-authored-by: David Malchin * chore(server/events): language Co-authored-by: David Malchin * chore(server/events): language Co-authored-by: David Malchin * chore(server/player): indentation Co-authored-by: David Malchin * [Chore] Remove Money Removed repetitive code that made zero sense * Update en.lua * Update player.lua * Update player.lua * Update functions.lua Co-authored-by: ItsANoBrainer Co-authored-by: Scullyy <51968381+Scullyy@users.noreply.github.com> Co-authored-by: Idris Dev Co-authored-by: Idris Co-authored-by: BerkieBb <82737367+BerkieBb@users.noreply.github.com> Co-authored-by: David Malchin Co-authored-by: Chris Usiak --- client/events.lua | 3 ++- locale/en.lua | 3 ++- server/commands.lua | 22 +++++++++++------ server/events.lua | 14 +++++------ server/functions.lua | 1 - server/player.lua | 7 ++++++ shared/locations.lua | 58 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 shared/locations.lua diff --git a/client/events.lua b/client/events.lua index 66f88e0..e9b6655 100644 --- a/client/events.lua +++ b/client/events.lua @@ -24,9 +24,10 @@ RegisterNetEvent('QBCore:Command:TeleportToPlayer', function(coords) SetPedCoordsKeepVehicle(ped, coords.x, coords.y, coords.z) end) -RegisterNetEvent('QBCore:Command:TeleportToCoords', function(x, y, z) +RegisterNetEvent('QBCore:Command:TeleportToCoords', function(x, y, z, h) local ped = PlayerPedId() SetPedCoordsKeepVehicle(ped, x, y, z) + SetEntityHeading(ped, h or GetEntityHeading(ped)) end) RegisterNetEvent('QBCore:Command:GoToMarker', function() diff --git a/locale/en.lua b/locale/en.lua index 57b8f02..d03b40b 100644 --- a/locale/en.lua +++ b/locale/en.lua @@ -8,6 +8,7 @@ local Translations = { company_too_poor = 'Your employer is broke', item_not_exist = 'Item does not exist', too_heavy = 'Inventory too full', + location_not_exist = 'Location does not exist', duplicate_license = 'Duplicate Rockstar License Found', no_valid_license = 'No Valid Rockstar License Found', not_whitelisted = 'You\'re not whitelisted for this server', @@ -26,7 +27,7 @@ local Translations = { off_duty = 'You are now off duty!', checking_ban = 'Hello %s. We are checking if you are banned.', join_server = 'Welcome %s to {Server Name}.', - checking_whitelisted = 'Hello %s. We are checking your allowance.' + checking_whitelisted = 'Hello %s. We are checking your allowance.', } } diff --git a/server/commands.lua b/server/commands.lua index b4f5d82..38f2766 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -81,15 +81,23 @@ function QBCore.Commands.Refresh(source) end -- Teleport - -QBCore.Commands.Add('tp', 'TP To Player or Coords (Admin Only)', { { name = 'id/x', help = 'ID of player or X position' }, { name = 'y', help = 'Y position' }, { name = 'z', help = 'Z position' } }, false, function(source, args) +QBCore.Commands.Add('tp', 'TP To Location/Player/Coords (Admin Only)', { { name = 'location/id/x', help = 'location name, ID of player, or X position' }, { name = 'y', help = 'Y position' }, { name = 'z', help = 'Z position' } }, false, function(source, args) if args[1] and not args[2] and not args[3] then - local target = GetPlayerPed(tonumber(args[1])) - if target ~= 0 then - local coords = GetEntityCoords(target) - TriggerClientEvent('QBCore:Command:TeleportToPlayer', source, coords) + if tonumber(args[1]) then + local target = GetPlayerPed(tonumber(args[1])) + if target ~= 0 then + local coords = GetEntityCoords(target) + TriggerClientEvent('QBCore:Command:TeleportToPlayer', source, coords) + else + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') + end else - TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') + local location = QBShared.Locations[args[1]] + if location then + TriggerClientEvent('QBCore:Command:TeleportToCoords', source, location.x, location.y, location.z, location.w) + else + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.location_not_exist'), 'error') + end end else if args[1] and args[2] and args[3] then diff --git a/server/events.lua b/server/events.lua index bb2995a..464dbed 100644 --- a/server/events.lua +++ b/server/events.lua @@ -173,18 +173,16 @@ RegisterNetEvent('QBCore:Server:UseItem', function(item) QBCore.Debug(item) end) -RegisterNetEvent('QBCore:Server:RemoveItem', function(itemName, amount, slot) +-- This event is exploitable and should not be used. It has been deprecated, and will be removed soon. function(itemName, amount, slot) +RegisterNetEvent('QBCore:Server:RemoveItem', function(itemName, amount) local src = source - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end - Player.Functions.RemoveItem(itemName, amount, slot) + print(string.format("%s triggered QBCore:Server:RemoveItem by ID %s for %s %s. This event is deprecated due to exploitation, and will be removed soon. Adjust your events accordingly to do this server side with player functions.", GetInvokingResource(), src, amount, itemName)) end) -RegisterNetEvent('QBCore:Server:AddItem', function(itemName, amount, slot, info) +-- This event is exploitable and should not be used. It has been deprecated, and will be removed soon. function(itemName, amount, slot, info) +RegisterNetEvent('QBCore:Server:AddItem', function(itemName, amount) local src = source - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end - Player.Functions.AddItem(itemName, amount, slot, info) + print(string.format("%s triggered QBCore:Server:AddItem by ID %s for %s %s. This event is deprecated due to exploitation, and will be removed soon. Adjust your events accordingly to do this server side with player functions.", GetInvokingResource(), src, amount, itemName)) end) -- Non-Chat Command Calling (ex: qb-adminmenu) diff --git a/server/functions.lua b/server/functions.lua index df55be2..16124c9 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -199,7 +199,6 @@ function QBCore.Functions.CreateVehicle(source, model, coords, warp) end -- Paychecks (standalone - don't touch) - function PaycheckInterval() if next(QBCore.Players) then for _, Player in pairs(QBCore.Players) do diff --git a/server/player.lua b/server/player.lua index 0c5064e..eb8eb25 100644 --- a/server/player.lua +++ b/server/player.lua @@ -167,6 +167,7 @@ end 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 @@ -269,6 +270,12 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) self.Functions.UpdatePlayerData() end + function self.Functions.GetMetaData(meta) + if not meta or type(meta) ~= 'string' then return end + meta = meta:lower() + return self.PlayerData.metadata[meta] + end + function self.Functions.AddJobReputation(amount) if not amount then return end amount = tonumber(amount) diff --git a/shared/locations.lua b/shared/locations.lua new file mode 100644 index 0000000..7c97fed --- /dev/null +++ b/shared/locations.lua @@ -0,0 +1,58 @@ +QBShared.Locations = { + -- Unknown/Random/Vanilla + ['burgershot'] = vector4(-1199.0568, -882.4495, 13.3500, 209.1105), + ['casino'] = vector4(923.2289, 47.3113, 81.1063, 237.6052), + + -- Gabz + ['arcade'] = vector4(-1649.6089, -1083.9313, 13.1575, 46.4121), + ['beanmachinelegion'] = vector4(116.16, -1022.99, 29.3, 0.0), + ['bowling'] = vector4(761.5008, -777.7256, 26.3078, 90.5581), + ['pizzaria'] = vector4(790.4561, -758.4601, 26.7424, 270.2329), + ['catcafe'] = vector4(-580.8388, -1072.7872, 22.3296, 359.0078), + ['carmeet'] = vector4(958.8237, -1699.6659, 29.5574, 71.2731), + ['davispd'] = vector4(382.5791, -1591.1827, 29.2828, 145.0991), + ['popsdiner'] = vector4(1595.9753, 6448.6421, 25.3170, 28.3026), + ['davisfirestation'] = vector4(216.7576, -1638.3801, 29.5151, 137.8174), + ['harmony'] = vector4(1183.0693, 2648.5313, 37.8363, 194.0603), + ['haters'] = vector4(-1117.1525, -1439.4297, 5.1075, 103.4411), + ['hayes'] = vector4(-1435.7040, -445.7360, 35.5964, 220.1762), + ['pdm'] = vector4(-48.2113, -1105.4769, 27.2634, 339.5899), + ['bennys'] = vector4(-47.5289, -1042.6086, 28.3532, 247.9748), + ['impound'] = vector4(-190.5874, -1156.1343, 23.0482, 158.9385), + ['lamesaauto'] = vector4(720.4776, -1092.1934, 22.2866, 310.1469), + ['lamesapd'] = vector4(824.3735, -1290.0670, 28.2364, 266.1293), + ['lostmc'] = vector4(982.6339, -104.7095, 74.8488, 30.8738), + ['pillbox'] = vector4(298.1153, -584.2825, 43.2609, 252.7553), + ['mrpd'] = vector4(432.0686, -981.6853, 30.7119, 267.3501), + ['mirrorparkhouse1'] = vector4(945.9535, -652.9119, 58.0228, 90.5541), + ['pacificbank'] = vector4(229.9529, 214.3890, 105.5561, 294.6791), + ['paletoliquor'] = vector4(-154.2287, 6328.8682, 31.5665, 133.1992), + ['paletopd'] = vector4(-436.0935, 6015.2026, 31.4892, 132.9752), + ['paletogasstation'] = vector4(120.2420, 6625.4722, 31.9580, 36.5687), + ['pinkcage'] = vector4(323.9055, -203.2524, 54.0866, 186.8505), + ['ponsonbys1'] = vector4(-165.2497, -304.3988, 38.07126, 0.0), + ['ponsonbys2'] = vector4(-1448.1, -236.8420, 48.15098, 0.0), + ['ponsonbys3'] = vector4(-709.8120, -150.6267, 35.75312, 0.0), + ['prison'] = vector4(1847.1284, 2586.0696, 45.6726, 91.7828), + ['rangerstation'] = vector4(387.3204, 790.1508, 187.6927, 4.7656), + ['recordastudio'] = vector4(473.3006, -109.4360, 62.7418, 350.8488), + ['sandypd'] = vector4(1839.7510, 3667.9907, 33.8787, 29.1744), + ['suburban1'] = vector4(124.9756, -217.6290, 55.81879, 0.0), + ['suburban2'] = vector4(617.4776, 2757.4810, 43.34935, 0.0), + ['suburban3'] = vector4(-1195.8690, -773.5746, 18.58485, 0.0), + ['suburban4'] = vector4(-3170.9670, 1049.9310, 22.12445, 0.0), + ['triadrecords'] = vector4(-829.0061, -698.2049, 28.0583, 291.2052), + ['tuner'] = vector4(157.5888, -3017.9968, 7.0400, 94.0695), + ['vu'] = vector4(129.4555, -1299.6754, 29.2327, 27.6378), + + -- Patoche + ['luxerydealership'] = vector4(-1273.22, -371.11, 36.64, 301.8), + + -- Unclejust + ['digitalden'] = vector4(-656.28, -849.92, 24.51, 167.42), + ['ifruitstore1'] = vector4(-646.78, -288.17, 35.49, 297.73), + ['ifruitstore2'] = vector4(-778.7451, -598.2717, 30.2772, 181.0197), + ['taxijob'] = vector4(908.7, -166.45, 74.13, 54.88), + ['vineyard'] = vector4(-1892.29, 2038.47, 140.86, 339.23), + ['weazelnews'] = vector4(-604.82, -933.22, 23.86, 292.3), +} \ No newline at end of file