Files
jim_bridge/shared/itemcontrol.lua
Jim Shield 27a0e147fb Add exploit check for removing items
Add a check for if a person is attempting to trigger remove minus of an item (possibly trying to give them selves items)

this will stop it in its tracks and warn the server console what happened
2025-10-24 21:40:26 +01:00

2970 lines
110 KiB
Lua

--[[
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
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
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