Files
jim_bridge/shared/itemcontrol.lua

2715 lines
100 KiB
Lua
Raw Normal View History

--[[
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.
]]
2025-03-26 21:43:48 +00:00
validTokens = {}
-- Function Compatability Table
local InvFunc = {
2025-08-14 13:07:15 +01:00
{ 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,
2025-08-14 13:07:15 +01:00
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 })
2025-08-14 13:07:15 +01:00
end,
serverOpenShop = function(shopName)
--
end,
2025-08-14 13:07:15 +01:00
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,
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
2025-08-14 13:07:15 +01:00
end,
registerStash =
function(name, label, slots, weight, owner, coords)
exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil)
end,
},
2025-08-14 13:07:15 +01:00
{ 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,
2025-08-14 13:07:15 +01:00
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)
2025-08-14 13:07:15 +01:00
--
end,
serverOpenShop =
function(shopName)
--
end,
2025-08-14 13:07:15 +01:00
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,
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)
2025-08-14 13:07:15 +01:00
debugPrint("^6Bridge^7: ^2Removing ^3"..CoreInv.." ^2Stash item^7:", k, v)
end
end,
registerStash =
function(name, label, slots, weight, owner, coords)
--
end,
},
2025-08-14 13:07:15 +01:00
{ 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,
2025-08-14 13:07:15 +01:00
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://"..OrigenInv.."/html/img/"..(Items[item].image or "")
end,
openShop =
function(name, label, items)
2025-08-14 13:07:15 +01:00
--
end,
serverOpenShop =
function(shopName)
--
end,
2025-08-14 13:07:15 +01:00
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,
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)
2025-08-14 13:07:15 +01:00
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,
},
2025-08-14 13:07:15 +01:00
{ 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)
2025-08-14 13:07:15 +01:00
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,
2025-08-14 13:07:15 +01:00
getPlayerInv =
function(src)
local grabInv = nil
if src then
grabInv = exports[CodeMInv]:GetInventory(getPlayer(src).citizenId, src)
else
grabInv = exports[CodeMInv]:GetClientPlayerInventory()
end
2025-08-14 13:07:15 +01:00
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)
2025-08-14 13:07:15 +01:00
end,
serverOpenShop =
function(shopName)
--
end,
2025-08-14 13:07:15 +01:00
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,
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)
2025-08-14 13:07:15 +01:00
end,
registerStash =
function(name, label, slots, weight, owner, coords)
--
end,
},
2025-08-14 13:07:15 +01:00
{ invName = TgiannInv,
removeItem =
function(src, item, remamount)
exports[TgiannInv]:RemoveItem(src, item, remamount)
2025-08-14 13:07:15 +01:00
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
2025-09-04 17:01:30 +01:00
local serverItemCheck = exports[TgiannInv]:GetItemCount(src, item, nil, true) or 0
2025-08-14 13:07:15 +01:00
return serverItemCheck >= amount, serverItemCheck
else
2025-08-14 13:07:15 +01:00
local localItemCheck = exports[TgiannInv]:GetItemCount(item) or 0
return localItemCheck >= amount, localItemCheck
end
2025-08-14 13:07:15 +01:00
end,
canCarry =
function(itemTable, src)
local resultTable = {}
for k, v in pairs(itemTable) do
resultTable[k] = exports[TgiannInv]:CanCarryItem(src, k, v)
end
2025-08-14 13:07:15 +01:00
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,
2025-08-14 13:07:15 +01:00
getPlayerInv =
function(src)
local grabInv = nil
if src then
grabInv = exports[TgiannInv]:GetPlayerItems(src)
else
2025-08-14 13:07:15 +01:00
grabInv = exports[TgiannInv]:GetPlayerItems()
end
2025-08-14 13:07:15 +01:00
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)
2025-08-14 13:07:15 +01:00
end,
serverOpenShop =
function(shopName)
exports[TgiannInv]:OpenShop(source, shopName)
end,
2025-08-14 13:07:15 +01:00
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
})
2025-08-14 13:07:15 +01:00
end,
clearStash =
function(stashId)
exports[TgiannInv]:DeleteInventory("stash", stashId)
end,
getStash =
function(stashName)
return exports[TgiannInv]:GetSecondaryInventoryItems("stash", stashName)
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)
2025-08-14 13:07:15 +01:00
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,
},
2025-08-14 13:07:15 +01:00
{ 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
2025-08-14 13:07:15 +01:00
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end
2025-08-14 13:07:15 +01:00
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
2025-08-14 13:07:15 +01:00
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,
2025-08-14 13:07:15 +01:00
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)
2025-08-14 13:07:15 +01:00
end,
serverOpenShop =
function(shopName)
if checkExportExists(JPRInv, "OpenShop") then
exports[JPRInv]:OpenShop(source, shopName)
end
end,
2025-08-14 13:07:15 +01:00
registerShop =
function(name, label, items, society)
exports[JPRInv]:CreateShop({
name = name,
label = label,
slots = #items,
2025-08-14 13:07:15 +01:00
items = items
})
2025-08-14 13:07:15 +01:00
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({})
})
2025-08-14 13:07:15 +01:00
end,
getStash =
function(stashName)
if checkExportExists(JPRInv, "GetStashItems") then
return exports[JPRInv]:GetStashItems(stashName) or {}
2025-08-14 13:07:15 +01:00
else
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
return result and json.decode(result) or {}
2025-08-14 13:07:15 +01:00
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])
2025-08-14 13:07:15 +01:00
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'")
2025-08-14 13:07:15 +01:00
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName[1],
2025-08-14 13:07:15 +01:00
['items'] = json.encode(stashItems)
})
2025-08-14 13:07:15 +01:00
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
2025-08-14 13:07:15 +01:00
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,
2025-08-14 13:07:15 +01:00
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)
2025-08-14 13:07:15 +01:00
end,
serverOpenShop =
function(shopName)
if checkExportExists(QBInv, "OpenShop") then
exports[QBInv]:OpenShop(source, shopName)
end
end,
2025-08-14 13:07:15 +01:00
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
2025-08-14 13:07:15 +01:00
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,
stashAddItem =
function(stashItems, stashName, items)
--
end,
stashRemoveItem =
function(stashItems, stashName, items)
if checkExportExists(QBInv, "RemoveItem") then
2025-08-14 13:07:15 +01:00
for k, v in pairs(items) do
exports[QBInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
2025-08-14 13:07:15 +01:00
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])
2025-08-14 13:07:15 +01:00
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'")
2025-08-14 13:07:15 +01:00
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName[1],
2025-08-14 13:07:15 +01:00
['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,
2025-08-14 13:07:15 +01:00
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)
2025-08-14 13:07:15 +01:00
end,
serverOpenShop =
function(shopName)
if checkExportExists(PSInv, "OpenShop") then
exports[PSInv]:OpenShop(source, shopName)
end
end,
2025-08-14 13:07:15 +01:00
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({})
})
2025-08-14 13:07:15 +01:00
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
2025-08-14 13:07:15 +01:00
end,
stashAddItem =
function(stashItems, stashName, items)
2025-08-14 13:07:15 +01:00
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
2025-08-14 13:07:15 +01:00
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)
})
2025-08-14 13:07:15 +01:00
end,
registerStash =
function(name, label, slots, weight, owner, coords)
--
end,
},
2025-08-14 13:07:15 +01:00
{ 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
2025-08-14 13:07:15 +01:00
TriggerClientEvent("rsg-inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end
2025-08-14 13:07:15 +01:00
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[OXInv]:CanCarryItem(src, k, v)
end
return resultTable
end,
getMaxInvWeight =
function()
if checkExportExists(RSGInv, "GetMaxWeight") then
return exports[RSGInv]:GetMaxWeight()
else
2025-08-14 13:07:15 +01:00
return InventoryWeight
end
2025-08-14 13:07:15 +01:00
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,
2025-08-14 13:07:15 +01:00
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)
2025-08-14 13:07:15 +01:00
end,
serverOpenShop =
function(shopName)
exports[RSGInv]:OpenShop(source, shopName)
end,
2025-08-14 13:07:15 +01:00
registerShop =
function(name, label, items, society)
exports[RSGInv]:CreateShop({
name = name,
label = label,
slots = #items,
items = items,
society = society,
})
2025-08-14 13:07:15 +01:00
end,
openStash =
function(data)
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
2025-08-14 13:07:15 +01:00
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')
2025-08-14 13:07:15 +01:00
debugPrint("^6Bridge^7: ^2Removing ^3"..RSGInv.." ^2Stash item^7:", k, v)
end
2025-08-14 13:07:15 +01:00
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)
2025-09-24 14:09:19 +01:00
if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
2025-08-14 13:07:15 +01:00
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
2025-08-14 20:08:09 +01:00
for i = 1, #InvFunc do
local inv = InvFunc[i]
2025-08-14 13:07:15 +01:00
if isStarted(inv.invName) then
local hasItem, count = inv.hasItem(item, amt, src, grabInv)
2025-08-14 13:07:15 +01:00
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
2025-08-14 13:07:15 +01:00
end
for _, v in pairs(hasTable) do
2025-08-14 13:07:15 +01:00
if not v.hasItem then
return false, hasTable
end
end
2025-08-14 13:07:15 +01:00
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 = ""
2025-08-14 20:08:09 +01:00
for i = 1, #InvFunc do
local inv = InvFunc[i]
if isStarted(inv.invName) then
foundInv = inv.invName
grabInv = inv.getPlayerInv(src)
break
end
end
if foundInv == "" then
print("^4ERROR^7: ^2No Supported Inventory detected ^7- ^2Check ^3starter^1.^2lua^7")
end
return grabInv, foundInv
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
2025-08-14 20:08:09 +01:00
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
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)
2025-04-07 20:52:59 +01:00
if not Items[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
2025-04-07 20:52:59 +01:00
TriggerServerEvent(getScript()..":server:toggleItem", true, item, amount, nil, info, nil, currentToken)
2025-03-26 21:43:48 +00:00
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 Items[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)
--- ```
2025-04-07 20:52:59 +01:00
RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info, slot, token)
--debugPrint(GetInvokingResource())
2025-04-07 20:52:59 +01:00
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return
end
if not Items[item] then
print("^6Bridge^7: ^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
2025-03-26 21:43:48 +00:00
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
2025-03-26 21:43:48 +00:00
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 not hasItem(item, amount or 1, src) then
dupeWarn(src, item, amount)
else
2025-08-14 20:08:09 +01:00
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
2025-09-28 13:48:17 +01:00
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
2025-08-14 20:08:09 +01:00
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
2025-09-28 13:48:17 +01:00
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
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
if v.name == item then
if v.slot <= lowestSlot then
lowestSlot = v.slot
metadata = itemcheck[k].info or itemcheck[k].metadata or {}
end
2025-04-30 17:53:26 +01:00
end
end
end
return metadata, lowestSlot
end
2025-04-30 17:53:26 +01:00
--- 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
2025-08-14 20:08:09 +01:00
for i = 1, #InvFunc do
local inv = InvFunc[i]
if isStarted(inv.invName) then
inv.setItemMetadata(data, src)
break
end
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")
--- ```
2025-09-13 02:50:31 +01:00
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'")
2025-09-13 02:50:31 +01:00
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 = {}
2025-08-14 20:08:09 +01:00
for i = 1, #InvFunc do
local inv = InvFunc[i]
if isStarted(inv.invName) then
2025-08-14 13:07:15 +01:00
return inv.canCarry(itemTable, src)
end
end
return resultTable
2025-03-26 21:43:48 +00:00
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
2025-08-14 20:08:09 +01:00
for i = 1, #InvFunc do
local inv = InvFunc[i]
if isStarted(inv.invName) then
weight = inv.getMaxInvWeight()
break
end
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
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)
2025-09-13 02:50:31 +01:00
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
2025-08-14 20:08:09 +01:00
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
-- 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)
2025-08-14 20:08:09 +01:00
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)
local stashResource = ""
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 = {}, {}
2025-08-14 20:08:09 +01:00
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)
break
end
end
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
jsonPrint(items)
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
2025-08-14 20:08:09 +01:00
for i = 1, #InvFunc do
local inv = InvFunc[i]
if isStarted(inv.invName) then
inv.stashRemoveItem(stashItems, stashName, items)
return
end
end
print("^4ERROR^7: ^2No supported Inventory detected ^7- ^2Check ^3starter^1.^2lua^7")
end
RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem)
-------------------------------------------------------------
-- 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)
2025-09-06 12:02:55 +01:00
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
2025-08-14 13:07:15 +01:00
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.
2025-09-06 12:02:55 +01:00
--- 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 {}
2025-08-14 20:08:09 +01:00
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)
local Ped = PlayerPedId()
local id = data.metadata and data.metadata.id or data.info and data.info.id or ""
if isStarted(OXInv) then
return
end
openStash({
stash = id,
coords = GetEntityCoords(Ped)
})
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),
2025-08-25 16:34:37 +01:00
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)
--jsonPrint(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
2025-08-14 20:08:09 +01:00
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)
break
end
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: ^2This isn't a registered store^1, refusing call^7")
return
end
if not distExploitCheck(shopExploitCheck[shopName], src) then
return
end
2025-08-14 20:08:09 +01:00
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
2025-08-14 20:08:09 +01:00
for i = 1, #InvFunc do
local inv = InvFunc[i]
if isStarted(inv.invName) then
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")
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