Skip to content
This repository was archived by the owner on Apr 28, 2026. It is now read-only.

Commit 4d178a5

Browse files
authored
fix(server): improve count validation and extra type checks (#23)
1 parent 895cca3 commit 4d178a5

2 files changed

Lines changed: 108 additions & 87 deletions

File tree

modules/inventory/server.lua

Lines changed: 94 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -920,24 +920,29 @@ end
920920
---@param item table | string
921921
---@param count number
922922
---@param metadata? table
923+
---@return boolean? success, string|SlotWithItem|nil response
923924
function Inventory.SetItem(inv, item, count, metadata)
924925
if type(item) ~= 'table' then item = Items(item) end
925926

926-
if item and count >= 0 then
927-
inv = Inventory(inv) --[[@as OxInventory]]
927+
if not item then return false, 'invalid_item' end
928+
if type(count) ~= 'number' then return false, 'invalid_count' end
928929

929-
if inv then
930-
inv.changed = true
931-
local itemCount = Inventory.GetItem(inv, item.name, metadata, true) --[[@as number]]
932-
933-
if count > itemCount then
934-
count -= itemCount
935-
return Inventory.AddItem(inv, item.name, count, metadata)
936-
elseif count <= itemCount then
937-
itemCount -= count
938-
return Inventory.RemoveItem(inv, item.name, itemCount, metadata)
939-
end
940-
end
930+
count = math.floor(count + 0.5)
931+
if count < 0 then return false, 'negative_count' end
932+
933+
inv = Inventory(inv) --[[@as OxInventory]]
934+
935+
if not inv then return false, 'invalid_inventory' end
936+
937+
inv.changed = true
938+
local itemCount = Inventory.GetItem(inv, item.name, metadata, true) --[[@as number]]
939+
940+
if count > itemCount then
941+
count -= itemCount
942+
return Inventory.AddItem(inv, item.name, count, metadata)
943+
elseif count < itemCount then
944+
itemCount -= count
945+
return Inventory.RemoveItem(inv, item.name, itemCount, metadata)
941946
end
942947
end
943948
exports('SetItem', Inventory.SetItem)
@@ -962,8 +967,10 @@ exports('GetCurrentWeapon', Inventory.GetCurrentWeapon)
962967
---@param slotId number
963968
---@return table? item
964969
function Inventory.GetSlot(inv, slotId)
970+
if not inv or type(slotId) ~= 'number' then return end
971+
965972
inv = Inventory(inv) --[[@as OxInventory]]
966-
local slot = inv and inv.items[slotId]
973+
local slot = inv and inv.items?[slotId]
967974

968975
if slot and not Items.UpdateDurability(inv, slot, Items(slot.name), nil, os.time()) then
969976
return slot
@@ -973,9 +980,12 @@ exports('GetSlot', Inventory.GetSlot)
973980

974981
---@param inv inventory
975982
---@param slotId number
983+
---@param durability number
976984
function Inventory.SetDurability(inv, slotId, durability)
985+
if not inv or type(slotId) ~= 'number' or type(durability) ~= 'number' then return end
986+
977987
inv = Inventory(inv) --[[@as OxInventory]]
978-
local slot = inv and inv.items[slotId]
988+
local slot = inv and inv.items?[slotId]
979989

980990
if not slot then return end
981991

@@ -993,8 +1003,10 @@ local Utils = require 'modules.utils.server'
9931003
---@param slotId number
9941004
---@param metadata { [string]: any }
9951005
function Inventory.SetMetadata(inv, slotId, metadata)
1006+
if not inv or type(slotId) ~= 'number' then return end
1007+
9961008
inv = Inventory(inv) --[[@as OxInventory]]
997-
local slot = inv and inv.items[slotId]
1009+
local slot = inv and inv.items?[slotId]
9981010

9991011
if not slot then return end
10001012

@@ -1094,14 +1106,18 @@ function Inventory.AddItem(inv, item, count, metadata, slot, cb)
10941106
if type(item) ~= 'table' then item = Items(item) end
10951107

10961108
if not item then return false, 'invalid_item' end
1109+
if type(count) ~= 'number' then return false, 'invalid_count' end
1110+
1111+
count = math.floor(count + 0.5)
1112+
if count <= 0 then return false, 'negative_count' end
10971113

10981114
inv = Inventory(inv) --[[@as OxInventory]]
10991115

11001116
if not inv?.slots then return false, 'invalid_inventory' end
11011117

11021118
local toSlot, slotMetadata, slotCount
11031119
local success, response = false
1104-
count = math.floor(count + 0.5)
1120+
11051121
metadata = assertMetadata(metadata)
11061122

11071123
if slot then
@@ -1300,83 +1316,83 @@ function Inventory.RemoveItem(inv, item, count, metadata, slot, ignoreTotal, str
13001316
if type(item) ~= 'table' then item = Items(item) end
13011317

13021318
if not item then return false, 'invalid_item' end
1319+
if type(count) ~= 'number' then return false, 'invalid_count' end
13031320

13041321
count = math.floor(count + 0.5)
1322+
if count <= 0 then return false, 'negative_count' end
13051323

1306-
if count > 0 then
1307-
inv = Inventory(inv) --[[@as OxInventory]]
1324+
inv = Inventory(inv) --[[@as OxInventory]]
1325+
1326+
if not inv?.slots then return false, 'invalid_inventory' end
13081327

1309-
if not inv?.slots then return false, 'invalid_inventory' end
1328+
metadata = assertMetadata(metadata)
1329+
if strict == nil then strict = true end
1330+
local itemSlots, totalCount = Inventory.GetItemSlots(inv, item, metadata, strict)
13101331

1311-
metadata = assertMetadata(metadata)
1312-
if strict == nil then strict = true end
1313-
local itemSlots, totalCount = Inventory.GetItemSlots(inv, item, metadata, strict)
1332+
if not itemSlots then return false end
13141333

1315-
if not itemSlots then return false end
1334+
if totalCount and count > totalCount then
1335+
if not ignoreTotal then return false, 'not_enough_items' end
13161336

1317-
if totalCount and count > totalCount then
1318-
if not ignoreTotal then return false, 'not_enough_items' end
1337+
count = totalCount
1338+
end
13191339

1320-
count = totalCount
1321-
end
1340+
local removed, total, slots = 0, count, {}
13221341

1323-
local removed, total, slots = 0, count, {}
1324-
1325-
if slot and itemSlots[slot] then
1326-
removed = count
1327-
Inventory.SetSlot(inv, item, -count, inv.items[slot].metadata, slot)
1328-
slots[#slots+1] = inv.items[slot] or slot
1329-
elseif itemSlots and totalCount > 0 then
1330-
for k, v in pairs(itemSlots) do
1331-
if removed < total then
1332-
if v == count then
1333-
TriggerClientEvent('ox_inventory:itemNotify', inv.id, { inv.items[k], 'ui_removed', v })
1334-
1335-
removed = total
1336-
inv.weight -= inv.items[k].weight
1337-
inv.items[k] = nil
1338-
slots[#slots+1] = inv.items[k] or k
1339-
elseif v > count then
1340-
Inventory.SetSlot(inv, item, -count, inv.items[k].metadata, k)
1341-
slots[#slots+1] = inv.items[k] or k
1342-
removed = total
1343-
count = v - count
1344-
else
1345-
TriggerClientEvent('ox_inventory:itemNotify', inv.id, { inv.items[k], 'ui_removed', v })
1346-
1347-
removed = removed + v
1348-
count = count - v
1349-
inv.weight -= inv.items[k].weight
1350-
inv.items[k] = nil
1351-
slots[#slots+1] = k
1352-
end
1353-
else break end
1354-
end
1355-
end
1342+
if slot and itemSlots[slot] then
1343+
removed = count
1344+
Inventory.SetSlot(inv, item, -count, inv.items[slot].metadata, slot)
1345+
slots[#slots+1] = inv.items[slot] or slot
1346+
elseif itemSlots and totalCount > 0 then
1347+
for k, v in pairs(itemSlots) do
1348+
if removed < total then
1349+
if v == count then
1350+
TriggerClientEvent('ox_inventory:itemNotify', inv.id, { inv.items[k], 'ui_removed', v })
13561351

1357-
if removed > 0 then
1358-
inv.changed = true
1352+
removed = total
1353+
inv.weight -= inv.items[k].weight
1354+
inv.items[k] = nil
1355+
slots[#slots+1] = inv.items[k] or k
1356+
elseif v > count then
1357+
Inventory.SetSlot(inv, item, -count, inv.items[k].metadata, k)
1358+
slots[#slots+1] = inv.items[k] or k
1359+
removed = total
1360+
count = v - count
1361+
else
1362+
TriggerClientEvent('ox_inventory:itemNotify', inv.id, { inv.items[k], 'ui_removed', v })
13591363

1360-
if inv.player and server.syncInventory then
1361-
server.syncInventory(inv)
1362-
end
1364+
removed = removed + v
1365+
count = count - v
1366+
inv.weight -= inv.items[k].weight
1367+
inv.items[k] = nil
1368+
slots[#slots+1] = k
1369+
end
1370+
else break end
1371+
end
1372+
end
13631373

1364-
local array = table.create(#slots, 0)
1374+
if removed > 0 then
1375+
inv.changed = true
13651376

1366-
for k, v in pairs(slots) do
1367-
array[k] = {item = type(v) == 'number' and { slot = v } or v, inventory = inv.id}
1368-
end
1377+
if inv.player and server.syncInventory then
1378+
server.syncInventory(inv)
1379+
end
1380+
1381+
local array = table.create(#slots, 0)
13691382

1370-
inv:syncSlotsWithClients(array, true)
1383+
for k, v in pairs(slots) do
1384+
array[k] = {item = type(v) == 'number' and { slot = v } or v, inventory = inv.id}
1385+
end
13711386

1372-
local invokingResource = server.loglevel > 1 and GetInvokingResource()
1387+
inv:syncSlotsWithClients(array, true)
13731388

1374-
if invokingResource then
1375-
lib.logger(inv.owner, 'removeItem', ('"%s" removed %sx %s from "%s"'):format(invokingResource, removed, item.name, inv.label))
1376-
end
1389+
local invokingResource = server.loglevel > 1 and GetInvokingResource()
13771390

1378-
return true
1391+
if invokingResource then
1392+
lib.logger(inv.owner, 'removeItem', ('"%s" removed %sx %s from "%s"'):format(invokingResource, removed, item.name, inv.label))
13791393
end
1394+
1395+
return true
13801396
end
13811397

13821398
return false, 'not_enough_items'
@@ -2415,7 +2431,7 @@ local function giveItem(playerId, slot, target, count)
24152431

24162432
if not fromInventory or not toInventory then return end
24172433

2418-
if count <= 0 then count = 1 end
2434+
if type(count) ~= 'number' or count <= 0 then count = 1 end
24192435

24202436
if toInventory.player then
24212437
local data = fromInventory.items[slot]

server.lua

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,8 @@ lib.addCommand({'additem', 'giveitem'}, {
561561

562562
if item then
563563
local inventory = Inventory(args.target) --[[@as OxInventory]]
564-
local count = args.count or 1
564+
local count = args.count and math.max(args.count, 1) or 1
565+
565566
local success, response = Inventory.AddItem(inventory, item.name, count, args.type and { type = tonumber(args.type) or args.type })
566567

567568
if not success then
@@ -581,25 +582,27 @@ lib.addCommand('removeitem', {
581582
params = {
582583
{ name = 'target', type = 'playerId', help = 'The player to remove the item from' },
583584
{ name = 'item', type = 'string', help = 'The name of the item' },
584-
{ name = 'count', type = 'number', help = 'The amount of the item to take' },
585+
{ name = 'count', type = 'number', help = 'The amount of the item to take', optional = true },
585586
{ name = 'type', help = 'Only remove items with a matching metadata "type"', optional = true },
586587
},
587588
restricted = 'group.admin',
588589
}, function(source, args)
589590
local item = Items(args.item)
590591

591-
if item and args.count > 0 then
592+
if item then
592593
local inventory = Inventory(args.target) --[[@as OxInventory]]
593-
local success, response = Inventory.RemoveItem(inventory, item.name, args.count, args.type and { type = tonumber(args.type) or args.type }, nil, true)
594+
local count = args.count and math.max(args.count, 1) or 1
595+
596+
local success, response = Inventory.RemoveItem(inventory, item.name, count, args.type and { type = tonumber(args.type) or args.type }, nil, true)
594597

595598
if not success then
596-
return Citizen.Trace(('Failed to remove %sx %s from player %s (%s)'):format(args.count, item.name, args.target, response))
599+
return Citizen.Trace(('Failed to remove %sx %s from player %s (%s)'):format(count, item.name, args.target, response))
597600
end
598601

599602
source = Inventory(source) or {label = 'console', owner = 'console'}
600603

601604
if server.loglevel > 0 then
602-
lib.logger(source.owner, 'admin', ('"%s" removed %sx %s from "%s"'):format(source.label, args.count, item.name, inventory.label))
605+
lib.logger(source.owner, 'admin', ('"%s" removed %sx %s from "%s"'):format(source.label, count, item.name, inventory.label))
603606
end
604607
end
605608
end)
@@ -618,16 +621,18 @@ lib.addCommand('setitem', {
618621

619622
if item then
620623
local inventory = Inventory(args.target) --[[@as OxInventory]]
621-
local success, response = Inventory.SetItem(inventory, item.name, args.count or 0, args.type and { type = tonumber(args.type) or args.type })
624+
local count = args.count and math.max(args.count, 0) or 0
625+
626+
local success, response = Inventory.SetItem(inventory, item.name, count or 0, args.type and { type = tonumber(args.type) or args.type })
622627

623628
if not success then
624-
return Citizen.Trace(('Failed to set %s count to %sx for player %s (%s)'):format(item.name, args.count, args.target, response))
629+
return Citizen.Trace(('Failed to set %s count to %sx for player %s (%s)'):format(item.name, count, args.target, response))
625630
end
626631

627632
source = Inventory(source) or {label = 'console', owner = 'console'}
628633

629634
if server.loglevel > 0 then
630-
lib.logger(source.owner, 'admin', ('"%s" set "%s" %s count to %sx'):format(source.label, inventory.label, item.name, args.count))
635+
lib.logger(source.owner, 'admin', ('"%s" set "%s" %s count to %sx'):format(source.label, inventory.label, item.name, count))
631636
end
632637
end
633638
end)

0 commit comments

Comments
 (0)