From d0bcb3674b3ffc649fa087fb1db23142d0e1354d Mon Sep 17 00:00:00 2001 From: Ihsan <122683406+rwixy@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:17:52 +0530 Subject: [PATCH 1/4] refactor(oxinventory): improve error handling and streamline inventory functions --- .../server/classes/overrides/oxinventory.lua | 319 +++++++++++++----- 1 file changed, 228 insertions(+), 91 deletions(-) diff --git a/[core]/es_extended/server/classes/overrides/oxinventory.lua b/[core]/es_extended/server/classes/overrides/oxinventory.lua index c3be26a8..cdfeaff3 100644 --- a/[core]/es_extended/server/classes/overrides/oxinventory.lua +++ b/[core]/es_extended/server/classes/overrides/oxinventory.lua @@ -1,11 +1,85 @@ -local Inventory +local OxInventory -if Config.CustomInventory ~= "ox" then return end +if Config.CustomInventory ~= "ox" then + return +end +---Stores the reference to the internal ox_inventory module. +---@param module table +---@return boolean +local function setOxInventory(module) + if type(module) ~= "table" then + return false + end + + OxInventory = module + return true +end + +---Returns the internal ox_inventory module. +---If es_extended missed the load event, it will attempt to retrieve it via the export. +---@return table +local function getOxInventory() + if OxInventory then + return OxInventory + end + + local state = GetResourceState("ox_inventory") + + if state ~= "started" then + error( + ("[es_extended] ox_inventory not started; current status: %s") + :format(tostring(state)), + 2 + ) + end + + local success, module = pcall(function() + return exports.ox_inventory:Inventory() + end) + + if not success then + error( + ("[es_extended] Failed to execute exports.ox_inventory:Inventory(): %s") + :format(tostring(module)), + 2 + ) + end + + if not setOxInventory(module) then + error( + "[es_extended] The Inventory export from ox_inventory returned an invalid module.", + 2 + ) + end + + return OxInventory +end + +-- Standard method used when ox_inventory finishes loading.. AddEventHandler("ox_inventory:loadInventory", function(module) - Inventory = module + if not setOxInventory(module) then + print("^1[es_extended] ox_inventory:loadInventory returned an invalid module.^7") + end end) +-- Standard method used when ox_inventory is stopped. +AddEventHandler("onResourceStop", function(resourceName) + if resourceName == "ox_inventory" then + OxInventory = nil + end +end) + +local function emptyMethod() + return function() end +end + +local function falseMethod() + return function() + return false + end +end + Core.PlayerFunctionOverrides.OxInventory = { getInventory = function(self) return function(minimal) @@ -13,26 +87,26 @@ Core.PlayerFunctionOverrides.OxInventory = { return self.inventory end - local minimalInventory = {} + local result = {} - for k, v in pairs(self.inventory) do - if v.count and v.count > 0 then - local metadata = v.metadata + for slot, item in pairs(self.inventory) do + if item.count and item.count > 0 then + local metadata = item.metadata - if v.metadata and next(v.metadata) == nil then + if type(metadata) == "table" and next(metadata) == nil then metadata = nil end - minimalInventory[#minimalInventory + 1] = { - name = v.name, - count = v.count, - slot = k, + result[#result + 1] = { + name = item.name, + count = item.count, + slot = slot, metadata = metadata, } end end - return minimalInventory + return result end end, @@ -45,18 +119,33 @@ Core.PlayerFunctionOverrides.OxInventory = { setAccountMoney = function(self) return function(accountName, money, reason) reason = reason or "unknown" - if money < 0 then return end + + if money < 0 then + return + end + local account = self.getAccount(accountName) - if not account then return end + if not account then + return + end money = account.round and ESX.Math.Round(money) or money self.accounts[account.index].money = money self.triggerEvent("esx:setAccountMoney", account) - TriggerEvent("esx:setAccountMoney", self.source, accountName, money, reason) - if Inventory.accounts[accountName] then - Inventory.SetItem(self.source, accountName, money) + TriggerEvent( + "esx:setAccountMoney", + self.source, + accountName, + money, + reason + ) + + local inventory = getOxInventory() + + if inventory.accounts[accountName] then + inventory.SetItem(self.source, accountName, money) end end end, @@ -64,17 +153,34 @@ Core.PlayerFunctionOverrides.OxInventory = { addAccountMoney = function(self) return function(accountName, money, reason) reason = reason or "unknown" - if money < 1 then return end + + if money < 1 then + return + end local account = self.getAccount(accountName) - if not account then return end + + if not account then + return + end money = account.round and ESX.Math.Round(money) or money - self.accounts[account.index].money = self.accounts[account.index].money + money + self.accounts[account.index].money = + self.accounts[account.index].money + money + self.triggerEvent("esx:setAccountMoney", account) - TriggerEvent("esx:addAccountMoney", self.source, accountName, money, reason) - if Inventory.accounts[accountName] then - Inventory.AddItem(self.source, accountName, money) + TriggerEvent( + "esx:addAccountMoney", + self.source, + accountName, + money, + reason + ) + + local inventory = getOxInventory() + + if inventory.accounts[accountName] then + inventory.AddItem(self.source, accountName, money) end end end, @@ -82,54 +188,103 @@ Core.PlayerFunctionOverrides.OxInventory = { removeAccountMoney = function(self) return function(accountName, money, reason) reason = reason or "unknown" - if money < 1 then return end + + if money < 1 then + return + end local account = self.getAccount(accountName) - if not account then return end + + if not account then + return + end money = account.round and ESX.Math.Round(money) or money - self.accounts[account.index].money = self.accounts[account.index].money - money + self.accounts[account.index].money = + self.accounts[account.index].money - money + self.triggerEvent("esx:setAccountMoney", account) - TriggerEvent("esx:removeAccountMoney", self.source, accountName, money, reason) - if Inventory.accounts[accountName] then - Inventory.RemoveItem(self.source, accountName, money) + TriggerEvent( + "esx:removeAccountMoney", + self.source, + accountName, + money, + reason + ) + + local inventory = getOxInventory() + + if inventory.accounts[accountName] then + inventory.RemoveItem(self.source, accountName, money) end end end, getInventoryItem = function(self) return function(name, metadata) - return Inventory.GetItem(self.source, name, metadata) + return getOxInventory().GetItem( + self.source, + name, + metadata + ) end end, addInventoryItem = function(self) return function(name, count, metadata, slot) - return Inventory.AddItem(self.source, name, count or 1, metadata, slot) + return getOxInventory().AddItem( + self.source, + name, + count or 1, + metadata, + slot + ) end end, removeInventoryItem = function(self) return function(name, count, metadata, slot) - return Inventory.RemoveItem(self.source, name, count or 1, metadata, slot) + return getOxInventory().RemoveItem( + self.source, + name, + count or 1, + metadata, + slot + ) end end, setInventoryItem = function(self) return function(name, count, metadata) - return Inventory.SetItem(self.source, name, count, metadata) + return getOxInventory().SetItem( + self.source, + name, + count, + metadata + ) end end, canCarryItem = function(self) return function(name, count, metadata) - return Inventory.CanCarryItem(self.source, name, count, metadata) + return getOxInventory().CanCarryItem( + self.source, + name, + count, + metadata + ) end end, canSwapItem = function(self) return function(firstItem, firstItemCount, testItem, testItemCount) - return Inventory.CanSwapItem(self.source, firstItem, firstItemCount, testItem, testItemCount) + return getOxInventory().CanSwapItem( + self.source, + firstItem, + firstItemCount, + testItem, + testItemCount + ) end end, @@ -137,83 +292,65 @@ Core.PlayerFunctionOverrides.OxInventory = { return function(newWeight) self.maxWeight = newWeight self.triggerEvent("esx:setMaxWeight", self.maxWeight) - return Inventory.SetMaxWeight(self.source, newWeight) + + return getOxInventory().SetMaxWeight( + self.source, + newWeight + ) end end, - addWeapon = function() - return function() end - end, + addWeapon = emptyMethod, + addWeaponComponent = emptyMethod, + addWeaponAmmo = emptyMethod, + updateWeaponAmmo = emptyMethod, + setWeaponTint = emptyMethod, + getWeaponTint = emptyMethod, + removeWeapon = emptyMethod, + removeWeaponComponent = emptyMethod, + removeWeaponAmmo = emptyMethod, - addWeaponComponent = function() - return function() end - end, - - addWeaponAmmo = function() - return function() end - end, - - updateWeaponAmmo = function() - return function() end - end, - - setWeaponTint = function() - return function() end - end, - - getWeaponTint = function() - return function() end - end, - - removeWeapon = function() - return function() end - end, - - removeWeaponComponent = function() - return function() end - end, - - removeWeaponAmmo = function() - return function() end - end, - - hasWeaponComponent = function() - return function() - return false - end - end, - - hasWeapon = function() - return function() - return false - end - end, + hasWeaponComponent = falseMethod, + hasWeapon = falseMethod, hasItem = function(self) return function(name, metadata) - return Inventory.GetItem(self.source, name, metadata) + return getOxInventory().GetItem( + self.source, + name, + metadata + ) end end, - getWeapon = function() - return function() end - end, + getWeapon = emptyMethod, syncInventory = function(self) return function(weight, maxWeight, items, money) - self.weight, self.maxWeight = weight, maxWeight + self.weight = weight + self.maxWeight = maxWeight self.inventory = items - if not money then return end + if not money then + return + end + for accountName, amount in pairs(money) do local account = self.getAccount(accountName) if account and ESX.Math.Round(account.money) ~= amount then account.money = amount + self.triggerEvent("esx:setAccountMoney", account) - TriggerEvent("esx:setAccountMoney", self.source, accountName, amount, "Sync account with item") + TriggerEvent( + "esx:setAccountMoney", + self.source, + accountName, + amount, + "Sync account with item" + ) end end end end, -} +} \ No newline at end of file From e08eb5079d539d84b39aa2e494954121de3f3095 Mon Sep 17 00:00:00 2001 From: Ihsan <122683406+rwixy@users.noreply.github.com> Date: Thu, 6 Aug 2026 23:43:48 +0530 Subject: [PATCH 2/4] refactor(ox_inventory): validate module methods and fix hasItem return contract --- .../server/classes/overrides/oxinventory.lua | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/[core]/es_extended/server/classes/overrides/oxinventory.lua b/[core]/es_extended/server/classes/overrides/oxinventory.lua index cdfeaff3..328eaefd 100644 --- a/[core]/es_extended/server/classes/overrides/oxinventory.lua +++ b/[core]/es_extended/server/classes/overrides/oxinventory.lua @@ -4,12 +4,31 @@ if Config.CustomInventory ~= "ox" then return end +local requiredMethods = { + "GetItem", + "AddItem", + "RemoveItem", + "SetItem", + "CanCarryItem", + "CanSwapItem", + "SetMaxWeight", +} + ---Stores the reference to the internal ox_inventory module. ---@param module table ---@return boolean +---@return string? error local function setOxInventory(module) if type(module) ~= "table" then - return false + return false, "module is not a table" + end + + for i = 1, #requiredMethods do + local method = requiredMethods[i] + + if type(module[method]) ~= "function" then + return false, ("missing method %s"):format(method) + end end OxInventory = module @@ -46,9 +65,11 @@ local function getOxInventory() ) end - if not setOxInventory(module) then + local ok, err = setOxInventory(module) + if not ok then error( - "[es_extended] The Inventory export from ox_inventory returned an invalid module.", + ("[es_extended] The Inventory export from ox_inventory returned an invalid module: %s") + :format(err), 2 ) end @@ -58,8 +79,9 @@ end -- Standard method used when ox_inventory finishes loading.. AddEventHandler("ox_inventory:loadInventory", function(module) - if not setOxInventory(module) then - print("^1[es_extended] ox_inventory:loadInventory returned an invalid module.^7") + local ok, err = setOxInventory(module) + if not ok then + print(("^1[es_extended] ox_inventory:loadInventory returned an invalid module: %s^7"):format(err)) end end) @@ -315,11 +337,17 @@ Core.PlayerFunctionOverrides.OxInventory = { hasItem = function(self) return function(name, metadata) - return getOxInventory().GetItem( + local item = getOxInventory().GetItem( self.source, name, metadata ) + + if not item or not item.count or item.count < 1 then + return false + end + + return item, item.count end end, From e1164b487bbb28f25c89d8ff5620e61f83dc1273 Mon Sep 17 00:00:00 2001 From: Ihsan <122683406+rwixy@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:42:35 +0530 Subject: [PATCH 3/4] refactor(ox_inventory): enhance account list parsing and improve proxy method validation --- .../server/classes/overrides/oxinventory.lua | 107 ++++++++++++------ 1 file changed, 72 insertions(+), 35 deletions(-) diff --git a/[core]/es_extended/server/classes/overrides/oxinventory.lua b/[core]/es_extended/server/classes/overrides/oxinventory.lua index 328eaefd..757fb015 100644 --- a/[core]/es_extended/server/classes/overrides/oxinventory.lua +++ b/[core]/es_extended/server/classes/overrides/oxinventory.lua @@ -14,29 +14,76 @@ local requiredMethods = { "SetMaxWeight", } ----Stores the reference to the internal ox_inventory module. ----@param module table ----@return boolean ----@return string? error -local function setOxInventory(module) - if type(module) ~= "table" then - return false, "module is not a table" - end +---Parses the inventory:accounts convar to build a lookup table. +---Handles both JSON array format (["money"]) and plain text. +---@return table +local function getAccountList() + local accounts = {} + local convar = GetConvar("inventory:accounts", "[]") + local ok, list = pcall(json.decode, convar) - for i = 1, #requiredMethods do - local method = requiredMethods[i] - - if type(module[method]) ~= "function" then - return false, ("missing method %s"):format(method) + if ok and type(list) == "table" then + for i = 1, #list do + accounts[list[i]] = true + end + else + -- Fallback for non-JSON formats (e.g. "money" or "money black_money") + for account in convar:gmatch("[%w_]+") do + if account ~= "[]" then + accounts[account] = true + end + end + end + + return accounts +end + +---Creates a proxy table that routes method calls to ox_inventory exports. +---@return table +local function createOxInventoryProxy() + local proxy = {} + local accounts = getAccountList() + + -- Validate that the exports actually exist before wrapping them + for i = 1, #requiredMethods do + local method = requiredMethods[i] + if exports.ox_inventory[method] == nil then + print(("^3[es_extended] WARNING: exports.ox_inventory.%s is missing — ox_inventory integration may be broken^7"):format(method)) + end + + proxy[method] = function(...) + return exports.ox_inventory[method](exports.ox_inventory, ...) + end + end + + if not OxInventory then + print("^2[es_extended] ox_inventory: using direct export proxy (Inventory() module unavailable)^7") + end + + proxy.accounts = accounts + return proxy +end + +---Checks if a module table contains all required methods. +---@param module table +---@return boolean +local function isValidModule(module) + if type(module) ~= "table" then + return false + end + + for i = 1, #requiredMethods do + if type(module[requiredMethods[i]]) ~= "function" then + return false end end - OxInventory = module return true end ----Returns the internal ox_inventory module. ----If es_extended missed the load event, it will attempt to retrieve it via the export. +---Returns the ox_inventory interface. +---Tries the legacy Inventory() export first for backward compatibility, +---then falls back to direct export calls. ---@return table local function getOxInventory() if OxInventory then @@ -57,31 +104,21 @@ local function getOxInventory() return exports.ox_inventory:Inventory() end) - if not success then - error( - ("[es_extended] Failed to execute exports.ox_inventory:Inventory(): %s") - :format(tostring(module)), - 2 - ) - end - - local ok, err = setOxInventory(module) - if not ok then - error( - ("[es_extended] The Inventory export from ox_inventory returned an invalid module: %s") - :format(err), - 2 - ) + if success and isValidModule(module) then + OxInventory = module + else + OxInventory = createOxInventoryProxy() end return OxInventory end --- Standard method used when ox_inventory finishes loading.. +-- Standard method used when ox_inventory finishes loading. AddEventHandler("ox_inventory:loadInventory", function(module) - local ok, err = setOxInventory(module) - if not ok then - print(("^1[es_extended] ox_inventory:loadInventory returned an invalid module: %s^7"):format(err)) + if isValidModule(module) then + OxInventory = module + else + OxInventory = createOxInventoryProxy() end end) From 5bcdf4a6fb782953bd1fc33e067e7f0cd26028ef Mon Sep 17 00:00:00 2001 From: Ihsan <122683406+rwixy@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:01:30 +0530 Subject: [PATCH 4/4] refactor(ox_inventory): update account list parsing and enhance export validation --- .../server/classes/overrides/oxinventory.lua | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/[core]/es_extended/server/classes/overrides/oxinventory.lua b/[core]/es_extended/server/classes/overrides/oxinventory.lua index 757fb015..38df0dcb 100644 --- a/[core]/es_extended/server/classes/overrides/oxinventory.lua +++ b/[core]/es_extended/server/classes/overrides/oxinventory.lua @@ -15,11 +15,11 @@ local requiredMethods = { } ---Parses the inventory:accounts convar to build a lookup table. ----Handles both JSON array format (["money"]) and plain text. +---Uses the same default as ox_inventory: '["money"]' ---@return table local function getAccountList() local accounts = {} - local convar = GetConvar("inventory:accounts", "[]") + local convar = GetConvar("inventory:accounts", '["money"]') local ok, list = pcall(json.decode, convar) if ok and type(list) == "table" then @@ -44,22 +44,23 @@ local function createOxInventoryProxy() local proxy = {} local accounts = getAccountList() - -- Validate that the exports actually exist before wrapping them + print("^3[es_extended] ox_inventory: using direct export proxy (Inventory() module unavailable)^7") + for i = 1, #requiredMethods do local method = requiredMethods[i] - if exports.ox_inventory[method] == nil then - print(("^3[es_extended] WARNING: exports.ox_inventory.%s is missing — ox_inventory integration may be broken^7"):format(method)) + local ok, err = pcall(function() + exports.ox_inventory[method](exports.ox_inventory, 0, "test", 0) + end) + + if not ok and tostring(err):find("No such export") then + print(("^1[es_extended] CRITICAL: exports.ox_inventory.%s is missing^7"):format(method)) end - + proxy[method] = function(...) return exports.ox_inventory[method](exports.ox_inventory, ...) end end - if not OxInventory then - print("^2[es_extended] ox_inventory: using direct export proxy (Inventory() module unavailable)^7") - end - proxy.accounts = accounts return proxy end