--[[ Usable Items & Inventory Utilities Module ------------------------------------------- This module provides functions for: • Registering items as usable across different inventory systems (ESX, QBcore, QBX). • Retrieving an item's image as a NUI link. • Adding and removing items from a player's inventory. • Toggling items in inventory (with server event for exploit protection). • Checking for item duplication exploits. • Handling tool durability mechanics. • Checking item availability and retrieving inventory. • Granting random rewards from a reward pool. • Checking if a player can carry specific items based on weight. ]] validTokens = {} -- Function Compatability Table local InvFunc = { { invName = OXInv, removeItem = function(src, item, remamount) exports[OXInv]:RemoveItem(src, item, remamount, nil) end, addItem = function(src, item, amountToAdd, info, slot) exports[OXInv]:AddItem(src, item, amountToAdd, info, slot) end, setItemMetadata = function(data, src) exports[OXInv]:SetMetadata(src, data.slot, data.metadata) end, hasItem = function(item, amount, src) if src then local serverItemCheck = exports[OXInv]:GetItem(src, item, nil, true) or 0 return serverItemCheck >= amount, serverItemCheck else local localItemCheck = exports[OXInv]:GetItemCount(item) or 0 return localItemCheck >= amount, localItemCheck end end, canCarry = function(itemTable, src) local resultTable = {} for k, v in pairs(itemTable) do resultTable[k] = exports[OXInv]:CanCarryItem(src, k, v) end return resultTable end, getMaxInvWeight = function() return exports[OXInv]:GetPlayerMaxWeight() end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += v.weight end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = exports[OXInv]:GetInventoryItems(src) else grabInv = exports[OXInv]:GetPlayerItems() end return grabInv end, invImg = function(item) return "nui://"..OXInv.."/web/images/"..(Items[item].image or "") end, openShop = function(name, label, items) exports[OXInv]:openInventory('shop', { type = name }) end, serverOpenShop = function(shopName) -- end, registerShop = function(name, label, items, society) exports[OXInv]:RegisterShop(name, { name = label, inventory = items, society = society, }) end, openStash = function(data) exports[OXInv]:openInventory('stash', data.stash) end, clearStash = function(stashId) exports[OXInv]:ClearInventory(stashId) end, getStash = function(stashName) local stash = exports[OXInv]:Inventory(stashName) -- Add fallback if ox can't find the stash and returns a boolean return type(stash) == "table" and stash.items or {} end, stashEditMetadata = function(stash, slot, metadata) exports[OXInv]:SetMetadata(stash, slot, metadata) end, stashAddItem = function(stashItems, stashName, items) end, stashRemoveItem = function(stashItems, stashName, items) for k, v in pairs(items) do for _, name in pairs(stashName) do local success = exports[OXInv]:RemoveItem(name, k, v) if success then debugPrint("^6Bridge^7: ^2Removing ^3"..OXInv.." ^2Stash item^7:", k, v) break end end end end, registerStash = function(name, label, slots, weight, owner, coords) exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil) end, }, { invName = CoreInv, removeItem = function(src, item, remamount) exports[CoreInv]:removeItem(src, item, remamount) end, addItem = function(src, item, amountToAdd, info, slot) exports[CoreInv]:addItem(src, item, amountToAdd, info) end, setItemMetadata = function(data, src) exports[CoreInv]:setMetadata(src, data.slot, data.metadata) end, hasItem = function(item, amount, src) if src then local serverItemCheck = exports[CoreInv]:getItemCount(src, item) or 0 return serverItemCheck >= amount, serverItemCheck else local localItemCheck = exports[CoreInv]:getItemCount(item) or 0 return localItemCheck >= amount, localItemCheck end end, canCarry = function(itemTable, src) local resultTable = {} for k, v in pairs(itemTable) do resultTable[k] = exports[CoreInv]:canCarry(src, k, v) end return resultTable end, getMaxInvWeight = function() return InventoryWeight end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += ((v.weight * v.amount) or 0) end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = exports[CoreInv]:getInventory(src) else grabInv = exports[CoreInv]:getInventory() end return grabInv end, invImg = function(item) return "nui://"..CoreInv.."/html/img/"..(Items[item].image or "") end, openShop = function(name, label, items) -- end, serverOpenShop = function(shopName) -- end, registerShop = function(name, label, items, society) -- end, openStash = function(data) TriggerServerEvent('core_inventory:server:openInventory', data.stash, 'stash') end, clearStash = function(stashId) exports[CoreInv]:clearInventory("stash-"..stashId) end, getStash = function(stashName) return exports[CoreInv]:getInventory(stashName) end, stashEditMetadata = function(stash, slot, metadata) local itemData = exports[CoreInv]:getItemBySlot(stash, slot) or {} if itemData then exports[CoreInv]:setItem(stash, itemData.item, itemData.count, metadata) end end, stashAddItem = function(stashItems, stashName, items) -- end, stashRemoveItem = function(stashItems, stashName, items) for k, v in pairs(items) do exports[CoreInv]:removeItemExact(stashName[1], k, v) debugPrint("^6Bridge^7: ^2Removing ^3"..CoreInv.." ^2Stash item^7:", k, v) end end, registerStash = function(name, label, slots, weight, owner, coords) -- end, }, { invName = OrigenInv, removeItem = function(src, item, remamount) exports[OrigenInv]:removeItem(src, item, remamount) end, addItem = function(src, item, amountToAdd, info, slot) exports[OrigenInv]:addItem(src, item, amountToAdd, info, slot) end, setItemMetadata = function(data, src) exports[OrigenInv]:setMetadata(src, data.slot, data.metadata) end, hasItem = function(item, amount, src) if src then local serverItemCheck = exports[OrigenInv]:getItemCount(src, item, false, false) or 0 return serverItemCheck >= amount, serverItemCheck else local localItemCheck = exports[OrigenInv]:Search('count', item) or 0 return localItemCheck >= amount, localItemCheck end end, canCarry = function(itemTable, src) local resultTable = {} for k, v in pairs(itemTable) do resultTable[k] = exports[OrigenInv]:canCarryItem(src, k, v) end return resultTable end, getMaxInvWeight = function() return InventoryWeight end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += ((v.weight * v.amount) or 0) end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = exports[OrigenInv]:GetInventoryItems(src) else grabInv = exports[OrigenInv]:GetInventory() end return grabInv end, invImg = function(item) return "nui://"..OrigenInv.."/html/images/"..(Items[item].image or "") end, openShop = function(name, label, items) -- end, serverOpenShop = function(shopName) -- end, registerShop = function(name, label, items, society) -- end, openStash = function(data) exports[OrigenInv]:openInventory('stash', data.stash, { label = data.label }) end, clearStash = function(stashId) exports[OrigenInv]:ClearInventory(stashId) end, getStash = function(stashName) return exports[OrigenInv]:getInventory(stashName) end, stashEditMetadata = function(stash, slot, metadata) exports[OrigenInv]:setMetadata(stash, slot, metadata) end, stashAddItem = function(stashItems, stashName, items) -- end, stashRemoveItem = function(stashItems, stashName, items) for k, v in pairs(items) do exports[OrigenInv]:RemoveFromStash(stashName[1], k, v) debugPrint("^6Bridge^7: ^2Removing ^3"..OrigenInv.." ^2Stash item^7:", k, v) end end, registerStash = function(name, label, slots, weight, owner, coords) exports[OrigenInv]:registerStash(name, label, slots or 50, weight or 4000000) end, }, { invName = CodeMInv, removeItem = function(src, item, remamount) exports[CodeMInv]:RemoveItem(src, item, remamount) end, addItem = function(src, item, amountToAdd, info, slot) exports[CodeMInv]:AddItem(src, item, amountToAdd, slot, info) end, setItemMetadata = function(data, src) exports[CodeMInv]:SetItemMetadata(src, data.slot, data.metadata) end, hasItem = function(item, amount, src, invCache) local count = 0 for _, itemData in pairs(invCache) do if itemData and itemData.name == item then count += (itemData.amount or 0) end end return count >= amount, count end, canCarry = function(itemTable, src) local resultTable = {} local items = getPlayerInv(src) local totalWeight = 0 if not items then return false end for _, item in pairs(items) do totalWeight += (item.weight * item.amount) end for k, v in pairs(itemTable) do local itemInfo = Items[k] if not itemInfo then resultTable[k] = true else resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight end end return resultTable end, getMaxInvWeight = function() return InventoryWeight end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += ((v.weight * v.amount) or 0) end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = exports[CodeMInv]:GetInventory(getPlayer(src).citizenId, src) else grabInv = exports[CodeMInv]:GetClientPlayerInventory() end return grabInv end, invImg = function(item) return "nui://"..CodeMInv.."/html/itemimages/"..(Items[item].image or "") end, openShop = function(name, label, items) TriggerEvent("codem-inventory:openshop", name) end, serverOpenShop = function(shopName) -- end, registerShop = function(name, label, items, society) -- end, openStash = function(data) TriggerServerEvent('codem-inventory:server:openstash', data.stash, data.slots, data.maxWeight, data.label) end, clearStash = function(stashId) exports[CodeMInv]:ClearInventory(stashId) end, getStash = function(stashName) return exports[CodeMInv]:GetStashItems(stashName) end, stashEditMetadata = function(stash, slot, metadata) end, stashAddItem = function(stashItems, stashName, items) -- end, stashRemoveItem = function(stashItems, stashName, items) for k, v in pairs(items) do for l in pairs(stashItems) do if stashItems[l].name == k then if (stashItems[l].amount - v) <= 0 then stashItems[l] = nil else debugPrint("^6Bridge^7: ^2Removing ^3"..CodeMInv.." ^2Stash item^7:", k, v) stashItems[l].amount -= v end end end end exports[CodeMInv]:UpdateStash(stashName[1], stashItems) end, registerStash = function(name, label, slots, weight, owner, coords) -- end, }, { invName = TgiannInv, removeItem = function(src, item, remamount) exports[TgiannInv]:RemoveItem(src, item, remamount) end, addItem = function(src, item, amountToAdd, info, slot) exports[TgiannInv]:AddItem(src, item, amountToAdd, slot, info) end, setItemMetadata = function(data, src) exports[TgiannInv]:UpdateItemMetadata(src, data.item, data.slot, data.metadata) end, hasItem = function(item, amount, src) if src then local serverItemCheck = exports[TgiannInv]:GetItemCount(src, item, nil, true) or 0 return serverItemCheck >= amount, serverItemCheck else local localItemCheck = exports[TgiannInv]:GetItemCount(item) or 0 return localItemCheck >= amount, localItemCheck end end, canCarry = function(itemTable, src) local resultTable = {} for k, v in pairs(itemTable) do resultTable[k] = exports[TgiannInv]:CanCarryItem(src, k, v) end return resultTable end, getMaxInvWeight = function() return InventoryWeight end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += ((v.weight * v.amount) or 0) end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = exports[TgiannInv]:GetPlayerItems(src) else grabInv = exports[TgiannInv]:GetPlayerItems() end return grabInv end, invImg = function(item) return "nui://inventory_images/images/"..(Items[item].image or "") end, openShop = function(name, label, items) TriggerServerEvent(getScript()..':server:openServerShop', name) end, serverOpenShop = function(shopName) exports[TgiannInv]:OpenShop(source, shopName) end, registerShop = function(name, label, items, society) exports[TgiannInv]:RegisterShop(name, items) end, openStash = function(data) TriggerServerEvent(getScript()..':server:openServerStash', { stashName = data.stash, label = data.label, maxweight = data.maxWeight or 600000, slots = data.slots or 40 }) end, clearStash = function(stashId) exports[TgiannInv]:DeleteInventory("stash", stashId) end, getStash = function(stashName) return exports[TgiannInv]:GetSecondaryInventoryItems("stash", stashName) end, stashEditMetadata = function(stash, slot, metadata) end, stashAddItem = function(stashItems, stashName, items) -- end, stashRemoveItem = function(stashItems, stashName, items) for k, v in pairs(items) do local itemData = exports[TgiannInv]:GetItemByNameFromSecondaryInventory("stash", stashName[1], k) exports[TgiannInv]:RemoveItemFromSecondaryInventory("stash", stashName[1], k, v, itemData.slot, nil) debugPrint("^6Bridge^7: ^2Removing ^3"..TgiannInv.." ^2Stash item^7:", k, v) end end, registerStash = function(name, label, slots, weight, owner, coords) exports[TgiannInv]:RegisterStash(name, label, slots or 50, weight or 4000000) end, }, { invName = JPRInv, removeItem = function(src, item, remamount) while remamount > 0 do if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then remamount -= 1 else print("^1Error removing "..item.." Amount left: "..remamount) break end end if Config.Crafting ~= nil and Config.Crafting.showItemBox then TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", amount or 1) end end, addItem = function(src, item, amountToAdd, info, slot) if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then if Config.Crafting ~= nil and Config.Crafting.showItemBox then TriggerClientEvent("ps-inventory:client:ItemBox", src, Items[item], "add", amountToAdd) end end end, setItemMetadata = function(data, src) local Player = Core.Functions.GetPlayer(src) Player.PlayerData.items[data.slot].info = data.metadata if data.metadata.durability then Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability end Player.Functions.SetInventory(Player.PlayerData.items) end, hasItem = function(item, amount, src, invCache) local count = 0 for _, itemData in pairs(invCache) do if itemData and itemData.name == item then count += (itemData.amount or itemData.count or 1) end end return count >= amount, count end, canCarry = function(itemTable, src) local resultTable = {} local items = getPlayerInv(src) local totalWeight = 0 if not items then return false end for _, item in pairs(items) do totalWeight += (item.weight * item.amount) end for k, v in pairs(itemTable) do local itemInfo = Items[k] if not itemInfo then resultTable[k] = true else resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight end end return resultTable end, getMaxInvWeight = function() if checkExportExists(JPRInv, "GetMaxWeight") then return exports[JPRInv]:GetMaxWeight() else return InventoryWeight end end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += ((v.weight * v.amount) or 0) end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items else grabInv = Core.Functions.GetPlayerData().items end return grabInv end, invImg = function(item) return "nui://"..JPRInv.."/html/images/"..(Items[item].image or "") end, openShop = function(name, label, items) TriggerServerEvent(getScript()..':server:openServerShop', name) TriggerServerEvent("inventory:server:OpenInventory", "shop", label, items) end, serverOpenShop = function(shopName) if checkExportExists(JPRInv, "OpenShop") then exports[JPRInv]:OpenShop(source, shopName) end end, registerShop = function(name, label, items, society) exports[JPRInv]:CreateShop({ name = name, label = label, slots = #items, items = items }) end, openStash = function(data) if QBInvNew then TriggerServerEvent(getScript()..':server:openServerStash', { stashName = data.stash, label = data.label, maxweight = data.maxWeight or 600000, slots = data.slots or 40 }) else TriggerEvent("inventory:client:SetCurrentStash", data.stash) TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, { slots = data.slots or 50, maxWeight = data.maxWeight or 600000 }) end end, clearStash = function(stashId) MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashId, ['items'] = json.encode({}) }) end, getStash = function(stashName) if checkExportExists(JPRInv, "GetStashItems") then return exports[JPRInv]:GetStashItems(stashName) or {} else local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName }) return result and json.decode(result) or {} end end, stashEditMetadata = function(stash, slot, metadata) end, stashAddItem = function(stashItems, stashName, items) -- end, stashRemoveItem = function(stashItems, stashName, items) if not stashItems or not next(stashItems) then stashItems = getStash(stashName[1]) end for k, v in pairs(items) do for l in pairs(stashItems) do if stashItems[l].name == k then if (stashItems[l].amount - v) <= 0 then stashItems[l] = nil else debugPrint("^6Bridge^7: ^2Removing ^3"..JPRInv.." ^2Stash item^7:", k, v) stashItems[l].amount -= v end end end end debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3JPR^2 stash '^6"..stashName[1].."^7'") MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName[1], ['items'] = json.encode(stashItems) }) end, registerStash = function(name, label, slots, weight, owner, coords) -- end, }, { invName = QBInv, removeItem = function(src, item, remamount) if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, remamount, slot) then TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "remove", amount or 1) else print("^1Error removing "..item.." Amount left: "..remamount) end end, addItem = function(src, item, amountToAdd, info, slot) if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "add", amountToAdd) end end, setItemMetadata = function(data, src) local Player = Core.Functions.GetPlayer(src) Player.PlayerData.items[data.slot].info = data.metadata if data.metadata.durability then Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability end Player.Functions.SetInventory(Player.PlayerData.items) end, hasItem = function(item, amount, src, invCache) local count = 0 for _, itemData in pairs(invCache) do if itemData and itemData.name == item then count += (itemData.amount or itemData.count or 1) end end return count >= amount, count end, canCarry = function(itemTable, src) local resultTable = {} if checkExportExists(QBInv, "CanAddItem") then for k, v in pairs(itemTable) do resultTable[k] = exports[QBInv]:CanAddItem(src, k, v) end else local items = getPlayerInv(src) local totalWeight = 0 if not items then return false end for _, item in pairs(items) do totalWeight += (item.weight * item.amount) end for k, v in pairs(itemTable) do local itemInfo = Items[k] if not itemInfo then resultTable[k] = true else resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight end end end return resultTable end, getMaxInvWeight = function() if checkExportExists(QBInv, "GetMaxWeight") then return exports[QBInv]:GetMaxWeight() else return InventoryWeight end end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += ((v.weight * v.amount) or 0) end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items else grabInv = Core.Functions.GetPlayerData().items end return grabInv end, invImg = function(item) return "nui://"..QBInv.."/html/images/"..(Items[item].image or "") end, openShop = function(name, label, items) TriggerServerEvent(getScript()..':server:openServerShop', name) TriggerServerEvent("inventory:server:OpenInventory", "shop", label, items) end, serverOpenShop = function(shopName) if checkExportExists(QBInv, "OpenShop") then exports[QBInv]:OpenShop(source, shopName) end end, registerShop = function(name, label, items, society) if checkExportExists(QBInv, "CreateShop") then exports[QBInv]:CreateShop({ name = name, label = label, slots = #items, items = items, society = society, }) end end, openStash = function(data) if QBInvNew then TriggerServerEvent(getScript()..':server:openServerStash', { stashName = data.stash, label = data.label, maxweight = data.maxWeight or 600000, slots = data.slots or 40 }) else TriggerEvent("inventory:client:SetCurrentStash", data.stash) TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, { slots = data.slots or 50, maxWeight = data.maxWeight or 600000 }) end end, clearStash = function(stashId) if checkExportExists(QBInv, "ClearStash") then exports[QBInv]:ClearStash(stashId) else MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashId, ['items'] = json.encode({}) }) end end, getStash = function(stashName) if QBInvNew then local result = exports[QBInv]:GetInventory(stashName) or {} return result.items or {} else local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName }) if result then return json.decode(result) else return {} end end end, stashEditMetadata = function(stash, slot, metadata) local stashData = getStash(stash) for i = 1, #stashData do if stashData[i].slot == slot then print("found slot") stashData[i].info = metadata end jsonPrint(stashData[i]) end debugPrint("^6Bridge^7: ^3stashEditMetadata^7: ^2Saving ^3QB^2 stash '^6"..stash.."^7'") MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stash, ['items'] = json.encode(stashData) }) end, stashAddItem = function(stashItems, stashName, items) -- end, stashRemoveItem = function(stashItems, stashName, items) if checkExportExists(QBInv, "RemoveItem") then for k, v in pairs(items) do exports[QBInv]:RemoveItem(stashName[1], k, v, false, 'crafting') debugPrint("^6Bridge^7: ^2Removing ^3"..QBInv.." ^2Stash item^7:", k, v) end else if not stashItems or not next(stashItems) then stashItems = getStash(stashName[1]) end for k, v in pairs(items) do for l in pairs(stashItems) do if stashItems[l].name == k then if (stashItems[l].amount - v) <= 0 then stashItems[l] = nil else debugPrint("^6Bridge^7: ^2Removing ^3"..QBInv.." ^2Stash item^7:", k, v) stashItems[l].amount -= v end end end end debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash '^6"..stashName[1].."^7'") MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName[1], ['items'] = json.encode(stashItems) }) end end, registerStash = function(name, label, slots, weight, owner, coords) -- end, }, { invName = PSInv, removeItem = function(src, item, remamount) while remamount > 0 do if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then remamount -= 1 else print("^1Error removing "..item.." Amount left: "..remamount) break end end if Config.Crafting ~= nil and Config.Crafting.showItemBox then TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", amount or 1) end end, addItem = function(src, item, amountToAdd, info, slot) if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then if Config.Crafting ~= nil and Config.Crafting.showItemBox then TriggerClientEvent("ps-inventory:client:ItemBox", src, Items[item], "add", amountToAdd) end end end, setItemMetadata = function(data, src) --debugPrint(src, data.item, 1, data.slot) local Player = Core.Functions.GetPlayer(src) Player.PlayerData.items[data.slot].info = data.metadata if data.metadata.durability then Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability end Player.Functions.SetInventory(Player.PlayerData.items) end, hasItem = function(item, amount, src, invCache) local count = 0 for _, itemData in pairs(invCache) do if itemData and itemData.name == item then count += (itemData.amount or itemData.count or 1) end end return count >= amount, count end, canCarry = function(itemTable, src) local resultTable = {} local items = getPlayerInv(src) local totalWeight = 0 if not items then return false end for _, item in pairs(items) do totalWeight += (item.weight * item.amount) end for k, v in pairs(itemTable) do local itemInfo = Items[k] if not itemInfo then resultTable[k] = true else resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight end end return resultTable end, getMaxInvWeight = function() if checkExportExists(PSInv, "GetMaxWeight") then return exports[PSInv]:GetMaxWeight() else return InventoryWeight end end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += ((v.weight * v.amount) or 0) end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items else grabInv = Core.Functions.GetPlayerData().items end return grabInv end, invImg = function(item) return "nui://"..PSInv.."/html/images/"..(Items[item].image or "") end, openShop = function(name, label, items) TriggerServerEvent(getScript()..':server:openServerShop', name) TriggerServerEvent("inventory:server:OpenInventory", "shop", label, items) end, serverOpenShop = function(shopName) if checkExportExists(PSInv, "OpenShop") then exports[PSInv]:OpenShop(source, shopName) end end, registerShop = function(name, label, items, society) if checkExportExists(PSInv, "CreateShop") then exports[PSInv]:CreateShop({ name = name, label = label, slots = #items, items = items, society = society, }) end end, openStash = function(data) if QBInvNew then TriggerServerEvent(getScript()..':server:openServerStash', { stashName = data.stash, label = data.label, maxweight = data.maxWeight or 600000, slots = data.slots or 40 }) else TriggerEvent("ps-inventory:client:SetCurrentStash", data.stash) TriggerEvent("inventory:client:SetCurrentStash", data.stash) TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, { slots = data.slots or 50, maxWeight = data.maxWeight or 600000 }) end end, clearStash = function(stashId) MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashId, ['items'] = json.encode({}) }) end, getStash = function(stashName) local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName }) if result then return json.decode(result) else return {} end end, stashAddItem = function(stashItems, stashName, items) end, stashRemoveItem = function(stashItems, stashName, items) if not stashItems or not next(stashItems) then stashItems = getStash(stashName[1]) end for k, v in pairs(items) do for l in pairs(stashItems) do if stashItems[l].name == k then if (stashItems[l].amount - v) <= 0 then stashItems[l] = nil else debugPrint("^6Bridge^7: ^2Removing ^3"..PSInv.." ^2Stash item^7:", k, v) stashItems[l].amount -= v end end end end debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3PS^2 stash ^7'^6"..stashName[1].."^7'") MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName[1], ['items'] = json.encode(stashItems) }) end, registerStash = function(name, label, slots, weight, owner, coords) -- end, }, { invName = RSGInv, removeItem = function(src, item, remamount) while remamount > 0 do if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then remamount -= 1 else print("^1Error removing "..item.." Amount left: "..remamount) break end end if Config.Crafting ~= nil and Config.Crafting.showItemBox then TriggerClientEvent("rsg-inventory:client:ItemBox", src, Items[item], "remove", amount or 1) end end, addItem = function(src, item, amountToAdd, info, slot) if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then TriggerClientEvent("rsg-inventory:client:ItemBox", src, Items[item], "add", amountToAdd) end end, setItemMetadata = function(data, src) local Player = Core.Functions.GetPlayer(src) Player.PlayerData.items[data.slot].info = data.metadata if data.metadata.durability then Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability end Player.Functions.SetInventory(Player.PlayerData.items) end, hasItem = function(item, amount, src, invCache) local count = 0 for _, itemData in pairs(invCache) do if itemData and itemData.name == item then count += (itemData.amount or itemData.count or 1) end end return count >= amount, count end, canCarry = function(itemTable, src) local resultTable = {} for k, v in pairs(itemTable) do resultTable[k] = exports[RSGInv]:CanCarryItem(src, k, v) end return resultTable end, getMaxInvWeight = function() if checkExportExists(RSGInv, "GetMaxWeight") then return exports[RSGInv]:GetMaxWeight() else return InventoryWeight end end, getCurrentInvWeight = function(src) local weight = 0 local itemcheck = getPlayerInv(src) for _, v in pairs(itemcheck) do weight += ((v.weight * v.amount) or 0) end return weight end, getPlayerInv = function(src) local grabInv = nil if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items else grabInv = Core.Functions.GetPlayerData().items end return grabInv end, invImg = function(item) return "nui://"..RSGInv.."/html/images/"..(Items[item].image or "") end, openShop = function(name, label, items) TriggerServerEvent(getScript()..':server:openServerShop', name) end, serverOpenShop = function(shopName) exports[RSGInv]:OpenShop(source, shopName) end, registerShop = function(name, label, items, society) exports[RSGInv]:CreateShop({ name = name, label = label, slots = #items, items = items, society = society, }) end, openStash = function(data) TriggerServerEvent(getScript()..':server:openServerStash', { stashName = data.stash, label = data.label, maxweight = data.maxWeight or 600000, slots = data.slots or 40 }) end, clearStash = function(stashId) exports[RSGInv]:ClearStash(stashId) end, getStash = function(stashName) return exports[RSGInv]:GetInventory(stashName) end, stashAddItem = function(stashItems, stashName, items) end, stashRemoveItem = function(stashItems, stashName, items) for k, v in pairs(items) do exports[RSGInv]:RemoveItem(stashName[1], k, v, false, 'crafting') debugPrint("^6Bridge^7: ^2Removing ^3"..RSGInv.." ^2Stash item^7:", k, v) end end, registerStash = function(name, label, slots, weight, owner, coords) -- end, }, } ------------------------------------------------------------- -- Item Availability & Inventory Retrieval ------------------------------------------------------------- --- --- Locks or unlocks the player's inventory. --- Freezes/unfreezes the player's position, sets inventory busy state, and toggles hotbar usage. --- --- @param toggle boolean True to lock inventory; false to unlock. --- --- @usage --- ```lua --- lockInv(true) -- Lock inventory. --- lockInv(false) -- Unlock inventory. --- ``` function lockInv(toggle) FreezeEntityPosition(PlayerPedId(), toggle) LocalPlayer.state:set("inv_busy", toggle, true) LocalPlayer.state:set("invBusy", toggle, true) TriggerEvent('inventory:client:busy:status', toggle) TriggerEvent('canUseInventoryAndHotbar:toggle', not toggle) end --- Checks if a player has the specified items in their inventory. --- --- Verifies whether the required quantities are present. Returns a boolean and a table of details. --- --- @param items string|table A single item name or table with required amounts. --- @param amount number The required quantity (default 1). --- @param src number|nil Player source ID (defaults to caller). --- @return boolean boolean True if all items are available; otherwise, false. --- @return table|nil table Table detailing counts for each item. --- ---@usage --- ```lua --- local hasAll, details = hasItem({"health_potion", "mana_potion"}, 2, playerId) --- if hasAll then --- -- Proceed with action --- else --- -- Inform the player about missing items --- end --- ``` function hasItem(items, amount, src) local amount = amount and amount or 1 local grabInv, foundInv = getPlayerInv(src) if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end local hasTable = {} for item, amt in pairs(items) do if not doesItemExist(item) then print("^4ERROR^7: ^2Script can't find ingredient item in Shared Items - ^1"..item.."^7") end for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then local hasItem, count = inv.hasItem(item, amt, src, grabInv) local foundMessage = "^6Bridge^7: ^3hasItem^7[^6"..foundInv.."^7]: "..tostring(item).." ^3"..count.."^7/^3"..amt if count >= amt then foundMessage = foundMessage.." ^5FOUND^7" else foundMessage = foundMessage .." ^1NOT FOUND^7" end debugPrint(foundMessage) hasTable[item] = { hasItem = hasItem, count = count } break end end end -- Fallback to default hasItem function if not hasTable or not next(hasTable) then if isStarted(ESXExport) then for item, amt in pairs(items) do local count = 0 for itemName, amount in pairs(grabInv) do if itemName and itemName == item then count += (amount or 1) end end hasTable[item] = { hasItem = count >= amt, count = count } end else for item, amt in pairs(items) do local count = 0 for _, itemData in pairs(grabInv) do if itemData and itemData.name == item then count += (itemData.amount or itemData.count or 1) end end hasTable[item] = { hasItem = count >= amt, count = count } end end end for _, v in pairs(hasTable) do if not v.hasItem then return false, hasTable end end return true, hasTable end --- Retrieves a player's inventory based on the active inventory system. --- --- @param src number|nil The player source ID (if nil, retrieves current player's inventory). --- @return table|nil table The inventory items. --- @return string|nil string The name of the inventory system. --- ---@usage --- ```lua --- local inventory, system = getPlayerInv(playerId) --- if inventory then --- -- Process inventory --- end --- ``` function getPlayerInv(src) local grabInv = nil local foundInv = "" for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then foundInv = inv.invName grabInv = inv.getPlayerInv(src) break end end -- Fallback to framework functions if not grabInv then if isStarted(QBExport) then if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items else grabInv = Core.Functions.GetPlayerData().items end elseif isStarted(ESXExport) then if src then local xPlayer = ESX.GetPlayerFromId(src) grabInv = xPlayer and xPlayer.getInventory() or {} else grabInv = triggerCallback(getScript()..":GetESXInv") end end end if grabInv == nil then print("^4ERROR^7: ^2No Supported Inventory detected ^7- ^2Check ^3starter^1.^2lua^7") end return grabInv, foundInv end if isServer() then createCallback(getScript()..":GetESXInv", function(source) local src = source local xPlayer = ESX.GetPlayerFromId(src) local inv = xPlayer.getInventory() return xPlayer.getInventory(src) end) end function isInventoryOpen() return IsNuiFocused() end ------------------------------------------------------------- -- Item Image Retrieval ------------------------------------------------------------- --- Retrieves the NUI link for an item's image from the active inventory system. --- --- @param item string The item name. --- @return string string A `nui://` link to the item's image, or an empty string if not found. --- --- @usage --- ```lua --- local imageLink = invImg("health_potion") --- if imageLink ~= "" then print(imageLink) end --- ``` function invImg(item) local imgLink = "" if item ~= "" and doesItemExist(item) then for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then imgLink = inv.invImg(item) break end end end return imgLink end ------------------------------------------------------------- -- Registering Usable Items ------------------------------------------------------------- --- Registers an item as usable for ESX, QBcore, or QBX. --- --- @param item string The name of the item. --- @param funct function The function to execute when the item is used. --- --- @usage --- ```lua --- createUseableItem("health_potion", function(source) --- -- Code to consume the health potion --- end) --- ``` function createUseableItem(item, funct) if doesItemExist(item) then local useableFunc = { { framework = ESXExport, func = function(item, funct) while not ESX do Wait(0) end ESX.RegisterUsableItem(item, funct) end, }, { framework = QBXExport, func = function(item, funct) exports[QBXExport]:CreateUseableItem(item, funct) end, }, { framework = QBExport, func = function(item, funct) Core.Functions.CreateUseableItem(item, funct) end, }, { framework = RSGExport, func = function(item, funct) Core.Functions.CreateUseableItem(item, funct) end, }, } for i = 1, #useableFunc do local framework = useableFunc[i] if isStarted(framework.framework) then debugPrint("^6Bridge^7: ^2Registering ^3UsableItem^2 with ^4"..framework.framework.."^7:", item) framework.func(item, funct) return end end -- Fallback to framework functiosn if isStarted(QBExport) then debugPrint("^6Bridge^7: ^2Registering ^3UsableItem^2 with ^4"..QBExport.."^7:", item) Core.Functions.CreateUseableItem(item, funct) elseif isStarted(ESXExport) then debugPrint("^6Bridge^7: ^2Registering ^3UsableItem^2 with ^4"..ESXExport.."^7:", item) while not ESX do Wait(0) end ESX.RegisterUsableItem(item, funct) end debugPrint("^4ERROR^7: No supported framework detected for registering usable item: ^3"..item.."^7") else print("^1ERROR^7: ^1Tried to make item usable but it didn't exist^7: "..item) end end ------------------------------------------------------------- -- Adding and Removing Items ------------------------------------------------------------- --- Adds an item to a player's inventory. --- --- Triggers a server event (or local event) to add the specified item. --- --- @param item string The item name. --- @param amount number The quantity to add. --- @param info table|nil Additional metadata for the item. --- @param src number|nil Optional player source; if nil, defaults to the caller. --- --- @usage --- ```lua --- addItem("health_potion", 2, { quality = "high" }) --- ``` function addItem(item, amount, info, src) if not doesItemExist(item) then print("^6Bridge^7: ^1Error^7 - ^2Tried to give ^7'^3"..item.."^7'^2 but it doesn't exist") return end if src then TriggerEvent(getScript()..":server:toggleItem", true, item, amount, src, info) else local timeout = GetGameTimer() + 5000 while currentToken == nil and GetGameTimer() < timeout do Wait(100) end TriggerServerEvent(getScript()..":server:toggleItem", true, item, amount, nil, info, nil, currentToken) currentToken = nil -- clear client cached token end end --- Removes an item from a player's inventory. --- --- Triggers a server event (or local event) to remove the specified item. --- --- @param item string The item name. --- @param amount number The quantity to remove. --- @param src number|nil Optional player source. --- @param slot number|nil Optional inventory slot. --- --- @usage --- ```lua --- removeItem("health_potion", 1) --- ``` function removeItem(item, amount, src, slot) if not doesItemExist(item) then print("^6Bridge^7: ^1Error^7 - ^2Tried to remove ^7'^3"..item.."^7'^2 but it doesn't exist") return end if src then --debugPrint(src) TriggerEvent(getScript()..":server:toggleItem", false, item, amount, src, nil, slot) else TriggerServerEvent(getScript()..":server:toggleItem", false, item, amount, nil, nil, slot) end end ------------------------------------------------------------- -- Toggle Items (Server Event) ------------------------------------------------------------- --- Server event handler to toggle (add or remove) an item from a player's inventory. --- --- This function validates the item, then calls the appropriate export functions based on the active inventory system. --- It also includes exploit protection via the dupeWarn function. --- --- @param give boolean True to add the item, false to remove. --- @param item string The item name. --- @param amount number The quantity. --- @param newsrc number|nil The player source; defaults to event source. --- @param info table|nil Additional metadata. --- @param slot number|nil Optional inventory slot. --- --- @usage --- ```lua --- TriggerServerEvent(getScript()..":server:toggleItem", true, "health_potion", 1) --- ``` RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info, slot, token) local excludeRes = { [QBExport] = true, [ESXExport] = true, [VorpExport] = true, } local invokingRes = GetInvokingResource() --debugPrint(GetInvokingResource()) if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] then debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7") return end if not doesItemExist(item) then print("^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." '^3"..item.."^7' but it doesn't exist") return end local src = (newsrc and tonumber(newsrc)) or source if (give == true or give == 1) then if newsrc == nil then -- this must be coming from client this would be blank if not checkToken(src, token, "item", item) then return end end end local action = (tostring(give) == "true" and "addItem" or "removeItem") local remamount = amount or 1 if item == nil then return end local invName = "" if give == 0 or give == false then if remamount < 0 then print("^1Error^7: ^7src - ^1Client tried to remove a minus number of item ^7- "..item) return end if not hasItem(item, amount or 1, src) then dupeWarn(src, item, amount) else for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then invName = inv.invName inv.removeItem(src, item, remamount) break end end ----- -- Fallback for if no inventory found: ----- if invName == "" then print("^4ERROR^7: No Supported Inventory detected - ^2Falling back to core functions") if isStarted(QBExport) or isStarted(QBXExport) then -- if qbcore or qbxcore, just use core functions invName = isStarted(QBXExport) and QBXExport or isStarted(QBExport) and QBExport Core.Functions.GetPlayer(src).Functions.RemoveItem(item, remamount, slot) elseif ESX and isStarted(ESXExport) then -- if esx then use core functions invName = ESX ESX.GetPlayerFromId(src).removeInventoryItem(item, remamount) end else debugPrint("^6Bridge^7: ^3"..action.."^7[^6"..invName.."^7] Player(^3"..src.."^7) "..getItemLabel(item).."("..item..") x"..(amount or 1)) end end else local amountToAdd = amount or 1 for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then invName = inv.invName inv.addItem(src, item, amountToAdd, info, slot) break end end if invName == "" then print("^4ERROR^7: No Supported Inventory detected - ^2Falling back to core functions") if isStarted(QBExport) or isStarted(QBXExport) then -- if qbcore or qbxcore, just use core functions invName = isStarted(QBXExport) and QBXExport or isStarted(QBExport) and QBExport Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) elseif ESX and isStarted(ESXExport) then -- if esx then use core functions invName = ESX ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd) end else debugPrint("^6Bridge^7: ^3"..action.."^7[^6"..invName.."^7] Player(^3"..src.."^7) "..getItemLabel(item).."("..item..") x"..(amount or 1)) end end end) ------------------------------------------------------------- -- Exploit Protection ------------------------------------------------------------- --- Warns and kicks a player if they try to remove an item they don't have. --- --- @param src number The player's source ID. --- @param item string The item name. --- --- @usage --- ```lua --- dupeWarn(playerId, "health_potion") --- ``` function dupeWarn(src, item, message) local name = getPlayer(src).name print(message or "^5DupeWarn^7: "..name.." (^1"..tostring(src).."^7) ^2Tried to remove item '^3"..item.."^7'^2 but it wasn't there^7") if not debugMode then DropPlayer(src, name.."("..tostring(src)..") kicked by exploit protection") end print("^5DupeWarn^7: "..name.."(^1"..tostring(src).."^7) ^2Dropped from server - exploit protection^7") end ------------------------------------------------------------- -- Tool Durability & Metadata ------------------------------------------------------------- --- Reduces the durability of a tool by a specified damage amount. --- --- If durability reaches zero or below, the tool is removed and a break sound is played. --- --- @param data table Contains: --- - item (string): The tool's name. --- - damage (number): The damage % to apply. --- --- @usage --- ```lua --- breakTool({ item = "drill", damage = 10 }) --- ``` function breakTool(data) local metadata, slot = getItemMetadata(data.item) metadata = metadata or {} if not metadata.durability then metadata.durability = 100 end metadata.durability -= data.damage if metadata.durability <= 0 then removeItem(data.item, 1, nil, slot) local breakId = GetSoundId() PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0) else TriggerServerEvent(getScript()..":server:setItemMetaData", { item = data.item, slot = slot, metadata = metadata }) end end --- Reduces the uses of a tool by a specified damage amount. --- --- If uses reaches zero or below, the tool is removed and a break sound is played. --- --- @param data table Contains: --- - item (string): The tool's name. --- - damage (number): The damage % to apply. --- --- @usage --- ```lua --- useToolDegrade({ item = "drill", maxUse = 10 }) --- ``` function useToolDegrade(data) -- WIP local metadata, slot = getItemMetadata(data.item) metadata = metadata or {} if not metadata["Uses Left"] then metadata["Uses Left"] = data.maxUse end metadata["Uses Left"] -= 1 if metadata["Uses Left"] <= 0 then removeItem(data.item, 1, nil, slot) local breakId = GetSoundId() PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0) else TriggerServerEvent(getScript()..":server:setItemMetaData", { item = data.item, slot = slot, metadata = metadata }) end end -- Grab whole inventory and check for metadata function getItemMetadata(item, slot, src) local lowestSlot = 100 local chosenSlot = slot local metadata = {} local itemcheck = getPlayerInv(src) if chosenSlot then for k, v in pairs(itemcheck) do v.slot = tonumber(v.slot) if v.name == item and v.slot == chosenSlot then lowestSlot = v.slot metadata = itemcheck[k].info or itemcheck[k].metadata or {} debugPrint("^6Bridge^7: ^2Found metadata for item ^3"..item.." ^2in slot ^3"..lowestSlot.."^7") end end else for k, v in pairs(itemcheck) do v.slot = tonumber(v.slot) if v.name == item then if v.slot <= lowestSlot then lowestSlot = v.slot metadata = itemcheck[k].info or itemcheck[k].metadata or {} end end end end return metadata, lowestSlot end --- Server event handler to set metadata for an item. --- --- Updates item metadata (e.g. durability) for the player's inventory based on slot. --- --- @param data table Contains: --- - item (string): The item name. --- - slot (number): The inventory slot. --- - metadata (table): The metadata to set. --- --- @usage --- ```lua --- TriggerServerEvent("script:server:setItemMetaData", { item = "drill", slot = 5, metadata = { durability = 80 } }) --- ``` RegisterNetEvent(getScript()..":server:setItemMetaData", function(data, src) local src = src or source for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then inv.setItemMetadata(data, src) return end end -- Fallback functions if isStarted(QBExport) or isStarted(QBXExport) then -- if qbcore or qbxcore, just use core functions local Player = Core.Functions.GetPlayer(src) Player.PlayerData.items[data.slot].info = data.metadata if data.metadata.durability then Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability end Player.Functions.SetInventory(Player.PlayerData.items) elseif ESX and isStarted(ESXExport) then -- if esx then use core functions --? end end) ------------------------------------------------------------- -- Random Reward ------------------------------------------------------------- --- Grants a random reward from a predefined reward pool if the player is eligible. --- --- Checks if the item qualifies for a reward, removes the item, then calculates a random reward based on rarity. --- --- @param itemName string The item name to check. --- ---@usage --- ```lua --- getRandomReward("gold_ring") --- ``` function getRandomReward(itemName, src) if Config.Rewards.RewardPool then local reward = false if type(Config.Rewards.RewardItem) == "string" then Config.Rewards.RewardItem = { Config.Rewards.RewardItem } end for k, v in pairs(Config.Rewards.RewardItem) do if v == itemName then reward = true break end end if reward then local totalRarity = 0 for i = 1, #Config.Rewards.RewardPool do totalRarity += Config.Rewards.RewardPool[i].rarity end debugPrint("^6Bridge^7: ^3getRandomReward^7: Total Rarity '"..totalRarity.."'") local randomNum = math.random(1, totalRarity) debugPrint("^6Bridge^7: ^3getRandomReward^7: Random Number '"..randomNum.."'") local currentRarity = 0 for i = 1, #Config.Rewards.RewardPool do currentRarity += Config.Rewards.RewardPool[i].rarity if randomNum <= currentRarity then debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Selected toy ^7'^6"..Config.Rewards.RewardPool[i].item.."^7'") addItem(Config.Rewards.RewardPool[i].item, 1, nil, src) return end end end end end ------------------------------------------------------------- -- Carry Capacity Check ------------------------------------------------------------- --- Checks if a player can carry the specified items based on weight. --- --- Calculates the current total weight in the player's inventory and determines whether adding the new items would exceed capacity. --- --- @param itemTable table A table where keys are item names and values are required quantities. --- @param src number The player's source ID. --- @return table A table mapping each item to a boolean indicating if it can be carried. --- --- @usage --- local carryCheck = canCarry({ ["health_potion"] = 2, ["mana_potion"] = 3 }, playerId) --- if carryCheck["health_potion"] and carryCheck["mana_potion"] then --- -- Player can carry items. --- else --- -- Notify player. --- end function canCarry(itemTable, src) local resultTable = {} for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then return inv.canCarry(itemTable, src) end end -- Fallback to default canCarry function local items = getPlayerInv(src) local totalWeight = 0 if not items then return false end for _, item in pairs(items) do totalWeight += (item.weight * item.amount) end for k, v in pairs(itemTable) do local itemInfo = Items[k] if not itemInfo then resultTable[k] = true else resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight end end return resultTable end ------------------------------------------------------------- -- Server Callback Registration ------------------------------------------------------------- if isServer() then createCallback(getScript()..":server:canCarry", function(source, itemTable) local result = canCarry(itemTable, source) return result end) createCallback(getScript()..":server:getMaxCarryCraft", function(source, data) local src = source local item = data.item local max = data.max or 100 local maxCanCarry = 0 for i = 1, max do local checkTable = { [item] = i } local result = canCarry(checkTable, src) if result[item] == true then maxCanCarry = i else break end end return maxCanCarry end) end --- Attempts to return the number value of the max inventory weight --- --- @param src number (optional) can be run in server or client. --- --- @usage --- ```lua --- local maxInvWeight = getMaxInvWeight() --- print("Max Inventory Weight:", maxInvWeight) --- ``` function getMaxInvWeight() local weight = 0 for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then weight = inv.getMaxInvWeight() break end end -- Fallback function if weight == 0 then weight = InventoryWeight end return weight end --- Returns current number value of the players inventory weight --- --- @param src number (optional) can be run in server or client. --- --- @usage --- ```lua --- local currentInvWeight = getCurrentInvWeight() --- print("Current Player Inventory Weight:", currentInvWeight) --- ``` function getCurrentInvWeight(src) local weight = 0 for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then weight = inv.getCurrentInvWeight(src) break end end -- fallback function if weight == 0 then local itemcheck = getPlayerInv(src) if isStarted(ESXExport) then for _, amount in pairs(itemcheck) do weight += (amount or 0) end else for _, v in pairs(itemcheck) do weight += ((v.weight * (v.amount or v.count)) or 0) end end end return weight end --- Retruns if the player has enough inventory slots free --- --- @param slots number The number of free slots. --- @param src number (optional) can be run in server or client. --- --- @usage --- ```lua --- local slots = 5 --- if hasFreeInventorySlots(slots, src) then --- print("Player has enough slots free!") --- end --- ``` function hasFreeInventorySlots(slots, src) local inv = getPlayerInv(src) if (countTable(inv) + slots) <= InventorySlots then return true end return false end ------------------------------------------------------------- -- Check Item Existance - return boolean(true,false) ------------------------------------------------------------- --- checks whether an item exists in your item database --- --- @param item string The item name. --- --- @usage --- ```lua --- local item = "apple" --- if doesItemExist(item) then print("item exists") end --- ``` function doesItemExist(item) local item = type(item) == "string" and item or tostring(item) if not item or item == "" then return false end if not Items or not next(Items) then return item.." (Missing)" end if Items[item] ~= nil then return Items[item] end return false end --- Attempts to return the item "label" --- If can't be found returns "item (Missing item error)" --- --- @param item string The item name. --- --- @usage --- ```lua --- local label = getItemLabel("apple") --- print("label") --- ``` function getItemLabel(item) local item = type(item) == "string" and item or tostring(item) if not item or item == "" then return "" end if not Items or not next(Items) then return item.." (Missing item error)" end if Items[item] ~= nil then return Items[item].label end return item.." (Missing item error)" end --[[ Stash Management Module ------------------------- This module handles stash-related operations including: • Retrieving stash items (from server or local cache). • Checking for required items in stashes. • Opening stashes using different inventory systems. • Removing items from stashes. • Checking if a stash has specific items. ]] -- Global variable to hold the current stash (used in callbacks). local stash -- If running on the server, create a callback to retrieve stash items. if isServer() then createCallback(getScript()..':server:GetStashItems', function(source, stashName) if stashName == nil or stashName == "" then return {} end stash = getStash(stashName) return stash end) end -- Local cache for stashes. local stashCache = {} --- Retrieves (or updates) a local stash cache entry with a timeout. --- When the cache is empty or expired, it triggers a server callback to update the items. --- --- @param stashName string The name of the stash. --- @param stop boolean (Optional) If true, clears the entire stash cache. --- @return boolean True if items exist in cache (and recheck is skipped), false otherwise. --- --- @usage --- ```lua --- local cached = GetStashTimeout("playerStash") --- ``` function GetStashTimeout(stashName, stop) if stop or (stashName == nil or stashName == "") then stashCache = {} return false end -- Retrieve cache for this stash, or initialize if not present. stash = stashCache[stashName] if not stash then debugPrint("^6Bridge^7: ^2Local Stash ^7'^3"..stashName.."^7'^2 cache ^1not ^2found^7, ^2need to grab from server^7") stashCache[stashName] = { items = {}, timeout = 0 } stash = stashCache[stashName] else debugPrint("^6Bridge^7: ^2Local Stash for ^7'^3"..stashName.."^7'^2 cache found^7") end -- If there are already items in cache, skip recheck. if countTable(stashCache[stashName].items) > 0 then debugPrint("^6Bridge^7: '^3"..stashName.."^7' ^2Items found in local cache, skipping server recheck") return true end -- If timeout has expired, update the stash items from the server. if stashCache[stashName].timeout <= 0 then stashCache[stashName].items = triggerCallback(getScript()..':server:GetStashItems', stashName) stashCache[stashName].timeout = 10000 -- Timeout in milliseconds. CreateThread(function() while stashCache[stashName] and stashCache[stashName].timeout > 0 do stashCache[stashName].timeout -= 1000 Wait(1000) end debugPrint("^6Bridge^7: ^2Local Stash ^7'^3"..stashName.."^7'^2 cache timed out^7, ^3Clearing^7") stashCache[stashName] = nil end) end return false end --- Checks if the specified stashes have the required items. --- --- If multiple stashes are provided (as a table), it iterates over each until all required items are found. --- --- @param stashes string|table Either a single stash name or a table of stash names. --- @param itemTable table A table where keys are item names and values are the required amounts. --- @return boolean, string|nil `boolean, string` true and the stash name if found, otherwise false and nil. --- --- @usage --- ```lua --- local found, stashName = checkStashItem({"playerStash", "storageStash"}, { iron = 2, wood = 5 }) --- ``` function checkStashItem(stashes, itemTable) if not stashes or stashes == "" then return hasItem(itemTable), nil end if type(stashes) == "table" then debugPrint("^6Bridge^7: ^2Checking multiple stashes for ingredients^7") -- Iterate over each provided stash name. for _, name in pairs(stashes) do GetStashTimeout(name) if stashhasItem(stashCache[name].items, itemTable, nil) then return true, name end end else debugPrint("^6Bridge^7: ^2Checking "..(stashes and " '^3"..stashes.."^7'" or "").." ingredients") GetStashTimeout(stashes) return stashhasItem(stashCache[stashes].items, itemTable), stashes end return false, nil end ------------------------------------------------------------- -- Stash Opening Functions ------------------------------------------------------------- --- local stashExploitCheck = {} if isServer() then createCallback(getScript()..":getRegisteredStashLocations", function(src, stashName) return stashExploitCheck[stashName] end) end --- Opens a stash using the active inventory system. --- --- Checks for job or gang restrictions before opening the stash. --- --- @param data table A table containing stash data: --- - stash (string): The stash identifier. --- - label (string): Display label. --- - maxWeight (number|nil): Maximum weight (default 600000). --- - slots (number|nil): Number of slots (default 40). --- - stashOptions (table|nil): Additional options for the stash. --- - job/gang (string|nil): Restriction for access. --- - coords (vector3): Coordinates to "look" at. --- --- @usage --- ```lua --- openStash({ stash = "playerStash", label = "Player Stash", coords = vector3(100, 200, 30) }) --- ``` function openStash(data) if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end local exploitCheck = triggerCallback(getScript()..":getRegisteredStashLocations", data.stash) if not exploitCheck then print("^3Warning^7: ^2This isn't a registered stash^1, refusing call^7") return end if not distExploitCheck(exploitCheck) then return end for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then inv.openStash(data) lookEnt(data.coords) return end end --Fallback to these commands TriggerEvent("inventory:client:SetCurrentStash", data.stash) TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, { slots = data.slots or 50, maxWeight = data.maxWeight or 600000 }) lookEnt(data.coords) end function stashEditMetadata(stash, slot, metadata) for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then inv.stashEditMetadata(stash, slot, metadata) return end end end -- Wrapper function for opening stash from the server. -- Messy but not much else I can do about it. RegisterNetEvent(getScript()..":server:openServerStash", function(data) local src = source if not stashExploitCheck[data.stashName] then print("^3Warning^7: ^1Source^7: ^3"..src.." ^1Tried to open a shop^7: ^2This isn't a registered stash^1, refusing call^7") return end if not distExploitCheck(stashExploitCheck[data.stashName], src) then return end if isStarted(TgiannInv) then exports[TgiannInv]:OpenInventory(source, 'stash', data.stashName, data) elseif isStarted(JPRInv) then exports[JPRInv]:OpenInventory(source, data.stashName, data) elseif isStarted(QBInv) then exports[QBInv]:OpenInventory(source, data.stashName, data) elseif isStarted(PSInv) then exports[PSInv]:OpenInventory(source, data.stashName, data) elseif isStarted(RSGInv) then exports[RSGInv]:OpenInventory(source, data.stashName, data) end end) function clearStash(stashId) for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then debugPrint("^5Bridge^7: ^2Clearing ^3"..inv.invName.."^2 Stash^7:", stashId) inv.clearStash(stashId) return end end end ------------------------------------------------------------- -- Stash Retrieval Function ------------------------------------------------------------- --- Retrieves stash items from the active inventory system. --- --- This function converts the raw stash items into a standardized table using the global Items lookup. --- --- @param stashName string The identifier for the stash. --- @return stashTable table A table of items from the stash. --- --- @usage --- ```lua --- local items = getStash("playerStash") --- ``` function getStash(stashName) if stashName == "" or type(stashName) ~= "string" then print("^6Bridge^7: ^2Stash name was not a string ^3"..stashName.."^7(^3"..type(stashName).."^7)") return {} end local stashItems, items = {}, {} for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then debugPrint("^6Bridge^7: ^2Retrieving ^3"..inv.invName.." ^2Stash^7:", stashName) stashItems = inv.getStash(stashName) goto skip end end -- Fallback to sql checks if no supported inventory found if not stashItems or not next(stashItems) then if isStarted(QBExport) or isStarted(QBXExport) then local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName }) if result then stashItems = json.decode(result) end elseif ESX and isStarted(ESXExport) then -- if esx then use core functions --? end end ::skip:: if stashItems then for _, item in pairs(stashItems) do local itemInfo = Items[item.name:lower()] if itemInfo then local indexNum = #items + 1 -- Fallback index if slot is missing. items[(item.slot or indexNum)] = { name = itemInfo.name or nil, amount = tonumber(item.amount) or tonumber(item.count), info = item.info or "", label = itemInfo.label or nil, description = itemInfo.description or "", weight = itemInfo.weight or nil, type = itemInfo.type or nil, unique = itemInfo.unique or nil, useable = itemInfo.useable or nil, image = itemInfo.image or nil, slot = (item.slot and item.slot) or indexNum, metadata = (item.metadata and item.metadata) or nil, } end end debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for '^6"..stashName.."^7' retrieved") end return items end ------------------------------------------------------------- -- Stash Item Removal Function ------------------------------------------------------------- --- Removes items from a stash using the active inventory system. --- --- Iterates over the provided items and adjusts the stash contents accordingly. --- --- @param stashItems table The current stash items. --- @param stashName string|table The stash identifier (or table of identifiers). --- @param items table A table of items to remove (keys are item names, values are amounts). --- --- @usage --- ```lua --- stashRemoveItem(currentItems, "playerStash", { iron = 2, wood = 5 }) --- ``` function stashRemoveItem(stashItems, stashName, items) if stashName == "" or stashName == nil then print("^1ERROR^7: ^1stashRemoveItem triggered but stashName was empty^7") return end if type(stashName) ~= "table" then stashName = { stashName } end for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then inv.stashRemoveItem(stashItems, stashName, items) return end end -- Fallback to core functions if isStarted(QBExport) then local stashItems = getStash(stashName[1]) for k, v in pairs(items) do for l in pairs(stashItems) do if stashItems[l].name == k then if (stashItems[l].amount - v) <= 0 then stashItems[l] = nil else debugPrint("^6Bridge^7: ^2Removing ^3"..PSInv.." ^2Stash item^7:", k, v) stashItems[l].amount -= v end end end end debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QBCORE^2 stash ^7'^6"..stashName[1].."^7'") MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName[1], ['items'] = json.encode(stashItems) }) elseif isStarted(ESXExport) then --? end print("^4ERROR^7: ^2No supported Inventory detected ^7- ^2Check ^3starter^1.^2lua^7") end RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem) function stashAddItem(stashItems, stashName, items) -- wip end ------------------------------------------------------------- -- Stash Item Availability Check ------------------------------------------------------------- --- Checks whether a stash has the required amount of specific items. --- --- It iterates through the provided items and tallies available quantities. --- --- @param stashItems table The items available in the stash. --- @param items string|table The item name or table of required items (key: item, value: amount). --- @param amount number (Optional) The required amount (if a single item is provided). --- @return boolean, table (`boolean, string`) Returns true (and a table with counts) if all items are available; false otherwise. --- --- @usage --- ```lua --- local hasAll, details = stashhasItem(currentStashItems, { iron = 2, wood = 5 }) --- ``` function stashhasItem(stashItems, items, amount) local invs = { OXInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv, QBInv, PSInv, RSGInv } local foundInv = "" for _, inv in ipairs(invs) do if isStarted(inv) then foundInv = inv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6") break end end -- Ensure items is a table. if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end local hasTable = {} for item, requiredAmount in pairs(items) do local count = 0 for _, itemData in pairs(stashItems) do if itemData and (itemData.name == item) then count += (itemData.amount or 1) end end local debugMsg = string.format("^6Bridge^7: ^3stashHasItem^7[^6%s^7]: %s '%s' ^3%d^7/^3%d^7", foundInv, (count >= requiredAmount and "^5FOUND^7" or "^1NOT FOUND^7"), item, count, requiredAmount) debugPrint(debugMsg) hasTable[item] = { hasItem = (count >= requiredAmount), count = count } end for k, v in pairs(hasTable) do if v.hasItem == false then return false, hasTable end end return true, hasTable end --- Registers a stash with the active inventory system. --- Not all --- --- @param name string Unique stash identifier. --- @param label string Display name for the stash. --- @param slots number|nil (Optional) Number of slots (default 50). --- @param weight number|nil (Optional) Maximum weight (default 4000000). --- @param owner string|nil (Optional) Owner identifier for personal stashes. --- @param coords table|nil (Optional) Coordinates for the stash location. --- @usage --- ```lua --- registerStash( --- "playerStash", --- "Player Stash", --- 100, --- 8000000, --- "player123", --- { x = 100.0, y = 200.0, z = 30.0 } --- ) --- ``` function registerStash(name, label, slots, weight, owner, coords) -- ensure tables exist stashExploitCheck = stashExploitCheck or {} stashExploitCheck[name] = stashExploitCheck[name] or {} for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then inv.registerStash(name, label, slots, weight, owner, coords) debugPrint("^6Bridge^7: ^2Registering ^4"..inv.invName.." ^3Stash^7:", name, "^4Label^7: "..label, coords and "- ^4Coord^7: "..formatCoord(coords) or "NO COORD SET") break end end if coords then stashExploitCheck[name][#stashExploitCheck[name]+1] = coords.xyz else print("^3Warning^7: ^1Stash ^4"..name.." ^1doesn^7'^1t have coords^7, ^1this is exploitable^7, ^1add coords to ^3regiterStash^7()") end end if isServer() then --- Registers an event to create an OX stash from the server. --- When triggered, it calls registerStash with the provided parameters. --- --- @event server:makeOXStash --- @param name string Unique stash identifier. --- @param label string Display name for the stash. --- @param slots number|nil (Optional) Number of slots. --- @param weight number|nil (Optional) Maximum weight. --- @param owner string|nil (Optional) Owner identifier. --- @param coords table|nil (Optional) Stash coordinates. --- @usage --- ```lua --- TriggerEvent(getScript()..":server:makeOXStash", name, label, slots, weight, owner, coords, token) --- ``` RegisterNetEvent(getScript()..":server:makeOXStash", function(name, label, slots, weight, owner, coords, token) local src = source or nil if src then if not checkToken(src, token, "stash", name) then return end end registerStash(name, label, slots, weight, owner, coords) end) end RegisterNetEvent(getScript()..":openGrabBox", function(data, stashData) local Ped = PlayerPedId() local id = data.metadata and data.metadata.id or data.info and data.info.id or "" if isStarted(OXInv) then -- this SHOLD be handled within ox_inventory if set up right return end openStash({ stash = id, coords = GetEntityCoords(Ped), maxWeight = stashData and stashData.maxWeight or 100000, slots = stashData and stashData.slots or 5, }) end) ------------------------------------------------------------- -- Selling Menu and Animation ------------------------------------------------------------- local sellShopExploitCheck = {} if isServer() then createCallback(getScript()..":getRegisteredSellShopLocation", function(src, name) return sellShopExploitCheck[name] or nil end) end --- Opens a selling menu with available items and prices. --- --- @param data table Contains selling menu data: --- - sellTable (`table`) Table with Header and Items (item names and prices). --- - ped (optional) (`number`) Ped entity involved. --- - onBack (optional) (`function`) Callback for returning. --- @usage --- ```lua --- sellMenu({ --- sellTable = { --- Header = "Sell Items", --- Items = { --- ["gold_ring"] = 100, --- ["diamond"] = 500, --- }, --- }, --- ped = pedEntity, --- onBack = function() print("Returning to previous menu") end, --- }) --- ``` function sellMenu(data) local origData = data local exploitCheck = triggerCallback(getScript()..":getRegisteredSellShopLocation", data.name) if not exploitCheck then print("^3Warning^7: ^2This isn't a registered store^1, refusing call^7") return end if not distExploitCheck(exploitCheck) then return end local Menu = {} if data.sellTable.Items then local itemList = {} for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end local _, hasTable = hasItem(itemList) for k, v in pairsByKeys(data.sellTable.Items) do if Items[k] then Menu[#Menu + 1] = { isMenuHeader = not hasTable[k].hasItem, icon = invImg(k), header = getItemLabel(k)..(hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""), txt = (Loc and Loc[Config.Lan]) and Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"] or "Sell ALL at $"..v.." each", onSelect = function() local token = triggerCallback(AuthEvent) sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end }, token) end, } end end else for k, v in pairsByKeys(data.sellTable) do if type(v) == "table" then Menu[#Menu + 1] = { arrow = true, header = k, txt = "Amount of items: "..countTable(v.Items), onSelect = function() v.onBack = function() sellMenu(origData) end v.sellTable = data.sellTable[k] v.name = data.name sellMenu(v) end, } end end end openMenu(Menu, { header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items or data.sellTable), headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items or data.sellTable), canClose = true, onBack = data.onBack, }) end --- Plays the selling animation and processes the sale transaction. --- --- Checks if the player has the item, plays animations, triggers the server event for selling, --- and then calls the onBack callback if provided. --- --- @param data table Contains: --- `- item: The item to sell. --- `- price: Price per item. --- `- ped (optional): Ped entity involved. --- `- onBack (optional): Callback to call on completion. ---@usage --- ```lua --- sellAnim({ --- item = "gold_ring", --- price = 100, --- ped = pedEntity, --- onBack = function() sellMenu(data) end, --- }) --- ``` function sellAnim(data, token) if not hasItem(data.item, 1) then triggerNotify(nil, (Loc[Config.Lan].error["dont_have"] or "You don't have any") .." "..getItemLabel(data.item), "error") return end -- Remove any attached clipboard objects. for _, obj in pairs(GetGamePool('CObject')) do for _, model in pairs({ `p_cs_clipboard` }) do if GetEntityModel(obj) == model and IsEntityAttachedToEntity(data.ped, obj) then DeleteObject(obj) DetachEntity(obj, 0, 0) SetEntityAsMissionEntity(obj, true, true) Wait(100) DeleteEntity(obj) end end end TriggerServerEvent(getScript()..":Sellitems", data, token) lookEnt(data.ped) local dict = "mp_common" playAnim(dict, "givetake2_a", 0.3, 48) playAnim(dict, "givetake2_b", 0.3, 48, data.ped) Wait(2000) StopAnimTask(PlayerPedId(), dict, "givetake2_a", 0.5) StopAnimTask(data.ped, dict, "givetake2_b", 0.5) if data.onBack then data.onBack() end end local bannedItems = { money = true, cash = true, bank = true, crypto = true, blackmoney = true, dirty_money = true, markedbills = true } --- Server event handler for processing item sales. --- Removes sold items from inventory and funds the player based on the sale. RegisterNetEvent(getScript()..":Sellitems", function(data, token) local src = source if not checkToken(src, token, "item", "cash") then return end -- Check bannedItems list for k, v in pairs(bannedItems) do if data.item == k then dupeWarn(src, data.item, "^3Source^7: "..src.." ^1Tried to spawn banned item with sell items function") return end end local hasItems, hasTable = hasItem(data.item, 1, src) if hasItems then removeItem(data.item, hasTable[data.item].count, src) print((hasTable[data.item].count * data.price), data.price) fundPlayer((hasTable[data.item].count * data.price), "cash", src) else triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..getItemLabel(data.item), "error", src) end end) function registerSellShop(name, coords) if coords then debugPrint("^6Bridge^7: ^2Registering ^3Sell Store^7: "..name, coords and "- ^4Coord^7: "..formatCoord(coords) or "NO COORD SET") sellShopExploitCheck[name] = sellShopExploitCheck[name] or {} sellShopExploitCheck[name][#sellShopExploitCheck[name]+1] = coords end end ------------------------------------------------------------- -- Shop Interface ------------------------------------------------------------- -- Keep a local cache of all registered shops coords, if they are actually registering in the inventory or not local shopExploitCheck = {} if isServer() then createCallback(getScript()..":getRegisteredShopLocation", function(src, name) return shopExploitCheck[name] or nil end) end --- Opens a shop interface for the player. --- --- Checks job/gang restrictions, then uses the active inventory system to open the shop. --- @param data table Contains: --- - shop (`string`) The shop identifier. --- - items (`table`) The items available in the shop. --- - coords (`vector3`) where the shop is located. --- - job/gang (optional) (`string`) Job or gang requirements. ---@usage --- ```lua --- openShop({ --- shop = "weapon_shop", --- items = weaponShopItems, --- coords = vector3(100.0, 200.0, 300.0), --- job = "police", --- }) --- ``` function openShop(data) if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end -- If shop has registered coords, limit players from being too far away from it when opening local exploitCheck = triggerCallback(getScript()..":getRegisteredShopLocation", data.shop) if not exploitCheck then print("^3Warning^7: ^2This isn't a registered store^1, refusing call^7") return end if not distExploitCheck(exploitCheck) then return end if Config.General.JimShops then TriggerServerEvent("jim-shops:ShopOpen", "shop", data.items.label, data.items) lookEnt(data.coords) return end if not data.items.items[1] then local shopMenu = {} for k, v in pairs(data.items.items) do local clonedTable = cloneTable(data) local itemsTable = v.items or v.Items shopMenu[#shopMenu+1] = { header = v.header or k, txt = countTable(itemsTable).." Products", onSelect = function() --clonedTable.originShop = data.shop clonedTable.shop = data.shop.."_"..k clonedTable.label = data.items.label.." - "..(v.header or k) clonedTable.slots = #itemsTable clonedTable.items.items = itemsTable openShop(clonedTable) end, } end return openMenu(shopMenu, { header = data.items.label, canClose = true, }) end for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then inv.openShop(data.shop, data.items.label, data.items) lookEnt(data.coords) return end end if isStarted(QBExport) then TriggerServerEvent(getScript()..':server:openServerShop', data.shop) TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items) elseif isStarted(ESXExport) then --? end end RegisterNetEvent(getScript()..':server:openServerShop', function(shopName) local src = source -- If shop has registered coords, limit players from being too far away from it when opening if not shopExploitCheck[shopName] then print("^3Warning^7: ^1Source^7: ^3"..src.." ^1Tried to open a shop ^7'"..shopName.."' ^2This isn't a registered store^7, ^1refusing call^7") return end if not distExploitCheck(shopExploitCheck[shopName], src) then return end for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then inv.serverOpenShop(shopName) break end end end) --- Registers a shop with the active inventory system. --- --- @param name string Unique shop identifier. --- @param label string Display name for the shop. --- @param items table List of available shop items. --- @param society string|nil (Optional) Society identifier for shared shops. --- @usage --- ```lua --- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons") --- ``` function registerShop(name, label, items, society, coords) -- ensure tables exist shopExploitCheck = shopExploitCheck or {} shopExploitCheck[name] = shopExploitCheck[name] or {} if isStarted("jim-shops") then -- Add support for new jim-shops registration export if checkExportExists("jim-shops", "registerShop") then exports["jim-shops"]:registerShop(name, label, items, society, coords) end end for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then if not items[1] then for k, v in pairs(items) do inv.registerShop(name.."_"..k, label.." - "..(v.header or k), v.items, society) shopExploitCheck[name.."_"..k] = shopExploitCheck[name.."_"..k] or {} shopExploitCheck[name.."_"..k][#shopExploitCheck[name.."_"..k]+1] = coords debugPrint("^6Bridge^7: ^2Registering ^5"..inv.invName.." ^3Store^7:", name.."_"..k, "^4Label^7: "..label.." - "..(v.header or k), coords and "- ^4Coord^7: "..formatCoord(coords) or "NO COORD SET") end else inv.registerShop(name, label, items, society) debugPrint("^6Bridge^7: ^2Registering ^5"..inv.invName.." ^3Store^7:", name, "^4Label^7: "..label, coords and "- ^4Coord^7: "..formatCoord(coords) or "NO COORD SET") end break end end -- If received coords, pass them to distance check cache if coords then shopExploitCheck[name][#shopExploitCheck[name]+1] = coords else print("^3Warning^7: ^1Store ^4"..name.." ^1doesn^7'^1t have coords^7, ^1this is exploitable^7, ^1add coords to ^3regiterShop^7()") end end