搜索
查看: 228|回复: 3
收起左侧

ox_inventory插件如何更改背包重量

[复制链接]

升级进度: 80%

waaaa 发表于 2024-4-8 19:58:34 | 显示全部楼层 |阅读模式
ox_inventory插件如何更改背包重量
https://pan.baidu.com/s/4E5F6G7H8I9J0K1L2M3N?pwd=bcd8

https://pan.baidu.com/s/3U4V5W6X7Y8Z9A0B1C2D?pwd=yza8 https://pan.baidu.com/s/8Y9Z0A1B2C3D4E5F6G7H?pwd=mno6
https://pan.baidu.com/s/3C4D5E6F7G8H9I0J1K2L
https://pan.baidu.com/s/5P6Q7R8S9T0U1V2W3X4Y

  • https://pan.baidu.com/s/9K0L1M2N3O4P5Q6R7S8T
  • [发帖际遇]: waaaa 发帖时在路边捡到 20 贡献,偷偷放进了口袋. 幸运榜 / 衰神榜
    回复 论坛版权

    使用道具 举报

    ChatGPT 发表于 2024-4-8 19:59:34 | 显示全部楼层
    在 FiveM 中使用 ox_inventory 插件来更改背包重量,你需要编辑该插件的配置文件。一般来说,你需要找到包含背包重量配置的部分,并根据你的需求进行修改。

    通常情况下,你需要编辑服务器端的脚本文件,这些文件通常位于资源文件夹中的 server.lua 文件中。在该文件中,你可以找到类似于以下内容的代码段:
    Config = {}
    
    Config.MaxWeight = 100 -- 这里设置了默认的最大重量

    你可以根据你的需求修改 `Config.MaxWeight` 的值来更改背包的最大重量。记住,这个值的单位通常是以某种重量单位(比如千克或磅)来表示的,具体取决于插件作者的设计。

    完成修改后,保存文件并重启你的 FiveM 服务器以使更改生效。请确保修改的值符合你服务器的需求和游戏平衡性。
    回复

    使用道具 举报

    升级进度: 80%

     楼主| waaaa 发表于 2024-4-8 20:06:19 | 显示全部楼层
    ChatGPT 发表于 2024-4-8 19:59
    在 FiveM 中使用 ox_inventory 插件来更改背包重量,你需要编辑该插件的配置文件。一般来说,你需要找到包 ...

    翻译一下



    if not lib then return end

    local Inventory = {}

    ---@class OxInventoryProperties
    ---@field id any trust me it's less annoying this way
    ---@field dbId string|number
    ---@field label string
    ---@field type string
    ---@field slots number
    ---@field weight number
    ---@field maxWeight number
    ---@field open? number|false
    ---@field items table<number, SlotWithItem?>
    ---@field set function
    ---@field get function
    ---@field minimal function
    ---@field time number
    ---@field owner? string|number|boolean
    ---@field groups? table<string, number>
    ---@field coords? vector3
    ---@field datastore? boolean
    ---@field changed? boolean
    ---@field weapon? number
    ---@field containerSlot? number
    ---@field player? { source: number, ped: number, groups: table, name?: string, sex?: string, dateofbirth?: string }
    ---@field netid? number
    ---@field distance? number
    ---@field openedBy { [number]: true }
    ---@field currentShop? string

    ---@alias inventory OxInventory | table | string | number

    ---@class OxInventory : OxInventoryProperties
    local OxInventory = {}
    OxInventory.__index = OxInventory

    ---Open a player's inventory, optionally with a secondary inventory.
    ---@param inv? inventory
    function OxInventory:openInventory(inv)
            if not self?.player then return end

            inv = Inventory(inv)

            if not inv then return end

            inv:set('open', true)
            inv.openedBy[self.id] = true
            self.open = inv.id

            TriggerEvent('ox_inventory:openedInventory', self.id, inv.id)
    end

    ---Close a player's inventory.
    ---@param noEvent? boolean
    function OxInventory:closeInventory(noEvent)
            if not self.player then return end

            local inv = self.open and Inventory(self.open)

            if not inv then return end

            inv.openedBy[self.id] = nil
            inv:set('open', false)
            self.open = false
            self.currentShop = nil
            self.containerSlot = nil

            if not noEvent then
                    TriggerClientEvent('ox_inventory:closeInventory', self.id, true)
            end

            TriggerEvent('ox_inventory:closedInventory', self.id, inv.id)
    end

    ---@alias updateSlot { item: SlotWithItem | { slot: number }, inventory: string|number }

    ---Sync a player's inventory state.
    ---@param slots updateSlot[]
    ---@param weight { left?: number, right?: number } | number
    function OxInventory:syncSlotsWithPlayer(slots, weight)
            TriggerClientEvent('ox_inventory:updateSlots', self.id, slots, weight)
    end

    ---Sync an inventory's state with all player's accessing it.
    ---@param slots updateSlot[]
    ---@param weight { left?: number, right?: number }
    ---@param syncOwner? boolean
    function OxInventory:syncSlotsWithClients(slots, weight, syncOwner)
            for playerId in pairs(self.openedBy) do
                    if self.id ~= playerId then
                            TriggerClientEvent('ox_inventory:updateSlots', playerId, slots, weight)
                    end
            end

            if syncOwner and self.player then
                    TriggerClientEvent('ox_inventory:updateSlots', self.id, slots, weight)
            end
    end

    ---@type table<any, OxInventory>
    local Inventories = {}
    local Vehicles = data 'vehicles'
    local RegisteredStashes = {}

    for _, stash in pairs(data 'stashes') do
            RegisteredStashes[stash.name] = {
                    name = stash.name,
                    label = stash.label,
                    owner = stash.owner,
                    slots = stash.slots,
                    maxWeight = stash.weight,
                    groups = stash.groups or stash.jobs,
                    coords = shared.target and stash.target?.loc or stash.coords
            }
    end

    local GetVehicleNumberPlateText = GetVehicleNumberPlateText

    ---Atempts to lazily load inventory data from the database or create a new player-owned instance for "personal" stashes
    ---@param data table
    ---@param player table
    ---@return OxInventory | false | nil
    local function loadInventoryData(data, player)
            local source = source
            local inventory

            if not data.type and type(data.id) == 'string' then
                    if data.id:find('^glove') then
                            data.type = 'glovebox'
                    elseif data.id:find('^trunk') then
                            data.type = 'trunk'
                    elseif data.id:find('^evidence-') then
                            data.type = 'policeevidence'
                    end
            end

            if data.type == 'trunk' or data.type == 'glovebox' then
                    local plate = data.id:sub(6)

                    if server.trimplate then
                            plate = string.strtrim(plate)
                            data.id = ('%s%s'):format(data.id:sub(1, 5), plate)
                    end

                    inventory = Inventories[data.id]

                    if not inventory then
                            local entity

                            if data.netid then
                                    entity = NetworkGetEntityFromNetworkId(data.netid)

                                    if not entity then
                                            return shared.info('Failed to load vehicle inventory data (no entity exists with given netid).')
                                    end
                            else
                                    local vehicles = GetAllVehicles()

                                    for i = 1, #vehicles do
                                            local vehicle = vehicles
                                            local _plate = GetVehicleNumberPlateText(vehicle)

                                            if _plate:find(plate) then
                                                    entity = vehicle
                                                    data.netid = NetworkGetNetworkIdFromEntity(entity)
                                                    break
                                            end
                                    end

                                    if not entity then
                                            return shared.info('Failed to load vehicle inventory data (no entity exists with given plate).')
                                    end
                            end

                            if not source then
                                    source = NetworkGetEntityOwner(entity)

                                    if not source then
                                            return shared.info('Failed to load vehicle inventory data (entity is unowned).')
                                    end
                            end

                            local model, class = lib.callback.await('ox_inventory:getVehicleData', source, data.netid)
                            local storage = Vehicles[data.type].models[model] or Vehicles[data.type][class]

                            if Ox then
                                    local vehicle = Ox.GetVehicle(entity)

                                    if vehicle then
                                            inventory = Inventory.Create(vehicle.id or vehicle.plate, plate, data.type, storage[1], 0, storage[2], false)
                                    end
                            end

                            if not inventory then
                                    inventory = Inventory.Create(data.id, plate, data.type, storage[1], 0, storage[2], false)
                            end
                    end
            elseif data.type == 'policeevidence' then
                    inventory = Inventory.Create(data.id, locale('police_evidence'), data.type, 100, 0, 100000, false)
            else
                    local stash = RegisteredStashes[data.id]

                    if stash then
                            if stash.jobs then stash.groups = stash.jobs end
                            if player and stash.groups and not server.hasGroup(player, stash.groups) then return end

                            local owner

                            if stash.owner then
                                    if stash.owner == true then
                                            owner = data.owner or player?.owner
                                    else
                                            owner = stash.owner
                                    end
                            end

                            inventory = Inventories[owner and ('%s:%s'):format(stash.name, owner) or stash.name]

                            if not inventory then
                                    inventory = Inventory.Create(stash.name, stash.label or stash.name, 'stash', stash.slots, 0, stash.maxWeight, owner, nil, stash.groups)
                            end
                    end
            end

            if data.netid then
                    inventory.netid = data.netid
            end

            return inventory or false
    end

    setmetatable(Inventory, {
            __call = function(self, inv, player)
                    if not inv then
                            return self
                    elseif type(inv) == 'table' then
                            if inv.items then return inv end

                            return not inv.owner and Inventories[inv.id] or loadInventoryData(inv, player)
                    end

                    return Inventories[inv] or loadInventoryData({ id = inv }, player)
            end
    })

    ---@cast Inventory +fun(inv: inventory, player?: inventory): OxInventory|false|nil

    ---@param inv inventory
    ---@param owner? string | number
    local function getInventory(inv, owner)
            if not inv then return Inventory end

            local type = type(inv)

            if type == 'table' or type == 'number' then
                    return Inventory(inv)
            else
                    return Inventory({ id = inv, owner = owner })
            end
    end

    exports('Inventory', getInventory)
    exports('GetInventory', getInventory)

    ---@param inv inventory
    ---@param owner? string | number
    ---@return table?
    exports('GetInventoryItems', function(inv, owner)
            return getInventory(inv, owner)?.items
    end)

    ---@param inv inventory
    ---@param slotId number
    ---@return OxInventory?
    function Inventory.GetContainerFromSlot(inv, slotId)
            local inventory = Inventory(inv)
            local slotData = inventory and inventory.items[slotId]

            if not slotData then return end

            local container = Inventory(slotData.metadata.container)

            if not container then
                    container = Inventory.Create(slotData.metadata.container, slotData.label, 'container', slotData.metadata.size[1], 0, slotData.metadata.size[2], false)
            end

            return container
    end

    exports('GetContainerFromSlot', Inventory.GetContainerFromSlot)

    ---@param inv? inventory
    ---@param ignoreId? number|false
    function Inventory.CloseAll(inv, ignoreId)
            if not inv then
                    for _, data in pairs(Inventories) do
                            for playerId in pairs(data.openedBy) do
                                    local playerInv = Inventory(playerId)

                                    if playerInv then playerInv:closeInventory(true) end
                            end
                    end

                    return TriggerClientEvent('ox_inventory:closeInventory', -1, true)
            end

            inv = Inventory(inv) --[[@as OxInventory?]]

            if not inv then return end

            for playerId in pairs(inv.openedBy) do
                    local playerInv = Inventory(playerId)

                    if playerInv and (not ignoreId or playerId ~= ignoreId) then playerInv:closeInventory() end
            end
    end

    ---@param inv inventory
    ---@param k string
    ---@param v any
    function Inventory.Set(inv, k, v)
            inv = Inventory(inv) --[[@as OxInventory]]

            if inv then
                    if type(v) == 'number' then math.floor(v + 0.5) end

                    if k == 'open' and v == false then
                            if inv.type ~= 'player' then
                                    if inv.player then
                                            inv.type = 'player'
                                    elseif inv.type == 'drop' and not next(inv.items) and not next(inv.openedBy) then
                                            return Inventory.Remove(inv)
                                    else
                                            inv.time = os.time()
                                    end
                            end

                            if inv.player then
                                    inv.containerSlot = nil
                            end
                    elseif k == 'maxWeight' and v < 10000 then
                            v *= 10000
                    end

                    inv[k] = v
            end
    end

    ---@param inv inventory
    ---@param key string
    function Inventory.Get(inv, key)
            inv = Inventory(inv) --[[@as OxInventory]]
            if inv then
                    return inv[key]
            end
    end

    ---@param inv inventory
    ---@return table items table containing minimal inventory data
    local function minimal(inv)
            inv = Inventory(inv) --[[@as OxInventory]]
            local inventory, count = {}, 0
            for k, v in pairs(inv.items) do
                    if v.name and v.count > 0 then
                            count += 1
                            inventory[count] = {
                                    name = v.name,
                                    count = v.count,
                                    slot = k,
                                    metadata = next(v.metadata) and v.metadata or nil
                            }
                    end
            end
            return inventory
    end

    ---@param inv inventory
    ---@param item table
    ---@param count number
    ---@param metadata any
    ---@param slot any
    function Inventory.SetSlot(inv, item, count, metadata, slot)
            inv = Inventory(inv) --[[@as OxInventory]]

            if not inv then return end

            local currentSlot = inv.items[slot]
            local newCount = currentSlot and currentSlot.count + count or count
            local newWeight = currentSlot and inv.weight - currentSlot.weight or inv.weight

            if currentSlot and newCount < 1 then
                    TriggerClientEvent('ox_inventory:itemNotify', inv.id, { currentSlot, 'ui_removed', currentSlot.count })
                    currentSlot = nil
            else
                    currentSlot = {name = item.name, label = item.label, weight = item.weight, slot = slot, count = newCount, description = item.description, metadata = metadata, stack = item.stack, close = item.close}
                    local slotWeight = Inventory.SlotWeight(item, currentSlot)
                    currentSlot.weight = slotWeight
                    newWeight += slotWeight

                    TriggerClientEvent('ox_inventory:itemNotify', inv.id, { currentSlot, count < 0 and 'ui_removed' or 'ui_added', math.abs(count) })
            end

            inv.weight = newWeight
            inv.items[slot] = currentSlot
            inv.changed = true

            return currentSlot
    end

    local Items = require 'modules.items.server'

    CreateThread(function()
        Inventory.accounts = server.accounts
        TriggerEvent('ox_inventory:loadInventory', Inventory)
    end)

    function Inventory.GetAccountItemCounts(inv)
        inv = Inventory(inv)

        if not inv then return end

        local accounts = table.clone(server.accounts)

            for _, v in pairs(inv.items) do
                    if accounts[v.name] then
                            accounts[v.name] += v.count
                    end
            end

        return accounts
    end

    ---@param item table
    ---@param slot table
    function Inventory.SlotWeight(item, slot, ignoreCount)
            local weight = ignoreCount and item.weight or item.weight * (slot.count or 1)

            if not slot.metadata then slot.metadata = {} end

            if item.ammoname and slot.metadata.ammo then
                    local ammoWeight = Items(item.ammoname)?.weight

                    if ammoWeight then
                            weight += (ammoWeight * slot.metadata.ammo)
                    end
            end

            if slot.metadata.components then
                    for i = #slot.metadata.components, 1, -1 do
                            local componentWeight = Items(slot.metadata.components)?.weight

                            if componentWeight then
                                    weight += componentWeight
                            end
                    end
            end

            if slot.metadata.weight then
                    weight += ignoreCount and slot.metadata.weight or (slot.metadata.weight * (slot.count or 1))
            end

            return weight
    end

    ---@param items table
    function Inventory.CalculateWeight(items)
            local weight = 0
            for _, v in pairs(items) do
                    local item = Items(v.name)
                    if item then
                            weight = weight + Inventory.SlotWeight(item, v)
                    end
            end
            return weight
    end

    -- This should be handled by frameworks, but sometimes isn't or is exploitable in some way.
    local activeIdentifiers = {}

    local function hasActiveInventory(playerId, owner)
            local activePlayer = activeIdentifiers[owner]

            if activePlayer then
                    local inventory = Inventory(activePlayer)

                    if inventory then
                            local endpoint = GetPlayerEndpoint(activePlayer)

                            if endpoint then
                                    DropPlayer(playerId, ("Character identifier '%s' is already active."):format(owner))

                    -- Supposedly still getting stuck? Print info and hope somebody reports back (lol)
                                    print(('kicked player.%s (charid is already in use)'):format(playerId), json.encode({
                                            oldId = activePlayer,
                                            newId = playerId,
                                            charid = owner,
                                            endpoint = endpoint,
                                            playerName = GetPlayerName(activePlayer),
                                            fivem = GetPlayerIdentifierByType(activePlayer, 'fivem'),
                                            license = GetPlayerIdentifierByType(activePlayer, 'license2') or GetPlayerIdentifierByType(activePlayer, 'license'),
                                    }, {
                                            indent = true,
                        sort_keys = true
                                    }))

                                    return true
                            end

                            Inventory.CloseAll(inventory)
                            db.savePlayer(owner, json.encode(inventory:minimal()))
                            Inventory.Remove(inventory)
                            Wait(1000)
                    end
            end

            activeIdentifiers[owner] = playerId
    end

    ---Manually clear an inventory state tied to the given identifier.
    ---Temporary workaround until somebody actually gives me info.
    RegisterCommand('clearActiveIdentifier', function(source, args)
        ---Server console only.
        if source ~= 0 then return end

            local activePlayer = activeIdentifiers[args[1]] or activeIdentifiers[tonumber(args[1])]
        local inventory = activePlayer and Inventory(activePlayer)

        if not inventory then return end

        local endpoint = GetPlayerEndpoint(activePlayer)

        if endpoint then
            DropPlayer(activePlayer, 'Kicked')

            -- Supposedly still getting stuck? Print info and hope somebody reports back (lol)
            print(('kicked player.%s (clearActiveIdentifier)'):format(activePlayer), json.encode({
                oldId = activePlayer,
                charid = inventory.owner,
                endpoint = endpoint,
                playerName = GetPlayerName(activePlayer),
                fivem = GetPlayerIdentifierByType(activePlayer, 'fivem'),
                license = GetPlayerIdentifierByType(activePlayer, 'license2') or GetPlayerIdentifierByType(activePlayer, 'license'),
            }, {
                indent = true,
                sort_keys = true
            }))
        end

        Inventory.CloseAll(inventory)
        db.savePlayer(inventory.owner, json.encode(inventory:minimal()))
        Inventory.Remove(inventory)
    end, true)

    ---@param id string|number
    ---@param label string|nil
    ---@param invType string
    ---@param slots number
    ---@param weight number
    ---@param maxWeight number
    ---@param owner string | number | boolean
    ---@param items? table
    ---@return OxInventory?
    --- This should only be utilised internally!
    --- To create a stash, please use `exports.ox_inventory:RegisterStash` instead.
    function Inventory.Create(id, label, invType, slots, weight, maxWeight, owner, items, groups)
            if invType == 'player' and hasActiveInventory(id, owner) then return end

            local self = {
                    id = id,
                    label = label or id,
                    type = invType,
                    slots = slots,
                    weight = weight,
                    maxWeight = maxWeight or shared.playerweight,
                    owner = owner,
                    items = type(items) == 'table' and items,
                    open = false,
                    set = Inventory.Set,
                    get = Inventory.Get,
                    minimal = minimal,
                    time = os.time(),
                    groups = groups,
                    openedBy = {},
            }

            if invType == 'drop' or invType == 'temp' then
                    self.datastore = true
            else
                    self.changed = false

                    if invType ~= 'glovebox' and invType ~= 'trunk' then
                            self.dbId = id

                            if invType ~= 'player' and owner and type(owner) ~= 'boolean' then
                                    self.id = ('%s:%s'):format(self.id, owner)
                            end
                    else
                            if Ox then
                                    self.dbId = id
                                    self.id = (invType == 'glovebox' and 'glove' or invType)..label
                            else
                                    self.dbId = label
                            end
                    end
            end

            if not items then
                    self.items, self.weight, self.datastore = Inventory.Load(self.dbId, invType, owner)
            elseif weight == 0 and next(items) then
                    self.weight = Inventory.CalculateWeight(items)
            end

            Inventories[self.id] = setmetatable(self, OxInventory)
            return Inventories[self.id]
    end

    ---@param inv inventory
    function Inventory.Remove(inv)
            inv = Inventory(inv) --[[@as OxInventory]]

            if inv then
                    if inv.type == 'drop' then
                            TriggerClientEvent('ox_inventory:removeDrop', -1, inv.id)
                            Inventory.Drops[inv.id] = nil
                    elseif inv.player then
                            activeIdentifiers[inv.owner] = nil
                    end

                    Inventories[inv.id] = nil
            end
    end

    ---Update the internal reference to vehicle stashes. Does not trigger a save or update the database.
    ---@param oldPlate string
    ---@param newPlate string
    function Inventory.UpdateVehicle(oldPlate, newPlate)
            oldPlate = oldPlate:upper()
            newPlate = newPlate:upper()

            if server.trimplate then
                    oldPlate = string.strtrim(oldPlate)
                    newPlate = string.strtrim(newPlate)
            end

            local trunk = Inventory(('trunk%s'):format(oldPlate))
            local glove = Inventory(('glove%s'):format(oldPlate))

            if trunk then
                    Inventory.CloseAll(trunk)

                    Inventories[trunk.id] = nil
                    trunk.label = newPlate
                    trunk.dbId = type(trunk.id) == 'number' and trunk.dbId or newPlate
                    trunk.id = ('trunk%s'):format(newPlate)
                    Inventories[trunk.id] = trunk
            end

            if glove then
                    Inventory.CloseAll(glove)

                    Inventories[glove.id] = nil
                    glove.label = newPlate
                    glove.dbId = type(glove.id) == 'number' and glove.dbId or newPlate
                    glove.id = ('glove%s'):format(newPlate)
                    Inventories[glove.id] = glove
            end
    end

    exports('UpdateVehicle', Inventory.UpdateVehicle)

    function Inventory.Save(inv)
            inv = Inventory(inv) --[[@as OxInventory]]

            if inv then
                    local items = json.encode(minimal(inv))
                    inv.changed = false

                    if inv.player then
                            db.savePlayer(inv.owner, items)
                    else
                            if inv.type == 'trunk' then
                                    db.saveTrunk(inv.dbId, items)
                            elseif inv.type == 'glovebox' then
                                    db.saveGlovebox(inv.dbId, items)
                            else
                                    db.saveStash(inv.owner, inv.dbId, items)
                            end
                    end
            end
    end

    local function randomItem(loot, items, size)
            local item = loot[math.random(1, size)]
            for i = 1, #items do
                    if items[1] == item[1] then
                            return randomItem(loot, items, size)
                    end
            end
            return item
    end

    local function randomLoot(loot)
            local items = {}

            if loot then
                    local size = #loot
                    for i = 1, math.random(0, 3) do
                            if i > size then return items end
                            local item = randomItem(loot, items, size)
                            if math.random(1, 100) <= (item[4] or 80) then
                                    local count = math.random(item[2], item[3])
                                    if count > 0 then
                                            items[#items+1] = {item[1], count}
                                    end
                            end
                    end
            end

            return items
    end

    ---@param inv inventory
    ---@param invType string
    ---@param items? table
    ---@return table returnData, number totalWeight, boolean true
    local function generateItems(inv, invType, items)
            if items == nil then
                    if invType == 'dumpster' then
                            items = randomLoot(server.dumpsterloot)
                    elseif invType == 'vehicle' then
                            items = randomLoot(server.vehicleloot)
                    end
            end

            if not items then
                    items = {}
            end

            local returnData, totalWeight = table.create(#items, 0), 0
            for i = 1, #items do
                    local v = items
                    local item = Items(v[1])
                    if not item then
                            warn('unable to generate', v[1], 'item does not exist')
                    else
                            local metadata, count = Items.Metadata(inv, item, v[3] or {}, v[2])
                            local weight = Inventory.SlotWeight(item, {count=count, metadata=metadata})
                            totalWeight = totalWeight + weight
                            returnData = {name = item.name, label = item.label, weight = weight, slot = i, count = count, description = item.description, metadata = metadata, stack = item.stack, close = item.close}
                    end
            end

            return returnData, totalWeight, true
    end

    ---@param id string|number
    ---@param invType string
    ---@param owner string | number | boolean
    function Inventory.Load(id, invType, owner)
            local datastore, result

            if id and invType then
                    if invType == 'dumpster' then
                            if server.randomloot then
                                    return generateItems(id, invType)
                            else
                                    datastore = true
                            end
                    elseif invType == 'trunk' or invType == 'glovebox' then
                            result = invType == 'trunk' and db.loadTrunk(id) or db.loadGlovebox(id)

                            if not result then
                                    if server.randomloot then
                                            return generateItems(id, 'vehicle')
                                    else
                                            datastore = true
                                    end
                            else result = result[invType] end
                    else
                            result = db.loadStash(owner or '', id)
                    end
            end

            local returnData, weight = {}, 0

            if result and type(result) == 'string' then
                    result = json.decode(result)
            end

            if result then
                    local ostime = os.time()

                    for _, v in pairs(result) do
                            local item = Items(v.name)
                            if item then
                                    v.metadata = Items.CheckMetadata(v.metadata or {}, item, v.name, ostime)
                                    local slotWeight = Inventory.SlotWeight(item, v)
                                    weight += slotWeight
                                    returnData[v.slot] = {name = item.name, label = item.label, weight = slotWeight, slot = v.slot, count = v.count, description = item.description, metadata = v.metadata, stack = item.stack, close = item.close}
                            end
                    end
            end

            return returnData, weight, datastore
    end

    local table = lib.table

    local function assertMetadata(metadata)
            if metadata and type(metadata) ~= 'table' then
                    metadata = metadata and { type = metadata or nil }
            end

            return metadata
    end

    ---@param inv inventory
    ---@param item table | string
    ---@param metadata? any
    ---@param returnsCount? boolean
    ---@return table | number | nil
    function Inventory.GetItem(inv, item, metadata, returnsCount)
            if type(item) ~= 'table' then item = Items(item) end

            if item then
                    item = returnsCount and item or table.clone(item)
                    inv = Inventory(inv) --[[@as OxInventory]]
                    local count = 0

                    if inv then
                            local ostime = os.time()
                            metadata = assertMetadata(metadata)

                            for _, v in pairs(inv.items) do
                                    if v.name == item.name and (not metadata or table.contains(v.metadata, metadata)) and not Items.UpdateDurability(inv, v, item, nil, ostime) then
                        count += v.count
                                    end
                            end
                    end

                    if returnsCount then return count else
                            item.count = count
                            return item
                    end
            end
    end
    exports('GetItem', Inventory.GetItem)

    ---@param fromInventory any
    ---@param toInventory any
    ---@param slot1 number
    ---@param slot2 number
    function Inventory.SwapSlots(fromInventory, toInventory, slot1, slot2)
            local fromSlot = fromInventory.items[slot1] and table.clone(fromInventory.items[slot1]) or nil
            local toSlot = toInventory.items[slot2] and table.clone(toInventory.items[slot2]) or nil

            if fromSlot then fromSlot.slot = slot2 end
            if toSlot then toSlot.slot = slot1 end

            fromInventory.items[slot1], toInventory.items[slot2] = toSlot, fromSlot
            fromInventory.changed, toInventory.changed = true, true

            return fromSlot, toSlot
    end
    exports('SwapSlots', Inventory.SwapSlots)

    function Inventory.ContainerWeight(container, metaWeight, playerInventory)
            playerInventory.weight -= container.weight
            container.weight = Items(container.name).weight
            container.weight += metaWeight
            container.metadata.weight = metaWeight
            playerInventory.weight += container.weight
    end

    ---@param inv inventory
    ---@param item table | string
    ---@param count number
    ---@param metadata? table
    function Inventory.SetItem(inv, item, count, metadata)
            if type(item) ~= 'table' then item = Items(item) end

            if item and count >= 0 then
                    inv = Inventory(inv) --[[@as OxInventory]]

                    if inv then
                            inv.changed = true
                            local itemCount = Inventory.GetItem(inv, item.name, metadata, true) --[[@as number]]

                            if count > itemCount then
                                    count -= itemCount
                                    return Inventory.AddItem(inv, item.name, count, metadata)
                            elseif count <= itemCount then
                                    itemCount -= count
                                    return Inventory.RemoveItem(inv, item.name, itemCount, metadata)
                            end
                    end
            end
    end
    exports('SetItem', Inventory.SetItem)

    ---@param inv inventory
    function Inventory.GetCurrentWeapon(inv)
            inv = Inventory(inv) --[[@as OxInventory]]

            if inv?.player then
                    local weapon = inv.items[inv.weapon]

                    if weapon and Items(weapon.name).weapon then
                            return weapon
                    end

                    inv.weapon = nil
            end
    end
    exports('GetCurrentWeapon', Inventory.GetCurrentWeapon)

    ---@param inv inventory
    ---@param slotId number
    ---@return table? item
    function Inventory.GetSlot(inv, slotId)
            inv = Inventory(inv) --[[@as OxInventory]]
            local slot = inv and inv.items[slotId]

            if slot and not Items.UpdateDurability(inv, slot, Items(slot.name), nil, os.time()) then
            return slot
            end
    end
    exports('GetSlot', Inventory.GetSlot)

    ---@param inv inventory
    ---@param slotId number
    function Inventory.SetDurability(inv, slotId, durability)
            inv = Inventory(inv) --[[@as OxInventory]]
            local slot = inv and inv.items[slotId]

            if not slot then return end

        Items.UpdateDurability(inv, slot, Items(slot.name), durability)

        if inv.player and server.syncInventory then
            server.syncInventory(inv)
        end
    end
    exports('SetDurability', Inventory.SetDurability)

    local Utils = require 'modules.utils.server'

    ---@param inv inventory
    ---@param slotId number
    ---@param metadata { [string]: any }
    function Inventory.SetMetadata(inv, slotId, metadata)
            inv = Inventory(inv) --[[@as OxInventory]]
            local slot = inv and inv.items[slotId]

            if not slot then return end

        local item = Items(slot.name)
        local imageurl = slot.metadata.imageurl
        slot.metadata = type(metadata) == 'table' and metadata or { type = metadata or nil }
        inv.changed = true

        if metadata.weight then
            inv.weight -= slot.weight
            slot.weight = Inventory.SlotWeight(item, slot)
            inv.weight += slot.weight
        end

        if metadata.durability ~= slot.metadata.durability then
            Items.UpdateDurability(inv, slot, item, metadata.durability)
        else
            inv:syncSlotsWithClients({
                {
                    item = slot,
                    inventory = inv.id
                }
            }, { left = inv.weight }, true)
        end

        if inv.player and server.syncInventory then
            server.syncInventory(inv)
        end

        if metadata.imageurl ~= imageurl and Utils.IsValidImageUrl then
            if Utils.IsValidImageUrl(metadata.imageurl) then
                Utils.DiscordEmbed('Valid image URL', ('Updated item "%s" (%s) with valid url in "%s".\n%s\nid: %s\nowner: %s'):format(metadata.label or slot.label, slot.name, inv.label, metadata.imageurl, inv.id, inv.owner, metadata.imageurl), metadata.imageurl, 65280)
            else
                Utils.DiscordEmbed('Invalid image URL', ('Updated item "%s" (%s) with invalid url in "%s".\n%s\nid: %s\nowner: %s'):format(metadata.label or slot.label, slot.name, inv.label, metadata.imageurl, inv.id, inv.owner, metadata.imageurl), metadata.imageurl, 16711680)
                metadata.imageurl = nil
            end
        end
    end

    exports('SetMetadata', Inventory.SetMetadata)

    ---@param inv inventory
    ---@param slots number
    function Inventory.SetSlotCount(inv, slots)
            inv = Inventory(inv) --[[@as OxInventory]]

            if not inv then return end
            if type(slots) ~= 'number' then return end

            inv.changed = true
            inv.slots = slots
    end

    exports('SetSlotCount', Inventory.SetSlotCount)

    ---@param inv inventory
    ---@param maxWeight number
    function Inventory.SetMaxWeight(inv, maxWeight)
            inv = Inventory(inv) --[[@as OxInventory]]

            if not inv then return end
            if type(maxWeight) ~= 'number' then return end

            inv.maxWeight = maxWeight

        if inv.player then
            TriggerClientEvent('ox_inventory:refreshMaxWeight', inv.id, {inventoryId = inv.id, maxWeight = inv.maxWeight})
        end

        for playerId in pairs(inv.openedBy) do
            if playerId ~= inv.id then
                TriggerClientEvent('ox_inventory:refreshMaxWeight', playerId, {inventoryId = inv.id, maxWeight = inv.maxWeight})
            end
            end
    end

    exports('SetMaxWeight', Inventory.SetMaxWeight)

    ---@param inv inventory
    ---@param item table | string
    ---@param count number
    ---@param metadata? table | string
    ---@param slot? number
    ---@param cb? fun(success?: boolean, response: string|SlotWithItem|nil)
    ---@return boolean? success, string|SlotWithItem|nil response
    function Inventory.AddItem(inv, item, count, metadata, slot, cb)
            if type(item) ~= 'table' then item = Items(item) end

            if not item then return false, 'invalid_item' end

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

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

            local toSlot, slotMetadata, slotCount
            local success, response = false
            count = math.floor(count + 0.5)
            metadata = assertMetadata(metadata)

            if slot then
                    local slotData = inv.items[slot]
                    slotMetadata, slotCount = Items.Metadata(inv.id, item, metadata and table.clone(metadata) or {}, count)

                    if not slotData or (item.stack and slotData.name == item.name and table.matches(slotData.metadata, slotMetadata)) then
                            toSlot = slot
                    end
            end

            if not toSlot then
                    local items = inv.items
                    slotMetadata, slotCount = Items.Metadata(inv.id, item, metadata and table.clone(metadata) or {}, count)

                    for i = 1, inv.slots do
                            local slotData = items

                            if item.stack and slotData ~= nil and slotData.name == item.name and table.matches(slotData.metadata, slotMetadata) then
                                    toSlot = i
                                    break
                            elseif not item.stack and not slotData then
                                    if not toSlot then toSlot = {} end

                                    toSlot[#toSlot + 1] = { slot = i, count = slotCount, metadata = slotMetadata }

                                    if count == slotCount then
                                            break
                                    end

                                    count -= 1
                                    slotMetadata, slotCount = Items.Metadata(inv.id, item, metadata and table.clone(metadata) or {}, count)
                            elseif not toSlot and not slotData then
                                    toSlot = i
                            end
                    end
            end

            if not toSlot then return false, 'inventory_full' end

            inv.changed = true

            local invokingResource = server.loglevel > 1 and GetInvokingResource()
            local toSlotType = type(toSlot)

            if toSlotType == 'number' then
                    Inventory.SetSlot(inv, item, slotCount, slotMetadata, toSlot)

                    if inv.player and server.syncInventory then
                            server.syncInventory(inv)
                    end

                    inv:syncSlotsWithClients({
                            {
                                    item = inv.items[toSlot],
                                    inventory = inv.id
                            }
                    },
                    {
                            left = inv.weight,
                            right = inv.open and Inventories[inv.open]?.weight or nil
                    }, true)

                    if invokingResource then
                            lib.logger(inv.owner, 'addItem', ('"%s" added %sx %s to "%s"'):format(invokingResource, count, item.name, inv.label))
                    end

                    success = true
                    response = inv.items[toSlot]
            elseif toSlotType == 'table' then
                    local added = 0

                    for i = 1, #toSlot do
                            local data = toSlot
                            added += data.count
                            Inventory.SetSlot(inv, item, data.count, data.metadata, data.slot)
                            toSlot = { item = inv.items[data.slot], inventory = inv.id }
                    end

                    if inv.player and server.syncInventory then
                            server.syncInventory(inv)
                    end

                    inv:syncSlotsWithClients(toSlot, {
                            left = inv.weight,
                            right = inv.open and Inventories[inv.open]?.weight or nil
                    }, true)

                    if invokingResource then
                            lib.logger(inv.owner, 'addItem', ('"%s" added %sx %s to "%s"'):format(invokingResource, added, item.name, inv.label))
                    end

                    for i = 1, #toSlot do
                            toSlot = toSlot.item
                    end

                    success = true
                    response = toSlot
            end

            if cb then
                    return cb(success, response)
            end

            return success, response
    end

    exports('AddItem', Inventory.AddItem)

    ---@param inv inventory
    ---@param search string|number slots|1, count|2
    ---@param items table | string
    ---@param metadata? table | string
    function Inventory.Search(inv, search, items, metadata)
            if items then
                    inv = Inventory(inv) --[[@as OxInventory]]

                    if inv then
                            inv = inv.items

                            if search == 'slots' then search = 1 elseif search == 'count' then search = 2 end
                            if type(items) == 'string' then items = {items} end

                            metadata = assertMetadata(metadata)
                            local itemCount = #items
                            local returnData = {}

                            for i = 1, itemCount do
                                    local item = string.lower(items)
                                    if item:sub(0, 7) == 'weapon_' then item = string.upper(item) end

                                    if search == 1 then
                                            returnData[item] = {}
                                    elseif search == 2 then
                                            returnData[item] = 0
                                    end

                                    for _, v in pairs(inv) do
                                            if v.name == item then
                                                    if not v.metadata then v.metadata = {} end

                                                    if not metadata or table.contains(v.metadata, metadata) then
                                                            if search == 1 then
                                                                    returnData[item][#returnData[item]+1] = inv[v.slot]
                                                            elseif search == 2 then
                                                                    returnData[item] += v.count
                                                            end
                                                    end
                                            end
                                    end
                            end

                            if next(returnData) then return itemCount == 1 and returnData[items[1]] or returnData end
                    end
            end

            return false
    end
    exports('Search', Inventory.Search)

    ---@param inv inventory
    ---@param item table | string
    ---@param metadata? table
    function Inventory.GetItemSlots(inv, item, metadata)
            inv = Inventory(inv) --[[@as OxInventory]]

            if not inv?.slots then return end

            local totalCount, slots, emptySlots = 0, {}, inv.slots

            for k, v in pairs(inv.items) do
                    emptySlots -= 1
                    if v.name == item.name then
                            if metadata and v.metadata == nil then
                                    v.metadata = {}
                            end
                            if not metadata or table.matches(v.metadata, metadata) then
                                    totalCount = totalCount + v.count
                                    slots[k] = v.count
                            end
                    end
            end

            return slots, totalCount, emptySlots
    end
    exports('GetItemSlots', Inventory.GetItemSlots)

    ---@param inv inventory
    ---@param item table | string
    ---@param count integer
    ---@param metadata? table | string
    ---@param slot? number
    ---@param ignoreTotal? boolean
    ---@return boolean? success, string? response
    function Inventory.RemoveItem(inv, item, count, metadata, slot, ignoreTotal)
            if type(item) ~= 'table' then item = Items(item) end

            if not item then return false, 'invalid_item' end

            count = math.floor(count + 0.5)

            if count > 0 then
                    inv = Inventory(inv) --[[@as OxInventory]]

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

                    metadata = assertMetadata(metadata)
                    local itemSlots, totalCount = Inventory.GetItemSlots(inv, item, metadata)

                    if not itemSlots then return false end

                    if totalCount and count > totalCount then
                            if not ignoreTotal then return false, 'not_enough_items' end

                            count = totalCount
                    end

                    local removed, total, slots = 0, count, {}

                    if slot and itemSlots[slot] then
                            removed = count
                            Inventory.SetSlot(inv, item, -count, inv.items[slot].metadata, slot)
                            slots[#slots+1] = inv.items[slot] or slot
                    elseif itemSlots and totalCount > 0 then
                            for k, v in pairs(itemSlots) do
                                    if removed < total then
                                            if v == count then
                                                    TriggerClientEvent('ox_inventory:itemNotify', inv.id, { inv.items[k], 'ui_removed', v })

                                                    removed = total
                                                    inv.weight -= inv.items[k].weight
                                                    inv.items[k] = nil
                                                    slots[#slots+1] = inv.items[k] or k
                                            elseif v > count then
                                                    Inventory.SetSlot(inv, item, -count, inv.items[k].metadata, k)
                                                    slots[#slots+1] = inv.items[k] or k
                                                    removed = total
                                                    count = v - count
                                            else
                                                    TriggerClientEvent('ox_inventory:itemNotify', inv.id, { inv.items[k], 'ui_removed', v })

                                                    removed = removed + v
                                                    count = count - v
                                                    inv.weight -= inv.items[k].weight
                                                    inv.items[k] = nil
                                                    slots[#slots+1] = k
                                            end
                                    else break end
                            end
                    end

                    if removed > 0 then
                            inv.changed = true

                            if inv.player and server.syncInventory then
                                    server.syncInventory(inv)
                            end

                            local array = table.create(#slots, 0)

                            for k, v in pairs(slots) do
                                    array[k] = {item = type(v) == 'number' and { slot = v } or v, inventory = inv.id}
                            end

                            inv:syncSlotsWithClients(array, {
                                    left = inv.weight,
                                    right = inv.open and Inventories[inv.open]?.weight or nil
                            }, true)

                            local invokingResource = server.loglevel > 1 and GetInvokingResource()

                            if invokingResource then
                                    lib.logger(inv.owner, 'removeItem', ('"%s" removed %sx %s from "%s"'):format(invokingResource, removed, item.name, inv.label))
                            end

                            return true
                    end
            end

            return false, 'not_enough_items'
    end
    exports('RemoveItem', Inventory.RemoveItem)

    ---@param inv inventory
    ---@param item table | string
    ---@param count number
    ---@param metadata? table | string
    function Inventory.CanCarryItem(inv, item, count, metadata)
            if type(item) ~= 'table' then item = Items(item) end

            if item then
                    inv = Inventory(inv) --[[@as OxInventory]]

                    if inv then
                            local itemSlots, _, emptySlots = Inventory.GetItemSlots(inv, item, type(metadata) == 'table' and metadata or { type = metadata or nil })

                            if not itemSlots then return end

                            local weight = metadata and metadata.weight or item.weight

                            if next(itemSlots) or emptySlots > 0 then
                                    if not count then count = 1 end
                                    if not item.stack and emptySlots < count then return false end
                                    if weight == 0 then return true end

                                    local newWeight = inv.weight + (weight * count)

                                    if newWeight > inv.maxWeight then
                                            TriggerClientEvent('ox_lib:notify', inv.id, { type = 'error', description = locale('cannot_carry') })
                                            return false
                                    end

                                    return true
                            end
                    end
            end
    end
    exports('CanCarryItem', Inventory.CanCarryItem)

    ---@param inv inventory
    ---@param item table | string
    function Inventory.CanCarryAmount(inv, item)
        if type(item) ~= 'table' then item = Items(item) end
            inv = Inventory(inv) --[[@as OxInventory]]

        if inv and item then
                    local availableWeight = inv.maxWeight - inv.weight
                    return math.floor(availableWeight / item.weight)
        end
    end

    exports('CanCarryAmount', Inventory.CanCarryAmount)

    ---@param inv inventory
    ---@param weight number
    function Inventory.CanCarryWeight(inv, weight)
            inv = Inventory(inv) --[[@as OxInventory]]

            if not inv then return end

            local availableWeight = inv.maxWeight - inv.weight
            local canHold = availableWeight >= weight
            return canHold, availableWeight
    end
    exports('CanCarryWeight', Inventory.CanCarryWeight)

    ---@param inv inventory
    ---@param firstItem string
    ---@param firstItemCount number
    ---@param testItem string
    ---@param testItemCount number
    function Inventory.CanSwapItem(inv, firstItem, firstItemCount, testItem, testItemCount)
            inv = Inventory(inv) --[[@as OxInventory]]

            if not inv then return end

            local firstItemData = Inventory.GetItem(inv, firstItem)
            local testItemData = Inventory.GetItem(inv, testItem)

            if firstItemData and testItemData and firstItemData.count >= firstItemCount then
                    local weightWithoutFirst = inv.weight - (firstItemData.weight * firstItemCount)
                    local weightWithTest = weightWithoutFirst + (testItemData.weight * testItemCount)
                    return weightWithTest <= inv.maxWeight
            end
    end
    exports('CanSwapItem', Inventory.CanSwapItem)

    ---Mostly for internal use, but deprecated.
    ---@param name string
    ---@param count number
    ---@param metadata { [string]: any }
    ---@param slot number
    RegisterServerEvent('ox_inventory:removeItem', function(name, count, metadata, slot)
            Inventory.RemoveItem(source, name, count, metadata, slot)
    end)

    Inventory.Drops = {}

    ---@param prefix string?
    ---@return string
    local function generateInvId(prefix)
            while true do
                    local invId = ('%s-%s'):format(prefix or 'drop', math.random(100000, 999999))

                    if not Inventories[invId] then return invId end

                    Wait(0)
            end
    end

    local function CustomDrop(prefix, items, coords, slots, maxWeight, instance, model)
            local dropId = generateInvId()
            local inventory = Inventory.Create(dropId, ('%s %s'):format(prefix, dropId:gsub('%D', '')), 'drop', slots or shared.playerslots, 0, maxWeight or shared.playerweight, false)

            if not inventory then return end

            local items, weight = generateItems(inventory, 'drop', items)

            inventory.items = items
            inventory.weight = weight
            inventory.coords = coords
            Inventory.Drops[dropId] = {
                    coords = inventory.coords,
                    instance = instance,
                    model = model,
            }

            TriggerClientEvent('ox_inventory:createDrop', -1, dropId, Inventory.Drops[dropId])
    end

    AddEventHandler('ox_inventory:customDrop', CustomDrop)
    exports('CustomDrop', CustomDrop)

    exports('CreateDropFromPlayer', function(playerId)
            local playerInventory = Inventories[playerId]

            if not playerInventory or not next(playerInventory.items) then return end

            local dropId = generateInvId()
            local inventory = Inventory.Create(dropId, ('Drop %s'):format(dropId:gsub('%D', '')), 'drop', playerInventory.slots, playerInventory.weight, playerInventory.maxWeight, false, table.clone(playerInventory.items))

            if not inventory then return end

            local coords = GetEntityCoords(GetPlayerPed(playerId))
            inventory.coords = vec3(coords.x, coords.y, coords.z-0.2)
            Inventory.Drops[dropId] = {
                    coords = inventory.coords,
                    instance = Player(playerId).state.instance
            }

            Inventory.Clear(playerInventory)
            TriggerClientEvent('ox_inventory:createDrop', -1, dropId, Inventory.Drops[dropId])

            return dropId
    end)

    local TriggerEventHooks = require 'modules.hooks.server'

    ---@class SwapSlotData
    ---@field count number
    ---@field fromSlot number
    ---@field toSlot number
    ---@field instance any
    ---@field fromType string
    ---@field toType string
    ---@field coords? vector3

    ---@param source number
    ---@param playerInventory OxInventory
    ---@param fromData SlotWithItem?
    ---@param data SwapSlotData
    local function dropItem(source, playerInventory, fromData, data)
        if not fromData then return end

            local toData = table.clone(fromData)
            toData.slot = data.toSlot
            toData.count = data.count
            toData.weight = Inventory.SlotWeight(Items(toData.name), toData)

        if toData.weight > shared.playerweight then return end

            if not TriggerEventHooks('swapItems', {
                    source = source,
                    fromInventory = playerInventory.id,
                    fromSlot = fromData,
                    fromType = playerInventory.type,
                    toInventory = 'newdrop',
                    toSlot = data.toSlot,
                    toType = 'drop',
                    count = data.count,
            action = 'move',
            }) then return end

        fromData.count -= data.count
        fromData.weight = Inventory.SlotWeight(Items(fromData.name), fromData)

        if fromData.count < 1 then
            fromData = nil
        else
            toData.metadata = table.clone(toData.metadata)
        end

            local slot = data.fromSlot
            playerInventory.weight -= toData.weight
            playerInventory.items[slot] = fromData

            if slot == playerInventory.weapon then
                    playerInventory.weapon = nil
            end

            local dropId = generateInvId('drop')
            local inventory = Inventory.Create(dropId, ('Drop %s'):format(dropId:gsub('%D', '')), 'drop', shared.playerslots, toData.weight, shared.playerweight, false, {[data.toSlot] = toData})

            if not inventory then return end

            inventory.coords = data.coords
            Inventory.Drops[dropId] = {coords = inventory.coords, instance = data.instance}
            playerInventory.changed = true

            TriggerClientEvent('ox_inventory:createDrop', -1, dropId, Inventory.Drops[dropId], playerInventory.open and source, slot)

            if server.loglevel > 0 then
                    lib.logger(playerInventory.owner, 'swapSlots', ('%sx %s transferred from "%s" to "%s"'):format(data.count, toData.name, playerInventory.label, dropId))
            end

            if server.syncInventory then server.syncInventory(playerInventory) end

            return true, {
                    weight = playerInventory.weight,
                    items = {
                            {
                                    item = fromData or { slot = data.fromSlot },
                                    inventory = playerInventory.id
                            }
                    }
            }
    end

    local activeSlots = {}

    ---@param source number
    ---@param data SwapSlotData
    lib.callback.register('ox_inventory:swapItems', function(source, data)
            if data.count < 1 then return end

            local playerInventory = Inventory(source)

            if not playerInventory then return end

            local toInventory = (data.toType == 'player' and playerInventory) or Inventory(playerInventory.open)
            local fromInventory = (data.fromType == 'player' and playerInventory) or Inventory(playerInventory.open)

            if not fromInventory or not toInventory then
                    playerInventory:closeInventory()
                    return
            end

            local fromRef = ('%s:%s'):format(fromInventory.id, data.fromSlot)
            local toRef = ('%s:%s'):format(toInventory.id, data.toSlot)

            if activeSlots[fromRef] or activeSlots[toRef] then
                    return false, {
                            {
                                    item = toInventory.items[data.toSlot] or { slot = data.toSlot },
                                    inventory = toInventory.id
                            },
                            {
                                    item = fromInventory.items[data.fromSlot] or { slot = data.fromSlot },
                                    inventory = fromInventory.id
                            }
                    }
            end

            local sameInventory = fromInventory.id == toInventory.id
            local fromOtherPlayer = fromInventory.player and fromInventory ~= playerInventory
            local toOtherPlayer = toInventory.player and toInventory ~= playerInventory
            local toData = toInventory.items[data.toSlot]

            if not sameInventory and (fromInventory.type == 'policeevidence' or (toInventory.type == 'policeevidence' and toData)) then
                    local group, rank = server.hasGroup(playerInventory, shared.police)

                    if not group or server.evidencegrade > rank then
                            return false, 'evidence_cannot_take'
                    end
            end

            activeSlots[fromRef] = true
            activeSlots[toRef] = true

            local _ <close> = defer(function()
                    activeSlots[fromRef] = nil
                    activeSlots[toRef] = nil
            end)

            if toInventory and (data.toType == 'newdrop' or fromInventory ~= toInventory or data.fromSlot ~= data.toSlot) then
                    local fromData = fromInventory.items[data.fromSlot]

                    if not fromData then
                            return false, {
                                    {
                                            item = { slot = data.fromSlot },
                                            inventory = fromInventory.id
                                    },
                                    {
                                            item = toData or { slot = data.toSlot },
                                            inventory = toInventory.id
                                    }
                            }
                    end

            if data.count > fromData.count then
                data.count = fromData.count
            end

            if data.toType == 'newdrop' then
                return dropItem(source, playerInventory, fromData, data)
            end

                    if fromData and (not fromData.metadata.container or fromData.metadata.container and toInventory.type ~= 'container') then
                            local container, containerItem = (not sameInventory and playerInventory.containerSlot) and (fromInventory.type == 'container' and fromInventory or toInventory)

                            if container then
                                    containerItem = playerInventory.items[playerInventory.containerSlot]
                            end

                            local hookPayload = {
                                    source = source,
                                    fromInventory = fromInventory.id,
                                    fromSlot = fromData,
                                    fromType = fromInventory.type,
                                    toInventory = toInventory.id,
                                    toSlot = toData or data.toSlot,
                                    toType = toInventory.type,
                                    count = data.count,
                            }

                            if toData and ((toData.name ~= fromData.name) or not toData.stack or (not table.matches(toData.metadata, fromData.metadata))) then
                                    -- Swap items
                                    local toWeight = not sameInventory and (toInventory.weight - toData.weight + fromData.weight) or 0
                                    local fromWeight = not sameInventory and (fromInventory.weight + toData.weight - fromData.weight) or 0
                                    hookPayload.action = 'swap'

                                    if not sameInventory then
                                            if (toWeight <= toInventory.maxWeight and fromWeight <= fromInventory.maxWeight) then
                                                    if not TriggerEventHooks('swapItems', hookPayload) then return end

                                                    if containerItem then
                                                            local toContainer = toInventory.type == 'container'
                                                            local whitelist = Items.containers[containerItem.name]?.whitelist
                                                            local blacklist = Items.containers[containerItem.name]?.blacklist
                                                            local checkItem = toContainer and fromData.name or toData.name

                                                            if (whitelist and not whitelist[checkItem]) or (blacklist and blacklist[checkItem]) then
                                                                    return
                                                            end

                                                            Inventory.ContainerWeight(containerItem, toContainer and fromWeight or toWeight, playerInventory)
                                                    end

                                                    if fromOtherPlayer then
                                                            TriggerClientEvent('ox_inventory:itemNotify', fromInventory.id, { fromData, 'ui_removed', fromData.count })
                                                            TriggerClientEvent('ox_inventory:itemNotify', fromInventory.id, { toData, 'ui_added', toData.count })
                                                    elseif toOtherPlayer then
                                                            TriggerClientEvent('ox_inventory:itemNotify', toInventory.id, { fromData, 'ui_added', fromData.count })
                                                            TriggerClientEvent('ox_inventory:itemNotify', toInventory.id, { toData, 'ui_removed', toData.count })
                                                    end

                                                    fromInventory.weight = fromWeight
                                                    toInventory.weight = toWeight
                                                    toData, fromData = Inventory.SwapSlots(fromInventory, toInventory, data.fromSlot, data.toSlot) --[[@as table]]

                                                    if server.loglevel > 0 then
                                                            lib.logger(playerInventory.owner, 'swapSlots', ('%sx %s transferred from "%s" to "%s" for %sx %s'):format(fromData.count, fromData.name, fromInventory.owner and fromInventory.label or fromInventory.id, toInventory.owner and toInventory.label or toInventory.id, toData.count, toData.name))
                                                    end
                                            else return false, 'cannot_carry' end
                                    else
                                            if not TriggerEventHooks('swapItems', hookPayload) then return end

                                            toData, fromData = Inventory.SwapSlots(fromInventory, toInventory, data.fromSlot, data.toSlot)
                                    end

                            elseif toData and toData.name == fromData.name and table.matches(toData.metadata, fromData.metadata) then
                                    -- Stack items
                                    toData.count += data.count
                                    fromData.count -= data.count
                                    local toSlotWeight = Inventory.SlotWeight(Items(toData.name), toData)
                                    local totalWeight = toInventory.weight - toData.weight + toSlotWeight

                                    if fromInventory.type == 'container' or sameInventory or totalWeight <= toInventory.maxWeight then
                                            hookPayload.action = 'stack'

                                            if not TriggerEventHooks('swapItems', hookPayload) then
                                                    toData.count -= data.count
                                                    fromData.count += data.count
                                                    return
                                            end

                                            local fromSlotWeight = Inventory.SlotWeight(Items(fromData.name), fromData)
                                            toData.weight = toSlotWeight

                                            if not sameInventory then
                                                    fromInventory.weight = fromInventory.weight - fromData.weight + fromSlotWeight
                                                    toInventory.weight = totalWeight

                                                    if container then
                                                            Inventory.ContainerWeight(containerItem, toInventory.type == 'container' and toInventory.weight or fromInventory.weight, playerInventory)
                                                    end

                                                    if fromOtherPlayer then
                                                            TriggerClientEvent('ox_inventory:itemNotify', fromInventory.id, { fromData, 'ui_removed', data.count })
                                                    elseif toOtherPlayer then
                                                            TriggerClientEvent('ox_inventory:itemNotify', toInventory.id, { toData, 'ui_added', data.count })
                                                    end

                                                    if server.loglevel > 0 then
                                                            lib.logger(playerInventory.owner, 'swapSlots', ('%sx %s transferred from "%s" to "%s"'):format(data.count, fromData.name, fromInventory.owner and fromInventory.label or fromInventory.id, toInventory.owner and toInventory.label or toInventory.id))
                                                    end
                                            end

                                            fromData.weight = fromSlotWeight
                                    else
                                            toData.count -= data.count
                                            fromData.count += data.count
                                            return false, 'cannot_carry'
                                    end
                            elseif data.count <= fromData.count then
                                    -- Move item to an empty slot
                                    toData = table.clone(fromData)
                                    toData.count = data.count
                                    toData.slot = data.toSlot
                                    toData.weight = Inventory.SlotWeight(Items(toData.name), toData)

                                    if fromInventory.type == 'container' or sameInventory or (toInventory.weight + toData.weight <= toInventory.maxWeight) then
                                            hookPayload.action = 'move'

                                            if not TriggerEventHooks('swapItems', hookPayload) then return end

                                            if not sameInventory then
                                                    local toContainer = toInventory.type == 'container'

                                                    if container then
                                                            if toContainer and containerItem then
                                                                    local whitelist = Items.containers[containerItem.name]?.whitelist
                                                                    local blacklist = Items.containers[containerItem.name]?.blacklist

                                                                    if (whitelist and not whitelist[fromData.name]) or (blacklist and blacklist[fromData.name]) then
                                                                            return
                                                                    end
                                                            end
                                                    end

                                                    fromInventory.weight -= toData.weight
                                                    toInventory.weight += toData.weight

                                                    if container then
                                                            Inventory.ContainerWeight(containerItem, toContainer and toInventory.weight or fromInventory.weight, playerInventory)
                                                    end

                                                    if fromOtherPlayer then
                                                            TriggerClientEvent('ox_inventory:itemNotify', fromInventory.id, { fromData, 'ui_removed', data.count })
                                                    elseif toOtherPlayer then
                                                            TriggerClientEvent('ox_inventory:itemNotify', toInventory.id, { fromData, 'ui_added', data.count })
                                                    end

                                                    if server.loglevel > 0 then
                                                            lib.logger(playerInventory.owner, 'swapSlots', ('%sx %s transferred from "%s" to "%s"'):format(data.count, fromData.name, fromInventory.owner and fromInventory.label or fromInventory.id, toInventory.owner and toInventory.label or toInventory.id))
                                                    end
                                            end

                                            fromData.count -= data.count
                                            fromData.weight = Inventory.SlotWeight(Items(fromData.name), fromData)

                                            if fromData.count > 0 then
                                                    toData.metadata = table.clone(toData.metadata)
                                            end
                                    else return false, 'cannot_carry_other' end
                            end

                            if fromData and fromData.count < 1 then fromData = nil end

                            ---@type updateSlot[]
                            local items = {}

                            if fromInventory.player and not fromOtherPlayer then
                                    if toInventory.type == 'container' and containerItem then
                                            items[#items + 1] = {
                                                    item = containerItem,
                                                    inventory = playerInventory.id
                                            }
                                    end
                            end

                            if toInventory.player and not toOtherPlayer then
                                    if fromInventory.type == 'container' and containerItem then
                                            items[#items + 1] = {
                                                    item = containerItem,
                                                    inventory = playerInventory.id
                                            }
                                    end
                            end

                            fromInventory.items[data.fromSlot] = fromData
                            toInventory.items[data.toSlot] = toData

                            if fromInventory.changed ~= nil then fromInventory.changed = true end
                            if toInventory.changed ~= nil then toInventory.changed = true end

                            if sameInventory then
                                    fromInventory:syncSlotsWithClients({
                                            {
                                                    item = fromInventory.items[data.toSlot] or { slot = data.toSlot },
                                                    inventory = fromInventory.id
                                            },
                                            {
                                                    item = fromInventory.items[data.fromSlot] or { slot = data.fromSlot },
                                                    inventory = fromInventory.id
                                            }
                                    }, { left = fromInventory.weight }, true)
                            else
                                    toInventory:syncSlotsWithClients({
                                            {
                                                    item = toInventory.items[data.toSlot] or { slot = data.toSlot },
                                                    inventory = toInventory.id
                                            }
                                    }, { left = toInventory.weight }, true)

                                    fromInventory:syncSlotsWithClients({
                                            {
                                                    item = fromInventory.items[data.fromSlot] or { slot = data.fromSlot },
                                                    inventory = fromInventory.id
                                            }
                                    }, { left = fromInventory.weight }, true)
                            end

                            local resp

                            if next(items) then
                                    resp = { weight = playerInventory.weight, items = items }
                            end

                            if server.syncInventory then
                                    if fromInventory.player then
                                            server.syncInventory(fromInventory)
                                    end

                                    if toInventory.player and not sameInventory then
                                            server.syncInventory(toInventory)
                                    end
                            end

                            local weaponSlot

                            if toInventory.weapon == data.toSlot then
                                    if not sameInventory then
                                            toInventory.weapon = nil
                                            TriggerClientEvent('ox_inventory:disarm', toInventory.id)
                                    else
                                            weaponSlot = data.fromSlot
                                            toInventory.weapon = weaponSlot
                                    end
                            end

                            if fromInventory.weapon == data.fromSlot then
                                    if not sameInventory then
                                            fromInventory.weapon = nil
                                            TriggerClientEvent('ox_inventory:disarm', fromInventory.id)
                                    elseif not weaponSlot then
                                            weaponSlot = data.toSlot
                                            fromInventory.weapon = weaponSlot
                                    end
                            end

                            return containerItem and containerItem.weight or true, resp, weaponSlot
                    end
            end
    end)

    function Inventory.Confiscate(source)
            local inv = Inventories[source]

            if inv?.player then
                    db.saveStash(inv.owner, inv.owner, json.encode(minimal(inv)))
                    table.wipe(inv.items)
                    inv.weight = 0
                    inv.changed = true

                    TriggerClientEvent('ox_inventory:inventoryConfiscated', inv.id)

                    if server.syncInventory then server.syncInventory(inv) end
            end
    end
    exports('ConfiscateInventory', Inventory.Confiscate)

    function Inventory.Return(source)
            local inv = Inventories[source]

            if inv?.player then
                    MySQL.scalar('SELECT data FROM ox_inventory WHERE name = ?', { inv.owner }, function(data)
                            if data then
                                    MySQL.query('DELETE FROM ox_inventory WHERE name = ?', { inv.owner })
                                    data = json.decode(data)
                                    local inventory, totalWeight = {}, 0

                                    if data and next(data) then
                                            for i = 1, #data do
                                                    local i = data
                                                    if type(i) == 'number' then break end
                                                    local item = Items(i.name)
                                                    if item then
                                                            local weight = Inventory.SlotWeight(item, i)
                                                            totalWeight = totalWeight + weight
                                                            inventory[i.slot] = {name = i.name, label = item.label, weight = weight, slot = i.slot, count = i.count, description = item.description, metadata = i.metadata, stack = item.stack, close = item.close}
                                                    end
                                            end
                                    end

                                    inv.changed = true
                                    inv.weight = totalWeight
                                    inv.items = inventory

                                    TriggerClientEvent('ox_inventory:inventoryReturned', source, {inventory, totalWeight})

                                    if server.syncInventory then server.syncInventory(inv) end
                            end
                    end)
            end
    end
    exports('ReturnInventory', Inventory.Return)

    ---@param inv inventory
    ---@param keep? string | string[] an item or list of items to ignore while clearing items
    function Inventory.Clear(inv, keep)
            inv = Inventory(inv) --[[@as OxInventory]]

            if not inv or not next(inv.items) then return end

            local updateSlots = {}
            local newWeight = 0
            local inc = 0

            if keep then
                    local keptItems = {}
                    local keepType = type(keep)

                    if keepType == 'string' then
                            for slot, v in pairs(inv.items) do
                                    if v.name == keep then
                                            keptItems[v.slot] = v
                                            newWeight += v.weight
                                    elseif updateSlots then
                                            inc += 1
                                            updateSlots[inc] = { item = { slot = slot }, inventory = inv.id }
                                    end
                            end
                    elseif keepType == 'table' and table.type(keep) == 'array' then
                            for slot, v in pairs(inv.items) do
                                    for i = 1, #keep do
                                            if v.name == keep then
                                                    keptItems[v.slot] = v
                                                    newWeight += v.weight
                                                    goto foundItem
                                            end
                                    end

                                    if updateSlots then
                                            inc += 1
                                            updateSlots[inc] = { item = { slot = slot }, inventory = inv.id }
                                    end

                                    ::foundItem::
                            end
                    end

                    table.wipe(inv.items)
                    inv.items = keptItems
            else
                    if updateSlots then
                            for slot in pairs(inv.items) do
                                    inc += 1
                                    updateSlots[inc] = { item = { slot = slot }, inventory = inv.id }
                            end
                    end

                    table.wipe(inv.items)
            end

            inv.weight = newWeight
            inv.changed = true

            inv:syncSlotsWithClients(updateSlots, {
                    left = inv.weight,
                    right = inv.open and Inventories[inv.open]?.weight or nil
            }, true)

            if not inv.player then
                    if inv.open then
                            local playerInv = Inventory(inv.open)

                            if not playerInv then return end

                            playerInv:closeInventory()
                    end

                    inv:openInventory(inv)

                    return
            end

            if server.syncInventory then server.syncInventory(inv) end

            inv.weapon = nil
    end

    exports('ClearInventory', Inventory.Clear)

    ---@param inv inventory
    ---@return integer?
    function Inventory.GetEmptySlot(inv)
            local inventory = Inventory(inv)

            if not inventory then return end

            local items = inventory.items

            for i = 1, inventory.slots do
                    if not items then
                            return i
                    end
            end
    end

    exports('GetEmptySlot', Inventory.GetEmptySlot)

    ---@param inv inventory
    ---@param itemName string
    ---@param metadata any
    function Inventory.GetSlotForItem(inv, itemName, metadata)
            local inventory = Inventory(inv)
            local item = Items(itemName) --[[@as OxServerItem?]]

            if not inventory or not item then return end

            metadata = assertMetadata(metadata)
            local items = inventory.items
            local emptySlot

            for i = 1, inventory.slots do
                    local slotData = items

                    if item.stack and slotData and slotData.name == item.name and table.matches(slotData.metadata, metadata) then
                            return i
                    elseif not item.stack and not slotData and not emptySlot then
                            emptySlot = i
                    end
            end

            return emptySlot
    end

    exports('GetSlotForItem', Inventory.GetSlotForItem)

    ---@param inv inventory
    ---@param itemName string
    ---@param metadata? any
    ---@param strict? boolean Strictly match metadata properties, otherwise use partial matching.
    ---@return SlotWithItem?
    function Inventory.GetSlotWithItem(inv, itemName, metadata, strict)
            local inventory = Inventory(inv)
            local item = Items(itemName) --[[@as OxServerItem?]]

            if not inventory or not item then return end

            metadata = assertMetadata(metadata)
            local tablematch = strict and table.matches or table.contains

            for _, slotData in pairs(inventory.items) do
                    if slotData and slotData.name == item.name and (not metadata or tablematch(slotData.metadata, metadata)) then
                if not Items.UpdateDurability(inventory, slotData, item, nil, os.time()) then
                    return slotData
                end
                    end
            end
    end

    exports('GetSlotWithItem', Inventory.GetSlotWithItem)

    ---@param inv inventory
    ---@param itemName string
    ---@param metadata? any
    ---@param strict? boolean Strictly match metadata properties, otherwise use partial matching.
    ---@return number?
    function Inventory.GetSlotIdWithItem(inv, itemName, metadata, strict)
            return Inventory.GetSlotWithItem(inv, itemName, metadata, strict)?.slot
    end

    exports('GetSlotIdWithItem', Inventory.GetSlotIdWithItem)

    ---@param inv inventory
    ---@param itemName string
    ---@param metadata? any
    ---@param strict? boolean Strictly match metadata properties, otherwise use partial matching.
    ---@return SlotWithItem[]?
    function Inventory.GetSlotsWithItem(inv, itemName, metadata, strict)
            local inventory = Inventory(inv)
            local item = Items(itemName) --[[@as OxServerItem?]]

            if not inventory or not item then return end

            metadata = assertMetadata(metadata)
            local response = {}
            local n = 0
            local tablematch = strict and table.matches or table.contains
            local ostime = os.time()

            for _, slotData in pairs(inventory.items) do
                    if slotData and slotData.name == item.name and (not metadata or tablematch(slotData.metadata, metadata)) then
                if not Items.UpdateDurability(inventory, slotData, item, nil, os.time()) then
                    n += 1
                    response[n] = slotData
                end
                    end
            end

            return response
    end

    exports('GetSlotsWithItem', Inventory.GetSlotsWithItem)

    ---@param inv inventory
    ---@param itemName string
    ---@param metadata? any
    ---@param strict? boolean Strictly match metadata properties, otherwise use partial matching.
    ---@return number[]?
    function Inventory.GetSlotIdsWithItem(inv, itemName, metadata, strict)
            local items = Inventory.GetSlotsWithItem(inv, itemName, metadata, strict)

            if items then
                    ---@cast items +number[]
                    for i = 1, #items do
                            items = items.slot
                    end

                    return items
            end
    end

    ---@param inv inventory
    ---@param itemName string
    ---@param metadata? any
    ---@param strict? boolean Strictly match metadata properties, otherwise use partial matching.
    ---@return number
    function Inventory.GetItemCount(inv, itemName, metadata, strict)
            local inventory = Inventory(inv)
            local item = Items(itemName) --[[@as OxServerItem?]]

            if not inventory or not item then return 0 end

            metadata = assertMetadata(metadata)
            local count = 0
            local tablematch = strict and table.matches or table.contains

            for _, slotData in pairs(inventory.items) do
                    if slotData and slotData.name == item.name and (not metadata or tablematch(slotData.metadata, metadata)) then
                            count += slotData.count
                    end
            end

            return count
    end

    exports('GetItemCount', Inventory.GetItemCount)

    ---@param inv OxInventory
    ---@param buffer table
    ---@param time integer
    ---@return integer | false | nil
    ---@return table | nil
    local function prepareInventorySave(inv, buffer, time)
        local shouldSave = not inv.datastore and inv.changed
        local n = 0

        for k, v in pairs(inv.items) do
            if not Items.UpdateDurability(inv, v, Items(v.name), nil, time) and shouldSave then
                n += 1
                buffer[n] = {
                    name = v.name,
                    count = v.count,
                    slot = k,
                    metadata = next(v.metadata) and v.metadata or nil
                }
            end
            end

        if not shouldSave then return end

        local data = next(buffer) and json.encode(buffer) or nil
        inv.changed = false
        table.wipe(buffer)

        if inv.player then
            if shared.framework == 'esx' then return end

            return 1, { data, inv.owner }
        end

        if inv.type == 'trunk' then
            return 2, { data, inv.dbId }
        end

        if inv.type == 'glovebox' then
            return 3, { data, inv.dbId }
        end

        return 4, { data, inv.owner and tostring(inv.owner) or '', inv.dbId }
    end

    local function saveInventories(manual)
            local time = os.time()
            local parameters = { {}, {}, {}, {} }
            local size = { 0, 0, 0, 0 }
        local buffer = {}

            for _, inv in pairs(Inventories) do
            local index, data = prepareInventorySave(inv, buffer, time)

            if index then
                size[index] += 1
                parameters[index][size[index]] = data
            end
            end

            db.saveInventories(parameters[1], parameters[2], parameters[3], parameters[4])

        if not manual then return end

        for _, inv in pairs(Inventories) do
            if not inv.open then
                if inv.datastore and inv.netid and (inv.type == 'trunk' or inv.type == 'glovebox') then
                    if NetworkGetEntityFromNetworkId(inv.netid) == 0 then
                        Inventory.Remove(inv)
                    end
                elseif not inv.player and (inv.datastore or inv.owner) and time - inv.time >= 1200 then
                    -- inv.time is a timestamp for when the inventory was last closed
                    -- if unopened for n seconds, the inventory is unloaded (datastore/temp stash is deleted)
                    Inventory.Remove(inv)
                end
            end
        end
    end

    lib.cron.new('*/5 * * * *', function()
        saveInventories()
    end)

    function Inventory.SaveInventories(lock)
            Inventory.Lock = lock or nil

            Inventory.CloseAll()
        saveInventories(lock)
    end

    AddEventHandler('playerDropped', function()
            if GetNumPlayerIndices() == 0 then
                    Inventory.SaveInventories()
            end
    end)

    AddEventHandler('txAdmin:events:serverShuttingDown', function()
            Inventory.SaveInventories(true)
    end)

    AddEventHandler('onResourceStop', function(resource)
            if resource == shared.resource then
                    Inventory.SaveInventories(true)
            end
    end)

    RegisterServerEvent('ox_inventory:closeInventory', function()
            local inventory = Inventories[source]

            if inventory?.open then
                    local secondary = Inventories[inventory.open]

                    if secondary then
                            secondary:closeInventory()
                    end

                    inventory:closeInventory(true)
            end
    end)

    RegisterServerEvent('ox_inventory:giveItem', function(slot, target, count)
            local fromInventory = Inventories[source]
            local toInventory = Inventories[target]

            if count <= 0 then count = 1 end

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

                    if not data then return end

                    local item = Items(data.name)

                    if not item or data.count < count or not Inventory.CanCarryItem(toInventory, item, count, data.metadata) then
                            return TriggerClientEvent('ox_lib:notify', fromInventory.id, { type = 'error', description = locale('cannot_give', count, data.label) })
                    end

                    local toSlot = Inventory.GetSlotForItem(toInventory, data.name, data.metadata)
                    local fromRef = ('%s:%s'):format(fromInventory.id, slot)
                    local toRef = ('%s:%s'):format(toInventory.id, toSlot)

                    if activeSlots[fromRef] or activeSlots[toRef] then
                            return TriggerClientEvent('ox_lib:notify', fromInventory.id, { type = 'error', description = locale('cannot_give', count, data.label) })
                    end

                    activeSlots[fromRef] = true
                    activeSlots[toRef] = true

                    local _ <close> = defer(function()
                            activeSlots[fromRef] = nil
                            activeSlots[toRef] = nil
                    end)

                    if TriggerEventHooks('swapItems', {
                            source = fromInventory.id,
                            fromInventory = fromInventory.id,
                            fromType = fromInventory.type,
                            toInventory = toInventory.id,
                            toType = toInventory.type,
                            count = data.count,
                            action = 'give',
                            fromSlot = data,
                    }) then
                            ---@todo manually call swapItems or something?
                            if Inventory.AddItem(toInventory, item, count, data.metadata, toSlot) then
                                    if Inventory.RemoveItem(fromInventory, item, count, data.metadata, slot) then
                                            if server.loglevel > 0 then
                                                    lib.logger(fromInventory.owner, 'giveItem', ('"%s" gave %sx %s to "%s"'):format(fromInventory.label, count, data.name, toInventory.label))
                                            end

                                            return
                                    end
                            end
                    end

                    return TriggerClientEvent('ox_lib:notify', fromInventory.id, { type = 'error', description = locale('cannot_give', count, data.label) })
            end
    end)

    local function updateWeapon(source, action, value, slot, specialAmmo)
            local inventory = Inventories[source]

            if not inventory then return end

            if not action then
                    inventory.weapon = nil
                    return
            end

            local type = type(value)

            if type == 'table' and action == 'component' then
                    local item = inventory.items[value.slot]

                    if item then
                            if item.metadata.components then
                                    for k, v in pairs(item.metadata.components) do
                                            if v == value.component then
                                                    if not Inventory.AddItem(inventory, value.component, 1) then return end

                                                    table.remove(item.metadata.components, k)
                                                    inventory:syncSlotsWithPlayer({
                                                            { item = item }
                                                    }, inventory.weight)

                                                    return true
                                            end
                                    end
                            end
                    end
            else
                    if not slot then slot = inventory.weapon end
                    local weapon = inventory.items[slot]

                    if weapon and weapon.metadata then
                            local item = Items(weapon.name)

                            if not item.weapon then
                                    inventory.weapon = nil
                                    return
                            end

                            if action == 'load' and weapon.metadata.durability > 0 then
                                    local ammo = Items(weapon.name).ammoname
                                    local diff = value - (weapon.metadata.ammo or 0)

                                    if not Inventory.RemoveItem(inventory, ammo, diff, specialAmmo) then return end

                                    weapon.metadata.ammo = value
                                    weapon.metadata.specialAmmo = specialAmmo
                                    weapon.weight = Inventory.SlotWeight(item, weapon)
                            elseif action == 'throw' then
                                    if not Inventory.RemoveItem(inventory, weapon.name, 1, weapon.metadata, weapon.slot) then return end
                            elseif action == 'component' then
                                    if type == 'number' then
                                            if not Inventory.AddItem(inventory, weapon.metadata.components[value], 1) then return false end

                                            table.remove(weapon.metadata.components, value)
                                            weapon.weight = Inventory.SlotWeight(item, weapon)
                                    elseif type == 'string' then
                                            local component = inventory.items[tonumber(value)]

                                            if not Inventory.RemoveItem(inventory, component.name, 1) then return false end

                                            table.insert(weapon.metadata.components, component.name)
                                            weapon.weight = Inventory.SlotWeight(item, weapon)
                                    end
                            elseif action == 'ammo' then
                                    if item.hash == `WEAPON_FIREEXTINGUISHER` or item.hash == `WEAPON_PETROLCAN` or item.hash == `WEAPON_HAZARDCAN` or item.hash == `WEAPON_FERTILIZERCAN` then
                                            weapon.metadata.durability = math.floor(value)
                                            weapon.metadata.ammo = weapon.metadata.durability
                                    elseif value < weapon.metadata.ammo then
                                            local durability = Items(weapon.name).durability * math.abs((weapon.metadata.ammo or 0.1) - value)
                                            weapon.metadata.ammo = value
                                            weapon.metadata.durability = weapon.metadata.durability - durability
                                            weapon.weight = Inventory.SlotWeight(item, weapon)
                                    end
                            elseif action == 'melee' and value > 0 then
                                    weapon.metadata.durability = weapon.metadata.durability - ((Items(weapon.name).durability or 1) * value)
                            end

                            if action ~= 'throw' then
                                    inventory:syncSlotsWithPlayer({
                                            { item = weapon }
                                    }, inventory.weight)
                            end

                            if server.syncInventory then server.syncInventory(inventory) end

                            return true
                    end
            end
    end

    lib.callback.register('ox_inventory:updateWeapon', updateWeapon)

    RegisterNetEvent('ox_inventory:updateWeapon', function(action, value, slot, specialAmmo)
            updateWeapon(source, action, value, slot, specialAmmo)
    end)

    lib.callback.register('ox_inventory:removeAmmoFromWeapon', function(source, slot)
            local inventory = Inventory(source)

            if not inventory then return end

            local slotData = inventory.items[slot]

            if not slotData or not slotData.metadata.ammo or slotData.metadata.ammo < 1 then return end

            local item = Items(slotData.name)

            if not item or not item.ammoname then return end

            if Inventory.AddItem(inventory, item.ammoname, slotData.metadata.ammo, { type = slotData.metadata.specialAmmo or nil }) then
                    slotData.metadata.ammo = 0
                    slotData.weight = Inventory.SlotWeight(item, slotData)

                    inventory:syncSlotsWithPlayer({
                            { item = slotData }
                    }, inventory.weight)

                    if server.syncInventory then server.syncInventory(inventory) end

                    return true
            end
    end)

    local function checkStashProperties(properties)
            local name, slots, maxWeight, coords in properties

            if type(name) ~= 'string' then
                    error(('received %s for stash name (expected string)'):format(type(name)))
            end

            if type(slots) ~= 'number' then
                    error(('received %s for stash slots (expected number)'):format(type(slots)))
            end

            if type(maxWeight) ~= 'number' then
                    error(('received %s for stash maxWeight (expected number)'):format(type(maxWeight)))
            end

            if coords then
                    local typeof = type(coords)

                    if typeof ~= 'vector3' then
                            if typeof == 'table' and table.type(coords) ~= 'array' then
                                    coords = vec3(coords.x or coords[1], coords.y or coords[2], coords.z or coords[3])
                            else
                                    if table.type(coords) == 'array' then
                                            for i = 1, #coords do
                                                    coords = vec3(coords.x, coords.y, coords.z)
                                            end
                                    else
                                            error(('received %s for stash coords (expected vector3 or array of vector3)'):format(typeof))
                                    end
                            end
                    end
            end

            return name, slots, maxWeight, coords
    end

    ---@param name string stash identifier when loading from the database
    ---@param label string display name when inventory is open
    ---@param slots number
    ---@param maxWeight number
    ---@param owner? string|number|boolean
    ---@param groups? table<string, number>
    ---@param coords? vector3|table<vector3>
    --- For simple integration with other resources that want to create valid stashes.
    --- This needs to be triggered before a player can open a stash.
    --- ```
    --- Owner sets the stash permissions.
    --- string: can only access the stash linked to the owner (usually player identifier)
    --- true: each player has a unique stash, but can request other player's stashes
    --- nil: always shared
    ---
    --- groups: { ['police'] = 0 }
    --- ```
    local function registerStash(name, label, slots, maxWeight, owner, groups, coords)
            name, slots, maxWeight, coords = checkStashProperties({
                    name = name,
                    slots = slots,
                    maxWeight = maxWeight,
                    coords = coords,
            })

            local curStash = RegisteredStashes[name]

            if curStash then
                    ---@todo creating proper stash classes with inheritence would simplify updating data
                    ---i.e. all stashes with the same type share groups, maxweight, slots, dbid, etc.
                    ---only label, owner, weight, coords, and items really need to vary
                    for _, stash in pairs(Inventories) do
                            if stash.type == 'stash' and stash.dbId == name then
                                    stash.label = label or stash.label
                                    stash.owner = (owner and owner ~= true) and stash.owner or owner
                                    stash.slots = slots or stash.slots
                                    stash.maxWeight = maxWeight or stash.maxWeight
                                    stash.groups = groups or stash.groups
                                    stash.coords = coords or stash.coords
                            end
                    end
            end

            RegisteredStashes[name] = {
                    name = name,
                    label = label,
                    owner = owner,
                    slots = slots,
                    maxWeight = maxWeight,
                    groups = groups,
                    coords = coords
            }
    end

    exports('RegisterStash', registerStash)

    ---@class TemporaryStashProperties
    ---@field label string
    ---@field slots number
    ---@field maxWeight number
    ---@field owner? string|number|boolean
    ---@field groups? table<string, number>
    ---@field coords? vector3
    ---@field items? { [number]: string, [number]: number, [number]: table | string }[]

    ---@param properties TemporaryStashProperties
    function Inventory.CreateTemporaryStash(properties)
            properties.name = generateInvId('temp')

            local name, slots, maxWeight, coords = checkStashProperties(properties)
            local inventory = Inventory.Create(name, properties.label, 'temp', slots, 0, maxWeight, properties.owner, {}, properties.groups)

            if not inventory then return end

            inventory.items, inventory.weight = generateItems(inventory, 'drop', properties.items)
            inventory.coords = coords

            return inventory.id
    end

    exports('CreateTemporaryStash', Inventory.CreateTemporaryStash)

    return Inventory


    回复

    使用道具 举报

    ChatGPT 发表于 2024-4-8 20:06:23 | 显示全部楼层
    waaaa 发表于 2024-04-08 20:06
    翻译一下



    if not lib then return end

    local Inve ...


    这段代码是在服务器脚本文件中的lua代码,主要是关于一个自定义背包系统的逻辑代码。代码通过定义了不同的函数来处理玩家的背
    回复

    使用道具 举报

    文明发言,和谐互动
    文明发言,和谐互动
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|小黑屋|首页 ( 冀ICP备2021005225号-1 ) |网站地图

    GMT+8, 2024-9-20 22:22 , Processed in 0.072251 second(s), 8 queries , Redis On.

    Powered by Discuz! X3.4

    © 2001-2013 ELITE论坛【技术无价-分享快乐】

    快速回复 返回顶部 返回列表